{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inputs-otp",
  "title": "OTP inputs",
  "description": "Styled inputs: the OTP set. 5 decorative inputs variations (PinInput, SegmentInput, CodeInput, MaskedInput, StepperInput) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/inputs-otp.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Minus, Plus } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* OTP and specialised input family: 5 segmented and formatted entries. Colour comes ONLY from tokens. The segment groups move focus with an internal state array plus refs; every box is a real <input> and carries an aria-label. The single-input variants pass native props straight through. No animation, motion/react is not needed. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst height: Record<StyledSize, string> = {\n  sm: \"h-8 text-sm\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-base\",\n  xl: \"h-12 text-base\",\n}\n\nconst fieldBase =\n  \"w-full bg-transparent text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\"\n\ntype Props = Omit<React.ComponentProps<\"input\">, \"size\"> & { size?: StyledSize }\n\n/* Segmentli kutu grubu: kontrollu ic dizi + ref ile ileri/geri odak. */\nfunction useOtp(length: number) {\n  const [values, setValues] = React.useState<string[]>(() => Array(length).fill(\"\"))\n  const refs = React.useRef<Array<HTMLInputElement | null>>([])\n  const setAt = (i: number, raw: string) => {\n    const ch = raw.slice(-1)\n    setValues((prev) => {\n      const next = [...prev]\n      next[i] = ch\n      return next\n    })\n    if (ch && i < length - 1) refs.current[i + 1]?.focus()\n  }\n  const onKey = (i: number, e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === \"Backspace\" && !values[i] && i > 0) {\n      refs.current[i - 1]?.focus()\n    } else if (e.key === \"ArrowLeft\" && i > 0) {\n      refs.current[i - 1]?.focus()\n    } else if (e.key === \"ArrowRight\" && i < length - 1) {\n      refs.current[i + 1]?.focus()\n    }\n  }\n  return { values, refs, setAt, onKey }\n}\n\nfunction Segmented({\n  length,\n  className,\n  size = \"md\",\n  look = \"box\",\n  ...props\n}: Props & { length: number; look?: \"box\" | \"underline\" }) {\n  const { values, refs, setAt, onKey } = useOtp(length)\n  const box =\n    look === \"underline\"\n      ? \"rounded-none border-0 border-b-2 border-field-border focus:border-primary\"\n      : \"rounded-lg border border-field-border focus:border-ring focus:ring-[3px] focus:ring-ring/50\"\n  return (\n    <span data-slot=\"styled-input\" className={cn(\"inline-flex items-center gap-2\", className)}>\n      {Array.from({ length }).map((_, i) => (\n        <input\n          key={i}\n          ref={(el) => {\n            refs.current[i] = el\n          }}\n          {...(i === 0 ? props : {})}\n          value={values[i]}\n          onChange={(e) => setAt(i, e.target.value)}\n          onKeyDown={(e) => onKey(i, e)}\n          inputMode=\"numeric\"\n          maxLength={1}\n          aria-label={`Digit ${i + 1}`}\n          className={cn(\n            fieldBase,\n            box,\n            height[size],\n            \"aspect-square text-center font-medium tabular-nums transition-colors\"\n          )}\n        />\n      ))}\n    </span>\n  )\n}\n\n/* Pin: 4 ayri tek-karakterli kutu. */\nexport function PinInput(props: Props) {\n  return <Segmented length={4} {...props} />\n}\n\n/* Segment: N kutulu (varsayilan 5), token alt-cizgili segment gorunumu. */\nexport function SegmentInput({ length = 5, ...props }: Props & { length?: number }) {\n  return <Segmented length={length} look=\"underline\" {...props} />\n}\n\n/* Code: 6 kutulu dogrulama kodu girisi. */\nexport function CodeInput(props: Props) {\n  return <Segmented length={6} {...props} />\n}\n\n/* Masked: sabit token onek (varsayilan \"@\") + tek gercek input. */\nexport function MaskedInput({\n  className,\n  size = \"md\",\n  prefix = \"@\",\n  ...props\n}: Props & { prefix?: string }) {\n  return (\n    <span\n      data-slot=\"styled-input\"\n      className={cn(\n        \"relative inline-flex w-56 max-w-full items-center gap-1 rounded-lg border border-field-border px-3 transition-colors focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\",\n        height[size],\n        className\n      )}\n    >\n      <span aria-hidden=\"true\" className=\"shrink-0 select-none font-medium text-muted-foreground\">\n        {prefix}\n      </span>\n      <input {...props} className={cn(fieldBase, \"h-full\")} />\n    </span>\n  )\n}\n\n/* Stepper: token +/- butonlu sayi girisi. */\nexport function StepperInput({\n  className,\n  size = \"md\",\n  min,\n  max,\n  step = 1,\n  ...props\n}: Props) {\n  const [value, setValue] = React.useState<string>(\n    props.defaultValue != null ? String(props.defaultValue) : props.value != null ? String(props.value) : \"\"\n  )\n  const current = props.value != null ? String(props.value) : value\n  const stepNum = typeof step === \"number\" ? step : Number(step) || 1\n  const minNum = min != null ? Number(min) : undefined\n  const maxNum = max != null ? Number(max) : undefined\n\n  const nudge = (dir: 1 | -1) => {\n    const base = current === \"\" ? 0 : Number(current)\n    let next = base + dir * stepNum\n    if (minNum != null) next = Math.max(minNum, next)\n    if (maxNum != null) next = Math.min(maxNum, next)\n    setValue(String(next))\n  }\n\n  const btn =\n    \"inline-flex h-full aspect-square shrink-0 items-center justify-center text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50 [&_svg]:size-4\"\n\n  return (\n    <span\n      data-slot=\"styled-input\"\n      className={cn(\n        \"relative inline-flex w-40 max-w-full items-center overflow-hidden rounded-lg border border-field-border transition-colors focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50\",\n        height[size],\n        className\n      )}\n    >\n      <button type=\"button\" aria-label=\"Decrease\" onClick={() => nudge(-1)} className={cn(btn, \"border-r border-field-border\")}>\n        <Minus />\n      </button>\n      <input\n        type=\"number\"\n        inputMode=\"numeric\"\n        min={min}\n        max={max}\n        step={step}\n        {...props}\n        value={current}\n        onChange={(e) => {\n          setValue(e.target.value)\n          props.onChange?.(e)\n        }}\n        className={cn(\n          fieldBase,\n          \"h-full min-w-0 flex-1 text-center tabular-nums [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n        )}\n      />\n      <button type=\"button\" aria-label=\"Increase\" onClick={() => nudge(1)} className={cn(btn, \"border-l border-field-border\")}>\n        <Plus />\n      </button>\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/inputs-otp.tsx"
    }
  ],
  "categories": [
    "styled",
    "inputs"
  ],
  "type": "registry:component"
}