{
  "title": "Input OTP",
  "description": "Accessible one-time password component with spring animations for character entry and active slot focus.",
  "name": "motion-input-otp",
  "dependencies": [
    "motion",
    "input-otp",
    "lucide-react"
  ],
  "registryDependencies": [],
  "type": "registry:ui",
  "files": [
    {
      "path": "components/paceui/motion/input-otp.tsx",
      "type": "registry:ui",
      "content": "import * as React from \"react\";\n\nimport { OTPInput, OTPInputContext } from \"input-otp\";\nimport { MinusIcon } from \"lucide-react\";\nimport { AnimatePresence, type Transition, motion, useReducedMotion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst ROLL_SPRING = { type: \"spring\", stiffness: 500, damping: 34 } as const;\nconst SHAKE_KEYFRAMES = [0, -5, 4, -2, 0];\n\nexport type OtpStatus = \"idle\" | \"success\" | \"error\";\nexport type OtpSize = \"sm\" | \"default\" | \"md\" | \"lg\";\n\nconst sizeClasses: Record<OtpSize, { slot: string; text: string; caret: string }> = {\n    sm: {\n        slot: \"size-8 text-xs\",\n        text: \"text-xs\",\n        caret: \"h-3.5\",\n    },\n    default: {\n        slot: \"size-9 text-sm\",\n        text: \"text-sm\",\n        caret: \"h-4\",\n    },\n    md: {\n        slot: \"size-10 text-base\",\n        text: \"text-base\",\n        caret: \"h-5\",\n    },\n    lg: {\n        slot: \"size-12 text-lg\",\n        text: \"text-lg\",\n        caret: \"h-6\",\n    },\n};\n\nconst AnimateContext = React.createContext<{\n    transition?: Transition;\n    status?: OtpStatus;\n    mask?: boolean;\n    size?: OtpSize;\n}>({});\n\nexport type InputOTPProps = Omit<React.ComponentProps<typeof OTPInput>, \"size\"> & {\n    containerClassName?: string;\n    transition?: Transition;\n    status?: OtpStatus;\n    mask?: boolean;\n    size?: OtpSize;\n};\n\nfunction InputOTP({\n    className,\n    containerClassName,\n    transition,\n    status = \"idle\",\n    mask = false,\n    size = \"default\",\n    ...props\n}: InputOTPProps) {\n    const shouldReduceMotion = useReducedMotion();\n\n    const content = (\n        <OTPInput\n            data-slot=\"input-otp\"\n            data-status={status}\n            containerClassName={cn(\"cn-input-otp flex items-center has-disabled:opacity-50\", containerClassName)}\n            spellCheck={false}\n            className={cn(\"disabled:cursor-not-allowed outline-none\", className)}\n            {...(props as any)}\n        />\n    );\n\n    return (\n        <AnimateContext.Provider\n            value={{\n                transition,\n                status,\n                mask,\n                size,\n            }}>\n            <motion.div\n                animate={{\n                    x: status === \"error\" && !shouldReduceMotion ? SHAKE_KEYFRAMES : 0,\n                }}\n                transition={{ duration: 0.32, ease: \"easeOut\" }}\n                className=\"inline-flex items-center\">\n                {content}\n            </motion.div>\n        </AnimateContext.Provider>\n    );\n}\n\nexport interface InputOTPGroupProps extends React.ComponentProps<\"div\"> {}\n\nfunction InputOTPGroup({ className, ...props }: InputOTPGroupProps) {\n    return (\n        <div\n            data-slot=\"input-otp-group\"\n            className={cn(\"has-aria-invalid:border-destructive flex items-center rounded-md\", className)}\n            {...props}\n        />\n    );\n}\n\nexport interface InputOTPSlotProps extends React.ComponentProps<\"div\"> {\n    index: number;\n    transition?: Transition;\n    mask?: boolean;\n    status?: OtpStatus;\n    size?: OtpSize;\n}\n\nfunction InputOTPSlot({\n    index,\n    className,\n    transition: transitionProp,\n    mask: maskProp,\n    status: statusProp,\n    size: sizeProp,\n    ...props\n}: InputOTPSlotProps) {\n    const inputOTPContext = React.useContext(OTPInputContext);\n    const context = React.useContext(AnimateContext);\n    const transition = transitionProp ?? context.transition ?? ROLL_SPRING;\n    const mask = maskProp ?? context.mask ?? false;\n    const status = statusProp ?? context.status ?? \"idle\";\n    const currentSize = sizeProp ?? context.size ?? \"default\";\n    const scale = sizeClasses[currentSize] ?? sizeClasses.default;\n    const shouldReduceMotion = useReducedMotion();\n\n    const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};\n\n    const prevCharRef = React.useRef(char);\n    const isCleared = !char && Boolean(prevCharRef.current);\n\n    React.useEffect(() => {\n        prevCharRef.current = char;\n    }, [char]);\n\n    return (\n        <div\n            data-slot=\"input-otp-slot\"\n            data-active={isActive}\n            data-status={status}\n            className={cn(\n                \"border-input aria-invalid:border-destructive data-[active=true]:border-ring data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 relative flex items-center justify-center overflow-hidden border -ml-px first:ml-0 transition-colors outline-none first:rounded-l-md last:rounded-r-md data-[active=true]:z-10\",\n                scale.slot,\n                status === \"error\" && \"border-destructive text-destructive bg-destructive/5 dark:bg-destructive/10\",\n                status === \"success\" && \"border-emerald-500/80 text-emerald-600 dark:border-emerald-400/80 dark:text-emerald-400 bg-emerald-500/5 dark:bg-emerald-500/10 z-10\",\n                className,\n            )}\n            {...(props as any)}>\n            <span className=\"pointer-events-none absolute inset-0 grid place-items-center overflow-hidden\">\n                <AnimatePresence initial={false} custom={isCleared}>\n                    {char ? (\n                        <motion.span\n                            key={char}\n                            custom={isCleared}\n                            initial={shouldReduceMotion ? false : { y: \"110%\" }}\n                            animate={\n                                status === \"success\" && !shouldReduceMotion\n                                    ? { y: [0, -3, 0], scale: [1, 1.15, 1] }\n                                    : { y: 0, scale: 1 }\n                            }\n                            exit={shouldReduceMotion ? { opacity: 0 } : isCleared ? { y: \"110%\" } : { y: \"-110%\" }}\n                            transition={\n                                status === \"success\" && !shouldReduceMotion\n                                    ? { duration: 0.35, ease: \"easeOut\", delay: index * 0.045 }\n                                    : shouldReduceMotion\n                                      ? { duration: 0 }\n                                      : transition\n                            }\n                            data-slot=\"input-otp-char\"\n                            className={cn(\n                                \"font-medium select-none transition-colors\",\n                                status === \"idle\" && \"text-foreground\",\n                                scale.text,\n                            )}>\n                            {mask ? \"•\" : char}\n                        </motion.span>\n                    ) : null}\n                </AnimatePresence>\n            </span>\n\n            {hasFakeCaret && (\n                <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n                    <div className={cn(\"animate-caret-blink bg-foreground w-px duration-1000\", scale.caret)} />\n                </div>\n            )}\n        </div>\n    );\n}\n\nexport interface InputOTPSeparatorProps extends React.ComponentProps<\"div\"> {}\n\nfunction InputOTPSeparator({ className, ...props }: InputOTPSeparatorProps) {\n    return (\n        <div\n            data-slot=\"input-otp-separator\"\n            className={cn(\"flex items-center [&_svg:not([class*='size-'])]:size-4\", className)}\n            role=\"separator\"\n            {...props}>\n            <MinusIcon />\n        </div>\n    );\n}\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };\n"
    }
  ],
  "mcp": {
    "description": "Accessible one-time password component. Provides spring character entry pop and active slot micro-scale focus physics.",
    "tags": [
      "component",
      "input-otp",
      "otp",
      "password",
      "paceui",
      "animation"
    ]
  }
}