{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "input-otp", "type": "registry:ui", "title": "Input OTP", "description": "Segmented one-time-code input over a hidden TextInput.", "registryDependencies": [ "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/utils.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/text.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json" ], "files": [ { "path": "registry/shared/ui/input-otp.tsx", "type": "registry:ui", "target": "components/ui/input-otp.tsx", "content": "import { useContext, useRef, useState } from 'react';\nimport {\n Platform,\n Pressable,\n StyleSheet,\n TextInput,\n View,\n type TextInputProps,\n type ViewProps,\n} from 'react-native';\nimport { Text } from '@/components/ui/text';\nimport { cn, FormFieldContext, useThemeColor } from '@/lib/utils';\n\nexport interface InputOTPProps extends ViewProps {\n length?: number;\n value?: string;\n onChange?: (value: string) => void;\n /** Fired once all slots are filled. */\n onComplete?: (value: string) => void;\n secure?: boolean;\n /** Mirrors shadcn `aria-invalid` — destructive slot borders. Auto-detected from FormField error. */\n invalid?: boolean;\n /** Prevents input focus and dims slots. */\n disabled?: boolean;\n autoFocus?: boolean;\n keyboardType?: TextInputProps['keyboardType'];\n className?: string;\n slotClassName?: string;\n}\n\nexport function InputOTP({\n length = 6,\n value = '',\n onChange,\n onComplete,\n secure = false,\n invalid,\n disabled = false,\n autoFocus = false,\n keyboardType = 'number-pad',\n className,\n slotClassName,\n ...props\n}: InputOTPProps) {\n const inputRef = useRef(null);\n const [focused, setFocused] = useState(false);\n const field = useContext(FormFieldContext);\n const colors = useThemeColor();\n const isInvalid = invalid ?? !!field?.error;\n\n const handleChange = (text: string) => {\n const next = text.replace(/[^0-9a-zA-Z]/g, '').slice(0, length);\n onChange?.(next);\n if (next.length === length) {\n onComplete?.(next);\n }\n };\n\n return (\n inputRef.current?.focus()}\n accessibilityRole=\"none\"\n accessibilityState={{ disabled }}\n className={cn('relative', disabled && 'opacity-50')}\n >\n \n {Array.from({ length }).map((_, i) => {\n const char = value[i] ?? '';\n const isActive =\n focused && !disabled && i === Math.min(value.length, length - 1);\n return (\n \n \n {secure && char ? '•' : char}\n \n \n );\n })}\n\n {/* Hidden full-area input wired for iOS & Android SMS Auto-Fill */}\n setFocused(true)}\n onBlur={() => setFocused(false)}\n keyboardType={keyboardType}\n autoComplete={Platform.select({\n android: 'sms-otp',\n default: 'one-time-code',\n })}\n textContentType=\"oneTimeCode\"\n accessibilityLabel=\"One-time verification code\"\n style={styles.hiddenInput}\n caretHidden\n />\n \n \n );\n}\n\nconst styles = StyleSheet.create({\n hiddenInput: {\n ...StyleSheet.absoluteFillObject,\n opacity: 0,\n },\n});\n" } ] }