{
  "$schema": "https://www.cooki-ui.design/schema/registry-item.json",
  "name": "otp-input",
  "type": "registry:ui",
  "title": "OTP Pin Input",
  "description": "6-digit verification code input with auto-advance, backspace handling, paste support, and spring highlight.",
  "category": "forms",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "registry/forms/otp-input/otp-input.tsx",
      "type": "registry:ui",
      "target": "components/ui/otp-input.tsx",
      "content": "import React, { useRef, useState, useEffect } from 'react';\nimport { motion } from 'framer-motion';\n\nexport interface OTPInputProps {\n  length?: number;\n  onComplete?: (code: string) => void;\n  className?: string;\n}\n\nexport const OTPInput: React.FC<OTPInputProps> = ({\n  length = 6,\n  onComplete,\n  className = '',\n}) => {\n  const [digits, setDigits] = useState<string[]>(Array(length).fill(''));\n  const [focusedIndex, setFocusedIndex] = useState<number>(0);\n  const inputRefs = useRef<(HTMLInputElement | null)[]>([]);\n\n  useEffect(() => {\n    setDigits((prev) => {\n      if (prev.length === length) return prev;\n      const next = Array(length).fill('');\n      for (let i = 0; i < Math.min(prev.length, length); i++) {\n        next[i] = prev[i];\n      }\n      return next;\n    });\n  }, [length]);\n\n  const handleChange = (index: number, val: string) => {\n    const char = val.slice(-1);\n    if (!/^\\d*$/.test(char)) return;\n\n    const next = [...digits];\n    next[index] = char;\n    setDigits(next);\n\n    if (char && index < length - 1) {\n      inputRefs.current[index + 1]?.focus();\n      setFocusedIndex(index + 1);\n    }\n\n    if (next.every((d) => d !== '') && next.length === length) {\n      onComplete?.(next.join(''));\n    }\n  };\n\n  const handleKeyDown = (index: number, e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === 'Backspace') {\n      if (!digits[index] && index > 0) {\n        inputRefs.current[index - 1]?.focus();\n        setFocusedIndex(index - 1);\n      } else if (digits[index]) {\n        const next = [...digits];\n        next[index] = '';\n        setDigits(next);\n      }\n    } else if (e.key === 'ArrowLeft' && index > 0) {\n      inputRefs.current[index - 1]?.focus();\n      setFocusedIndex(index - 1);\n    } else if (e.key === 'ArrowRight' && index < length - 1) {\n      inputRefs.current[index + 1]?.focus();\n      setFocusedIndex(index + 1);\n    }\n  };\n\n  const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {\n    e.preventDefault();\n    const pasted = e.clipboardData.getData('text').trim().slice(0, length);\n    if (!/^\\d+$/.test(pasted)) return;\n\n    const next = [...digits];\n    pasted.split('').forEach((char, idx) => {\n      if (idx < length) next[idx] = char;\n    });\n    setDigits(next);\n    const targetIdx = Math.min(pasted.length, length - 1);\n    inputRefs.current[targetIdx]?.focus();\n    setFocusedIndex(targetIdx);\n\n    if (next.every((d) => d !== '')) {\n      onComplete?.(next.join(''));\n    }\n  };\n\n  return (\n    <div className={`flex items-center gap-2 ${className}`}>\n      {digits.map((digit, idx) => {\n        const isFocused = focusedIndex === idx;\n        const isFilled = digit !== '';\n        return (\n          <div key={idx} className=\"relative\">\n            <input\n              ref={(el) => (inputRefs.current[idx] = el)}\n              type=\"text\"\n              id={`otp-input-${idx}`}\n              name={`otp-input-${idx}`}\n              aria-label={`Security Pin Digit ${idx + 1}`}\n              inputMode=\"numeric\"\n              maxLength={1}\n              value={digit}\n              onFocus={() => setFocusedIndex(idx)}\n              onChange={(e) => handleChange(idx, e.target.value)}\n              onKeyDown={(e) => handleKeyDown(idx, e)}\n              onPaste={handlePaste}\n              className={`w-10 h-12 sm:w-12 sm:h-14 text-center font-mono text-lg font-bold rounded-xl border bg-white dark:bg-zinc-900/60 text-zinc-900 dark:text-white transition-all outline-none ${\n                isFocused\n                  ? 'border-indigo-500 shadow-lg shadow-indigo-500/20 ring-2 ring-indigo-500/20'\n                  : isFilled\n                  ? 'border-emerald-500/50 dark:border-emerald-500/40'\n                  : 'border-zinc-300 dark:border-white/10 hover:border-zinc-400 dark:hover:border-white/20'\n              }`}\n            />\n            {isFocused && (\n              <motion.div\n                layoutId=\"otp-glow\"\n                className=\"absolute inset-0 rounded-xl pointer-events-none ring-2 ring-indigo-500/30\"\n                transition={{ type: 'spring', stiffness: 350, damping: 25 }}\n              />\n            )}\n          </div>\n        );\n      })}\n    </div>\n  );\n};\n\nexport const OtpInput = OTPInput;\n"
    }
  ]
}