{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "otp-input",
  "title": "OTP input",
  "description": "N-digit code, paste-aware",
  "dependencies": [],
  "registryDependencies": [
    "@kit-native/lib-utils",
    "@kit-native/text"
  ],
  "files": [
    {
      "path": "components/kit/otp-input.tsx",
      "content": "import { Text } from '@/components/ui/text';\nimport { cn } from '@/lib/utils';\nimport * as React from 'react';\nimport { type NativeSyntheticEvent, TextInput, type TextInputKeyPressEventData, View } from 'react-native';\n\ntype Props = {\n  length?: number;\n  value: string;\n  onChange: (next: string) => void;\n  onComplete?: (code: string) => void;\n  error?: string;\n  autoFocus?: boolean;\n  className?: string;\n};\n\n/** N-digit one-time-code entry: auto-advances, handles paste, backspaces into the previous cell. */\nexport function OtpInput({ length = 6, value, onChange, onComplete, error, autoFocus = true, className }: Props) {\n  const refs = React.useRef<(TextInput | null)[]>([]);\n  const [focusIdx, setFocusIdx] = React.useState(autoFocus ? 0 : -1);\n\n  const digits = React.useMemo(() => {\n    const clean = (value ?? '').replace(/\\D/g, '').slice(0, length);\n    return Array.from({ length }, (_, i) => clean[i] ?? '');\n  }, [value, length]);\n\n  React.useEffect(() => {\n    if (!autoFocus) return;\n    const t = setTimeout(() => refs.current[0]?.focus(), 0);\n    return () => clearTimeout(t);\n  }, [autoFocus]);\n\n  const commit = (next: string[]) => {\n    const joined = next.join('');\n    onChange(joined);\n    if (joined.replace(/\\D/g, '').length === length) onComplete?.(joined);\n  };\n\n  const setAt = (idx: number, ch: string) => {\n    const clean = ch.replace(/\\D/g, '');\n    const next = [...digits];\n    if (clean.length > 1) {\n      for (let i = 0; i < clean.length && idx + i < length; i++) next[idx + i] = clean[i];\n      commit(next);\n      refs.current[Math.min(idx + clean.length, length - 1)]?.focus();\n      return;\n    }\n    next[idx] = clean;\n    commit(next);\n    if (clean && idx < length - 1) refs.current[idx + 1]?.focus();\n  };\n\n  const onKeyPress = (idx: number) => (e: NativeSyntheticEvent<TextInputKeyPressEventData>) => {\n    if (e.nativeEvent.key === 'Backspace' && !digits[idx] && idx > 0) {\n      const next = [...digits];\n      next[idx - 1] = '';\n      onChange(next.join(''));\n      refs.current[idx - 1]?.focus();\n    }\n  };\n\n  return (\n    <View className={className}>\n      <View className=\"flex-row gap-2\">\n        {digits.map((d, idx) => (\n          <TextInput\n            key={idx}\n            ref={(r) => {\n              refs.current[idx] = r;\n            }}\n            value={d}\n            onChangeText={(ch) => setAt(idx, ch)}\n            onKeyPress={onKeyPress(idx)}\n            onFocus={() => setFocusIdx(idx)}\n            onBlur={() => setFocusIdx((c) => (c === idx ? -1 : c))}\n            keyboardType=\"number-pad\"\n            maxLength={idx === 0 ? length : 1}\n            selectTextOnFocus\n            textContentType=\"oneTimeCode\"\n            autoComplete=\"one-time-code\"\n            accessibilityLabel={`Digit ${idx + 1} of ${length}`}\n            // textAlignVertical + no font padding: without these the digit rides high\n            // in a fixed-height TextInput on Android.\n            textAlignVertical=\"center\"\n            style={{ includeFontPadding: false }}\n            className={cn(\n              'bg-card text-foreground h-14 flex-1 rounded-lg border p-0 text-center text-2xl font-bold',\n              error ? 'border-destructive' : focusIdx === idx ? 'border-primary' : 'border-border'\n            )}\n          />\n        ))}\n      </View>\n      {error ? <Text className=\"text-destructive mt-2 text-center text-sm\">{error}</Text> : null}\n    </View>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/kit/otp-input.tsx"
    }
  ],
  "meta": {
    "exports": [
      "OtpInput"
    ],
    "api": "<OtpInput length value onChange onComplete error? />"
  },
  "docs": "Run `npx expo install --fix` afterwards so Expo SDK packages match your SDK. Needs the kit theme (`@kit-native/theme`) and `KitThemeProvider` from `@kit-native/lib-theme-context` at the root.",
  "categories": [
    "inputs"
  ],
  "type": "registry:component"
}