حقل الرمز (OTP Input)
حقل لإدخال الرموز التي تُستخدم مرة واحدة، لكل حرف منها خانة. ويمكن كتابة الرمز أو لصقه أو تعبئته تلقائيًا من الرسالة النصية.
التثبيت
bash
npx @smicolon/fasla-ui@latest add input-otpقبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.
معاينة
الأنماط (variant)
أربعة أنواع تختلف في طريقة تجميع الخانات.
بسيط
أرقام فقط
مع فاصل
مع تباعد
الحالات
الخانة النشطة هي التي سيُكتب فيها الحرف التالي. يُظهر الحقل حالة الخطأ عند إدخال رمز غير صحيح، ويخفت كله عند تعطيله.
الافتراضية
النشطة
التحذيرية
المعطّلة
التسمية ورسالة الخطأ
أعطِ الحقل تسمية ظاهرة، واعرض رسالة الخطأ تحته ليقرأها قارئ الشاشة مع الحقل.
رمز التحقق
أدخل الرمز المكوّن من 6 أرقام الوارد في الرسالة النصية. في هذا المثال الرمز هو 123456.
إتاحة الوصول (accessibility)
- يقع فوق الخانات حقل
inputحقيقي واحد يحمل الرمز كله، فيقرؤه قارئ الشاشة حقلًا نصيًا واحدًا. ويطلب من الهاتف الرمز الوارد في الرسالة النصية بالقيمةautoComplete="one-time-code"، ويُظهر لوحة الأرقام بالقيمةinputMode="numeric". أما الخانات فمخفية عن قارئ الشاشة، حتى لا يُقرأ الرمز مرتين. - سمِّ الحقل دائمًا: بالسمة
aria-labelledbyمع تسمية ظاهرة، أو بالسمةaria-labelإن لم توجد تسمية. - تصل السمة
aria-invalidإلى الحقل نفسه، فيُعلن قارئ الشاشة أن الرمز غير صالح. واربط رسالة الخطأ بالسمةaria-describedby. - تمتلئ الخانات بالترتيب عند الكتابة، ويمسح مفتاح Backspace آخرها. وتنقلك مفاتيح الأسهم بين الخانات، ويملؤها اللصق كلها دفعة واحدة. ويُتجاهل كل حرف لا يطابق الخاصية
pattern. - إذا كتبت الرمز أو لصقته بالأرقام العربية المشرقية، مثل ١٢٣، حُفظ بالأرقام اللاتينية، فيبقى الرمز واحدًا في كل اللغات ويطابق الخاصية
patternالتي تقبل الأرقام وحدها. - يتبع الاتجاه السمة
dirفي الصفحة، ولا توجد له خاصية. في الصفحات من اليمين إلى اليسار تقع الخانة الأولى على اليمين ويمتلئ الرمز منها. ويتحرك كل سهم في الاتجاه الذي يشير إليه، فالسهم الأيسر ينقلك إلى الخانة التالية، في كل المتصفحات.
الخصائص (props)
InputOTP
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
maxLength | number | لا يوجد | عدد حروف الرمز، ولكل حرف خانة. وهي خاصية مطلوبة. |
value | string | لا يوجد | الرمز في الحقل المتحكَّم به. وتُستخدم مع الدالة onChange. |
defaultValue | string | لا يوجد | الرمز عند العرض الأول، في الحقل غير المتحكَّم به. |
onChange | (value) => void | لا يوجد | تُستدعى بالرمز الجديد عند كل تغيير. |
onComplete | (value) => void | لا يوجد | تُستدعى بالرمز حين تمتلئ الخانة الأخيرة. |
pattern | string | لا يوجد | تعبير نمطي يجب أن يطابقه الرمز، مثل REGEXP_ONLY_DIGITS من الحزمة input-otp. ويُتجاهل كل تغيير لا يطابقه. وتُقرأ الأرقام العربية المشرقية أرقامًا لاتينية. |
pasteTransformer | (text) => string | لا يوجد | تنظّف النص الملصوق قبل فحصه، كأن تحذف المسافات والشرطات من رمز منسوخ. |
inputMode | string | "numeric" | لوحة المفاتيح التي تظهر على الشاشة. استخدم القيمة "text" للرموز التي فيها حروف. |
autoComplete | string | "one-time-code" | تتيح للهاتف أن يقترح الرمز الوارد في الرسالة النصية. |
disabled | boolean | false | تعطّل الحقل وتخفّفه إلى 50%. |
aria-invalid | boolean | لا يوجد | تجعل الرمز خاطئًا: يُعلن الحقل غير صالح، ويصير حدّ كل خانة أحمر. |
aria-label | string | لا يوجد | الاسم الذي يقرؤه قارئ الشاشة حين لا توجد تسمية ظاهرة. |
aria-labelledby | string | لا يوجد | معرّف التسمية الظاهرة التي تسمّي الحقل. |
aria-describedby | string | لا يوجد | معرّف النص المساعد أو رسالة الخطأ التي تُقرأ مع الحقل. |
name | string | لا يوجد | الاسم الذي يُرسل به الرمز مع النموذج. |
containerClassName | string | لا يوجد | أصناف إضافية للصف الذي يضم المجموعات والفواصل، مثل gap-3 لمسافة أوسع من 8px الافتراضية بينها. |
children | ReactNode | لا يوجد | المجموعات والفواصل. |
className | string | لا يوجد | أصناف إضافية للحقل المخفي. |
InputOTPGroup
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
children | ReactNode | لا يوجد | الخانات، متجاورة بحدود مشتركة. |
className | string | لا يوجد | أصناف إضافية للمجموعة. |
InputOTPSlot
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
index | number | لا يوجد | ترتيب الحرف الذي تعرضه الخانة في الرمز، بدءًا من 0. |
aria-invalid | boolean | لا يوجد | تجعل هذه الخانة وحدها خاطئة. ومن دونها تتبع الخانة السمة aria-invalid على المكوّن InputOTP. |
className | string | لا يوجد | أصناف إضافية للخانة. |
InputOTPSeparator
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
children | ReactNode | لا يوجد | يحلّ محلّ الخط بمحتوى من عندك، في المربع نفسه بعرض 24px. |
className | string | لا يوجد | أصناف إضافية للفاصل. |
الاستخدام
حقل بسيط من ستة أرقام
tsx
import { REGEXP_ONLY_DIGITS } from "input-otp"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
export function VerificationCode() {
return (
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS}
aria-label="رمز التحقق"
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
)
}مع تسمية ورسالة خطأ
tsx
"use client"
import * as React from "react"
import { REGEXP_ONLY_DIGITS } from "input-otp"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"
export function VerifyCode({ onVerify }: { onVerify: (code: string) => Promise<boolean> }) {
const labelId = React.useId()
const messageId = React.useId()
const [code, setCode] = React.useState("")
const [wrong, setWrong] = React.useState(false)
return (
<div className="flex flex-col gap-2">
<span id={labelId} className="text-sm font-medium">
رمز التحقق
</span>
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS}
value={code}
onChange={(next) => {
setCode(next)
setWrong(false)
}}
onComplete={async (next) => setWrong(!(await onVerify(next)))}
aria-labelledby={labelId}
aria-describedby={messageId}
aria-invalid={wrong || undefined}
>
<InputOTPGroup>
{Array.from({ length: 6 }, (_, index) => (
<InputOTPSlot key={index} index={index} />
))}
</InputOTPGroup>
</InputOTP>
<p id={messageId} className={wrong ? "text-xs text-destructive" : "text-xs text-muted-foreground"}>
{wrong ? "الرمز غير صحيح. راجع الرسالة وحاول مرة أخرى." : "أدخل الرمز المكوّن من 6 أرقام الوارد في الرسالة النصية."}
</p>
</div>
)
}رقم سري بخانات متباعدة
tsx
import { REGEXP_ONLY_DIGITS } from "input-otp"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"
export function PinField() {
return (
<InputOTP maxLength={4} pattern={REGEXP_ONLY_DIGITS} name="pin" aria-label="الرقم السري">
{Array.from({ length: 4 }, (_, index) => (
<InputOTPGroup key={index}>
<InputOTPSlot index={index} />
</InputOTPGroup>
))}
</InputOTP>
)
}