{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pin-input",
  "title": "Pin Input",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/pin-input/pin-input.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { OTPInput, OTPInputContext } from 'input-otp'\nimport { Minus } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\ntype PinInputStatus = 'error' | 'warning' | 'success' | 'default'\ntype PinInputSize = 'sm' | 'md' | 'lg'\n\ninterface PinInputContextValue {\n  mask: boolean\n  status: PinInputStatus\n  size: PinInputSize\n}\n\nconst PinInputUiContext = React.createContext<PinInputContextValue>({\n  mask: false,\n  status: 'default',\n  size: 'md',\n})\n\nexport interface PinInputProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof OTPInput>, 'render' | 'children' | 'maxLength' | 'size'> {\n  /** Number of slots. Maps to input-otp's `maxLength`. */\n  maxLength?: number\n  /** Render the typed characters as dots instead of plain text. */\n  mask?: boolean\n  status?: PinInputStatus\n  size?: PinInputSize\n  /** Fires with the joined string once every slot is filled. */\n  onComplete?: (value: string) => void\n  children?: React.ReactNode\n  className?: string\n  containerClassName?: string\n}\n\nconst PinInput = React.forwardRef<React.ElementRef<typeof OTPInput>, PinInputProps>(\n  (\n    {\n      className,\n      containerClassName,\n      maxLength = 6,\n      mask = false,\n      status = 'default',\n      size = 'md',\n      onComplete,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    return (\n      <PinInputUiContext.Provider value={{ mask, status, size }}>\n        <OTPInput\n          ref={ref}\n          data-uipkge=\"\"\n          data-slot=\"pin-input\"\n          maxLength={maxLength}\n          onComplete={onComplete}\n          containerClassName={cn(\n            'flex items-center gap-2 has-disabled:opacity-50',\n            containerClassName,\n          )}\n          className={cn('disabled:cursor-not-allowed', className)}\n          {...props}\n        >\n          {children}\n        </OTPInput>\n      </PinInputUiContext.Provider>\n    )\n  },\n)\nPinInput.displayName = 'PinInput'\n\nconst PinInputGroup = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      data-uipkge=\"\"\n      data-slot=\"pin-input-group\"\n      className={cn('flex items-center', className)}\n      {...props}\n    />\n  ),\n)\nPinInputGroup.displayName = 'PinInputGroup'\n\nconst sizeClassMap: Record<PinInputSize, string> = {\n  sm: 'h-8 w-8 text-sm',\n  lg: 'h-12 w-12 text-xl',\n  md: 'h-10 w-10 text-base',\n}\n\nconst statusClassMap: Record<PinInputStatus, string> = {\n  error: 'border-destructive focus-within:border-destructive focus-within:ring-destructive/40 text-destructive',\n  warning: 'border-warning focus-within:border-warning focus-within:ring-warning/40 text-warning',\n  success: 'border-success focus-within:border-success focus-within:ring-success/40 text-success',\n  default: '',\n}\n\nexport interface PinInputSlotProps extends React.ComponentPropsWithoutRef<'div'> {\n  index: number\n  /** Override the inherited mask flag for this slot. */\n  mask?: boolean\n}\n\nconst PinInputSlot = React.forwardRef<HTMLDivElement, PinInputSlotProps>(\n  ({ index, mask: maskProp, className, ...props }, ref) => {\n    const ui = React.useContext(PinInputUiContext)\n    const inputContext = React.useContext(OTPInputContext)\n    const slot = inputContext.slots[index]\n    const char = slot?.char ?? null\n    const isActive = slot?.isActive ?? false\n    const effectiveMask = maskProp ?? ui.mask\n\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"pin-input-slot\"\n        data-active={isActive ? '' : undefined}\n        className={cn(\n          'border-input bg-background text-foreground relative -ml-px flex items-center justify-center border text-center shadow-xs transition-[border-color,box-shadow] outline-none first:ml-0 first:rounded-l-md last:rounded-r-md',\n          'focus-within:border-ring focus-within:ring-ring/40 focus-within:relative focus-within:z-10 focus-within:ring-2',\n          'disabled:cursor-not-allowed disabled:opacity-50',\n          isActive && 'border-ring ring-ring/40 z-10 ring-2',\n          sizeClassMap[ui.size],\n          statusClassMap[ui.status],\n          className,\n        )}\n        {...props}\n      >\n        {char != null && (effectiveMask ? <span className=\"bg-foreground size-2 rounded-full\" /> : char)}\n      </div>\n    )\n  },\n)\nPinInputSlot.displayName = 'PinInputSlot'\n\nconst PinInputSeparator = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(\n  ({ children, ...props }, ref) => (\n    <div ref={ref} data-uipkge=\"\" data-slot=\"pin-input-separator\" role=\"separator\" {...props}>\n      {children ?? <Minus />}\n    </div>\n  ),\n)\nPinInputSeparator.displayName = 'PinInputSeparator'\n\nexport { PinInput, PinInputGroup, PinInputSlot, PinInputSeparator }\n",
      "type": "registry:ui",
      "target": "~/components/ui/pin-input/pin-input.tsx"
    },
    {
      "path": "packages/registry-react/components/pin-input/index.ts",
      "content": "export {\n  PinInput,\n  PinInputGroup,\n  PinInputSlot,\n  PinInputSeparator,\n  type PinInputProps,\n  type PinInputSlotProps,\n} from './pin-input'\n",
      "type": "registry:ui",
      "target": "~/components/ui/pin-input/index.ts"
    }
  ],
  "dependencies": [
    "input-otp",
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "One-time-code input — N separate boxes that auto-advance and accept paste. Use for SMS verification, 2FA, and short numeric codes. Length, masking, and per-slot status all configurable.",
  "categories": [
    "form"
  ]
}