'use client'; import { useRef } from 'react'; import { cn } from '@/lib/utils'; const otpThemes = { dark: { box: 'border-[#555762] bg-transparent text-white focus:border-[#7b7f8f] focus:ring-white/10' }, light: { box: 'border-[#eceef4] bg-white text-[#111827] focus:border-[#c7cad6] focus:ring-[#eceef4]' } } as const; interface OtpInputProps { value: string; onChange: (value: string) => void; onComplete?: (value: string) => void; length?: number; disabled?: boolean; variant?: keyof typeof otpThemes; className?: string; } export function OtpInput({ value, onChange, onComplete, length = 6, disabled = false, variant = 'dark', className }: OtpInputProps) { const theme = otpThemes[variant]; const inputsRef = useRef>([]); const digits = Array.from({ length }, (_, index) => value[index] ?? ''); function commit(next: string) { const clean = next.replace(/\D/g, '').slice(0, length); onChange(clean); if (clean.length === length) { onComplete?.(clean); } } function handleChange(index: number, raw: string) { const char = raw.replace(/\D/g, '').slice(-1); if (!char) return; const chars = value.split(''); chars[index] = char; const next = chars.join('').slice(0, length); commit(next); if (index < length - 1) { inputsRef.current[index + 1]?.focus(); } } function handleKeyDown(index: number, event: React.KeyboardEvent) { if (event.key === 'Backspace') { event.preventDefault(); const chars = value.split(''); if (chars[index]) { chars[index] = ''; onChange(chars.join('')); } else if (index > 0) { chars[index - 1] = ''; onChange(chars.join('')); inputsRef.current[index - 1]?.focus(); } } if (event.key === 'ArrowLeft' && index > 0) { inputsRef.current[index - 1]?.focus(); } if (event.key === 'ArrowRight' && index < length - 1) { inputsRef.current[index + 1]?.focus(); } } function handlePaste(event: React.ClipboardEvent) { event.preventDefault(); const pasted = event.clipboardData.getData('text').replace(/\D/g, '').slice(0, length); if (!pasted) return; commit(pasted); const focusIndex = Math.min(pasted.length, length - 1); inputsRef.current[focusIndex]?.focus(); } return (
{digits.map((digit, index) => ( { inputsRef.current[index] = element; }} type="text" inputMode="numeric" autoComplete="one-time-code" maxLength={1} disabled={disabled} value={digit} onChange={(event) => handleChange(index, event.target.value)} onKeyDown={(event) => handleKeyDown(index, event)} onPaste={handlePaste} className={cn( 'h-14 w-full rounded-2xl border text-center text-2xl font-semibold outline-none transition focus:ring-4 disabled:opacity-50', theme.box )} /> ))}
); }