{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-otp",
  "title": "Input OTP",
  "description": "One-time-code field with paste and autofill.",
  "registryDependencies": [
    "https://components.exe.xyz/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/ui/input-otp.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction digitsOnly(value: string, length: number) {\n  return value.replace(/\\D/g, \"\").slice(0, length)\n}\n\nfunction InputOtp({\n  value,\n  defaultValue = \"\",\n  onChange,\n  onComplete,\n  length = 6,\n  groupEvery,\n  isInvalid = false,\n  isDisabled = false,\n  className,\n  \"aria-label\": ariaLabel,\n  ...props\n}: Omit<React.ComponentProps<\"div\">, \"onChange\" | \"defaultValue\"> & {\n  value?: string\n  defaultValue?: string\n  onChange?: (value: string) => void\n  onComplete?: (value: string) => void\n  length?: number\n  groupEvery?: number\n  isInvalid?: boolean\n  isDisabled?: boolean\n  \"aria-label\": string\n}) {\n  const [uncontrolled, setUncontrolled] = React.useState(() =>\n    digitsOnly(defaultValue, length)\n  )\n  const current = digitsOnly(value ?? uncontrolled, length)\n  const inputRef = React.useRef<HTMLInputElement>(null)\n  const [focused, setFocused] = React.useState(false)\n  const [caret, setCaret] = React.useState(current.length)\n\n  const emit = React.useCallback(\n    (next: string) => {\n      const sanitized = digitsOnly(next, length)\n      if (value === undefined) setUncontrolled(sanitized)\n      onChange?.(sanitized)\n      if (sanitized.length === length) onComplete?.(sanitized)\n    },\n    [length, onChange, onComplete, value]\n  )\n\n  const syncCaret = () => {\n    const el = inputRef.current\n    if (!el) return\n    setCaret(el.selectionStart ?? current.length)\n  }\n\n  const activeIndex = focused\n    ? Math.min(Math.max(caret, 0), Math.max(length - 1, 0))\n    : Math.min(current.length, length - 1)\n\n  return (\n    <div\n      data-slot=\"input-otp\"\n      data-invalid={isInvalid || undefined}\n      data-disabled={isDisabled || undefined}\n      className={cn(\"relative inline-flex\", className)}\n      {...props}\n    >\n      <input\n        ref={inputRef}\n        value={current}\n        disabled={isDisabled}\n        inputMode=\"numeric\"\n        autoComplete=\"one-time-code\"\n        autoCapitalize=\"none\"\n        autoCorrect=\"off\"\n        spellCheck={false}\n        pattern=\"[0-9]*\"\n        maxLength={length}\n        aria-label={ariaLabel}\n        aria-invalid={isInvalid || undefined}\n        aria-disabled={isDisabled || undefined}\n        onChange={(event) => emit(event.target.value)}\n        onFocus={() => {\n          setFocused(true)\n          syncCaret()\n        }}\n        onBlur={() => setFocused(false)}\n        onClick={syncCaret}\n        onKeyUp={syncCaret}\n        onSelect={syncCaret}\n        className=\"absolute inset-0 z-10 cursor-text opacity-0 disabled:cursor-not-allowed\"\n      />\n      <div\n        aria-hidden\n        className=\"flex items-center gap-1.5\"\n        data-slot=\"input-otp-group\"\n      >\n        {Array.from({ length }, (_, index) => {\n          const gap = Boolean(\n            groupEvery && index > 0 && index % groupEvery === 0\n          )\n          const filled = current[index] ?? \"\"\n          const isActive = focused && !isDisabled && index === activeIndex\n\n          return (\n            <React.Fragment key={index}>\n              {gap ? <span className=\"w-2 shrink-0\" /> : null}\n              <div\n                data-slot=\"input-otp-slot\"\n                data-active={isActive || undefined}\n                className={cn(\n                  \"relative flex size-9 items-center justify-center rounded-[2px] border border-input bg-card font-mono text-sm\",\n                  isActive && \"border-ring ring-3 ring-ring/50\",\n                  isInvalid && \"border-destructive ring-3 ring-destructive/20\",\n                  isDisabled && \"bg-input/50 opacity-50\"\n                )}\n              >\n                {filled ? (\n                  filled\n                ) : isActive ? (\n                  <span className=\"h-4 w-px bg-foreground motion-safe:animate-pulse\" />\n                ) : null}\n              </div>\n            </React.Fragment>\n          )\n        })}\n      </div>\n    </div>\n  )\n}\n\nexport { InputOtp }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}