{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "otp-input",
  "type": "registry:ui",
  "title": "OTP Input",
  "description": "Verification code field with auto-focus movement and paste detection.",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/otp-input.tsx",
      "content": "import * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface OTPInputProps {\n  length?: number\n  value?: string\n  defaultValue?: string\n  onChange?: (value: string) => void\n  onComplete?: (value: string) => void\n  disabled?: boolean\n  className?: string\n  ariaLabel?: string\n}\n\nexport function OTPInput({\n  length = 4,\n  value,\n  defaultValue = '',\n  onChange,\n  onComplete,\n  disabled = false,\n  className,\n  ariaLabel = 'One-time code',\n}: OTPInputProps) {\n  const isControlled = value !== undefined\n  const [internal, setInternal] = React.useState(defaultValue)\n  const current = (isControlled ? value : internal) ?? ''\n  const inputRefs = React.useRef<Array<HTMLInputElement | null>>([])\n  const completedRef = React.useRef(false)\n\n  const setValue = (next: string) => {\n    const trimmed = next.slice(0, length)\n    if (!isControlled) setInternal(trimmed)\n    onChange?.(trimmed)\n    if (trimmed.length === length) {\n      if (!completedRef.current) {\n        completedRef.current = true\n        onComplete?.(trimmed)\n      }\n    } else {\n      completedRef.current = false\n    }\n  }\n\n  const focusIndex = (index: number) => {\n    inputRefs.current[Math.min(Math.max(index, 0), length - 1)]?.focus()\n  }\n\n  const handleChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {\n    const char = event.target.value.replace(/\\D/g, '').slice(-1)\n    const chars = current.split('')\n    chars[index] = char\n    setValue(chars.join(''))\n    if (char) focusIndex(index + 1)\n  }\n\n  const handleKeyDown = (index: number, event: React.KeyboardEvent<HTMLInputElement>) => {\n    if (event.key === 'Backspace') {\n      event.preventDefault()\n      if (current[index]) {\n        const chars = current.split('')\n        chars[index] = ''\n        setValue(chars.join(''))\n      } else {\n        focusIndex(index - 1)\n      }\n    } else if (event.key === 'ArrowLeft') {\n      focusIndex(index - 1)\n    } else if (event.key === 'ArrowRight') {\n      focusIndex(index + 1)\n    }\n  }\n\n  const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {\n    event.preventDefault()\n    const pasted = event.clipboardData.getData('text').replace(/\\D/g, '').slice(0, length)\n    if (pasted) {\n      setValue(pasted)\n      focusIndex(Math.min(pasted.length, length - 1))\n    }\n  }\n\n  return (\n    <div className={cn('flex items-center gap-2 font-sans', className)} role=\"group\" aria-label={ariaLabel}>\n      {Array.from({ length }).map((_, index) => (\n        <input\n          key={index}\n          ref={(el) => {\n            inputRefs.current[index] = el\n          }}\n          type=\"text\"\n          inputMode=\"numeric\"\n          autoComplete={index === 0 ? 'one-time-code' : 'off'}\n          aria-label={`${ariaLabel} digit ${index + 1}`}\n          value={current[index] ?? ''}\n          disabled={disabled}\n          onChange={(event) => handleChange(index, event)}\n          onKeyDown={(event) => handleKeyDown(index, event)}\n          onPaste={handlePaste}\n          className={cn(\n            'h-12 w-11 rounded-xl bg-rk-surface text-center font-mono text-lg font-black text-rk-ink rk-border-sm rk-shadow-sm transition-all outline-none',\n            'focus:bg-rk-primary/40 focus:ring-4 focus:ring-black/15',\n            'disabled:opacity-50',\n            current[index] && 'bg-rk-primary/25'\n          )}\n        />\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/otp-input.tsx"
    }
  ]
}