'use client'; import React, { useRef, useEffect } from 'react'; interface SegmentedOtpInputProps { value: string; onChange: (value: string) => void; length?: number; disabled?: boolean; autoFocus?: boolean; className?: string; } export function SegmentedOtpInput({ value, onChange, length = 6, disabled = false, autoFocus = true, className = '', }: SegmentedOtpInputProps) { const inputsRef = useRef<(HTMLInputElement | null)[]>([]); // Split value into characters array padded to length const digits = Array.from({ length }, (_, i) => value[i] ?? ''); useEffect(() => { if (autoFocus && inputsRef.current[0]) { inputsRef.current[0]?.focus(); } }, [autoFocus]); const updateDigit = (index: number, char: string) => { const chars = [...digits]; chars[index] = char; // Build new value const nextVal = chars.join('').trimEnd(); onChange(nextVal); }; const handleKeyDown = (index: number, e: React.KeyboardEvent) => { if (e.key === 'Backspace') { e.preventDefault(); if (digits[index]) { // Clear current cell updateDigit(index, ''); } else if (index > 0) { // Move to previous and clear updateDigit(index - 1, ''); inputsRef.current[index - 1]?.focus(); } } else if (e.key === 'ArrowLeft' && index > 0) { e.preventDefault(); inputsRef.current[index - 1]?.focus(); } else if (e.key === 'ArrowRight' && index < length - 1) { e.preventDefault(); inputsRef.current[index + 1]?.focus(); } }; const handleChange = (index: number, e: React.ChangeEvent) => { const inputVal = e.target.value; if (!inputVal) { updateDigit(index, ''); return; } // Take the last character typed (in case multiple chars were entered) const char = inputVal.slice(-1); updateDigit(index, char); // Auto advance to next input if available if (index < length - 1) { inputsRef.current[index + 1]?.focus(); inputsRef.current[index + 1]?.select(); } }; const handlePaste = (e: React.ClipboardEvent) => { e.preventDefault(); const pasted = e.clipboardData.getData('text').replace(/\s|-/g, ''); if (!pasted) return; const trimmed = pasted.slice(0, length); onChange(trimmed); // Focus either the cell after the pasted length or the last cell const targetIdx = Math.min(trimmed.length, length - 1); inputsRef.current[targetIdx]?.focus(); }; return (
{Array.from({ length }).map((_, index) => { const isFilled = Boolean(digits[index]); return ( { inputsRef.current[index] = el; }} type="text" inputMode="numeric" autoComplete={index === 0 ? 'one-time-code' : 'off'} maxLength={1} disabled={disabled} value={digits[index]} aria-label={`Digit ${index + 1}`} data-testid={`otp-input-${index}`} onChange={(e) => handleChange(index, e)} onKeyDown={(e) => handleKeyDown(index, e)} onFocus={(e) => e.target.select()} className={`w-11 h-13 sm:w-13 sm:h-16 text-center text-xl sm:text-2xl font-mono font-black tracking-widest rounded-xl transition-all duration-150 outline-none ${ isFilled ? 'border-2 border-[#00f2fe] text-white bg-[#0e1726] shadow-[0_0_12px_rgba(0,242,254,0.35)]' : 'border border-[#262a33] text-white/90 bg-[#070a0f] hover:border-[#3a494b]' } focus:border-[#00f2fe] focus:bg-[#0f1d30] focus:shadow-[0_0_16px_rgba(0,242,254,0.5)] focus:scale-105 disabled:opacity-40 disabled:cursor-not-allowed`} /> ); })}
); }