{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "otp-input",
  "type": "registry:ui",
  "title": "OtpInput",
  "description": "کد تأیید (OTP)",
  "dependencies": [
    "@kashi-ui/persian@^1.8.0"
  ],
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/field.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json",
    "https://kashi.sepantapouya.com/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/kashi/OtpInput.tsx",
      "type": "registry:ui",
      "target": "components/kashi/OtpInput.tsx",
      "content": "\"use client\";\n\nimport { forwardRef, useId, useRef, useState, type ReactNode } from \"react\";\nimport { onlyDigits, toFaDigits } from \"@kashi-ui/persian\";\nimport { cx } from \"./cx\";\nimport { Field, describedBy } from \"./Field\";\nimport { mergeRefs } from \"./utils\";\nimport { useKashi } from \"./KashiProvider\";\n\nexport interface OtpInputProps {\n  label?: ReactNode;\n  hint?: ReactNode;\n  error?: ReactNode;\n  /** تعداد رقم @default 6 */\n  length?: number;\n  value?: string;\n  defaultValue?: string;\n  onChange?: (code: string) => void;\n  /** وقتی همهٔ رقم‌ها وارد شد */\n  onComplete?: (code: string) => void;\n  /** @default \"fa\" */\n  digits?: \"fa\" | \"latin\";\n  /** خط فاصله بعد از این تعداد رقم، مثلاً 3 برای ۱۲۳-۴۵۶ */\n  groupAfter?: number;\n  size?: \"sm\" | \"md\";\n  disabled?: boolean;\n  autoFocus?: boolean;\n  name?: string;\n  id?: string;\n  className?: string;\n}\n\n/**\n * کد تأیید یک‌بارمصرف: یک input واقعی (نه شش input جدا)، پس چسباندن، پاک کردن،\n * صفحه‌خوان و پر شدن خودکار کد پیامکی (autocomplete=\"one-time-code\") بی‌نقص کار می‌کنند.\n */\nexport const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(function OtpInput(\n  { label, hint, error, length = 6, value, defaultValue = \"\", onChange, onComplete, digits, groupAfter, size = \"md\", disabled, autoFocus, name, id, className },\n  ref,\n) {\n  const { digits: D } = useKashi();\n  digits ??= D;\n  const autoId = useId();\n  const inputId = id ?? autoId;\n  const [inner, setInner] = useState(() => onlyDigits(defaultValue).slice(0, length));\n  const code = value !== undefined ? onlyDigits(value).slice(0, length) : inner;\n  const [caret, setCaret] = useState(code.length);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const d = (c: string) => (digits === \"fa\" ? toFaDigits(c) : c);\n  const active = Math.min(caret, length - 1);\n\n  const sync = () => setCaret(Math.min(inputRef.current?.selectionStart ?? code.length, length));\n\n  return (\n    <Field id={inputId} label={label} hint={hint} error={error} className={className}>\n      <div className={cx(\"k-otp\", size === \"sm\" && \"k-otp--sm\", !!error && \"k-otp--invalid\")} onClick={() => inputRef.current?.focus()}>\n        <input\n          ref={mergeRefs(ref, inputRef)}\n          id={inputId}\n          className=\"k-otp__input\"\n          type=\"text\"\n          inputMode=\"numeric\"\n          autoComplete=\"one-time-code\"\n          pattern=\"\\d*\"\n          maxLength={length * 2 /* ارقام فارسی و فاصله‌های چسبانده‌شده */}\n          value={code}\n          disabled={disabled}\n          autoFocus={autoFocus}\n          aria-invalid={error ? true : undefined}\n          aria-describedby={describedBy(inputId, hint, error)}\n          onChange={(e) => {\n            const next = onlyDigits(e.target.value).slice(0, length);\n            if (value === undefined) setInner(next);\n            onChange?.(next);\n            setCaret(next.length);\n            if (next.length === length && next !== code) onComplete?.(next);\n          }}\n          onSelect={sync}\n          onKeyUp={sync}\n          onFocus={sync}\n        />\n        {Array.from({ length }, (_, i) => (\n          <span key={i} style={{ display: \"contents\" }}>\n            {groupAfter && i === groupAfter && <span className=\"k-otp__sep\" aria-hidden=\"true\" />}\n            <span className=\"k-otp__slot\" aria-hidden=\"true\" data-filled={code[i] ? \"\" : undefined} data-active={i === active ? \"\" : undefined}>\n              {code[i] ? d(code[i]) : \"\"}\n            </span>\n          </span>\n        ))}\n      </div>\n      {name && <input type=\"hidden\" name={name} value={code} />}\n    </Field>\n  );\n});\n"
    }
  ]
}