{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"code-input","type":"registry:ui","title":"Code Input","description":"One box per digit for verification codes, with a blinking caret in the next box. Pasting a code and autofill from SMS both work.","dependencies":["motion@^14.0.0"],"files":[{"path":"components/ui/code-input.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { fieldSurface } from \"@/lib/field\";\nimport { cn } from \"@/lib/utils\";\n\ntype CodeInputProps = Omit<React.ComponentProps<\"input\">, \"value\" | \"onChange\" | \"maxLength\"> & {\n  value: string;\n  onChange: (value: string) => void;\n  onComplete?: (value: string) => void;\n  length?: number;\n};\n\nfunction CodeInput({ value, onChange, onComplete, length = 6, className, ...props }: CodeInputProps) {\n  const [focused, setFocused] = React.useState(false);\n  const active = Math.min(value.length, length - 1);\n\n  return (\n    <div data-slot=\"code-input\" className={cn(\"relative flex w-fit gap-2\", className)}>\n      {Array.from({ length }, (_, i) => {\n        const current = focused && i === active;\n        return (\n          <div\n            key={i}\n            aria-hidden\n            className={cn(\n              fieldSurface,\n              \"relative flex h-12 min-w-11 flex-1 items-center justify-center text-lg font-semibold tabular-nums\",\n              current && \"field-halo\",\n              props[\"aria-invalid\"] && \"border-destructive\",\n            )}\n          >\n            {value[i] ?? \"\"}\n            {current && value.length < length && (\n              <motion.span\n                className=\"bg-foreground absolute h-5 w-px\"\n                animate={{ opacity: [1, 1, 0, 0] }}\n                transition={{ duration: 1, repeat: Infinity, times: [0, 0.5, 0.5, 1] }}\n              />\n            )}\n          </div>\n        );\n      })}\n      <input\n        {...props}\n        data-slot=\"code-input-field\"\n        inputMode=\"numeric\"\n        autoComplete=\"one-time-code\"\n        pattern=\"\\d*\"\n        maxLength={length}\n        value={value}\n        onFocus={(event) => {\n          setFocused(true);\n          event.currentTarget.setSelectionRange(value.length, value.length);\n          props.onFocus?.(event);\n        }}\n        onBlur={(event) => {\n          setFocused(false);\n          props.onBlur?.(event);\n        }}\n        onChange={(event) => {\n          const next = event.target.value.replace(/\\D/g, \"\").slice(0, length);\n          onChange(next);\n          if (next.length === length && value.length !== length) onComplete?.(next);\n        }}\n        className=\"absolute inset-0 cursor-text text-transparent caret-transparent opacity-0 outline-none selection:bg-transparent\"\n      />\n    </div>\n  );\n}\n\nexport { CodeInput };\n"},{"path":"lib/field.ts","type":"registry:lib","content":"export const fieldSurface =\n  \"bg-background border-input dark:bg-input/30 rounded-lg border shadow-xs transition-[border-color,box-shadow] outline-none hover:border-foreground/25 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive\";\n\nexport function isFieldInvalid(meta: { isTouched: boolean; isValid: boolean }) {\n  return meta.isTouched && !meta.isValid;\n}\n"}],"css":{"@utility field-halo":{"border-color":"color-mix(in oklab, var(--foreground) 40%, transparent)","box-shadow":"0 0 0 3px color-mix(in oklab, var(--foreground) 9%, transparent)"}}}