import React, { useRef, useEffect } from "react"; interface OtpInputProps { value: string; onChange: (code: string) => void; length?: number; disabled?: boolean; } export function OtpInput({ value, onChange, length = 6, disabled = false, }: OtpInputProps) { const inputsRef = useRef<(HTMLInputElement | null)[]>([]); // Array of digits according to desired length const digits = Array.from({ length }, (_, i) => value[i] || ""); useEffect(() => { // Focus the first empty input or the first one on mount const firstEmptyIndex = digits.findIndex((d) => !d); const targetIndex = firstEmptyIndex === -1 ? 0 : firstEmptyIndex; inputsRef.current[targetIndex]?.focus(); }, []); const handleChange = ( e: React.ChangeEvent, index: number ) => { const rawVal = e.target.value.replace(/\D/g, ""); if (!rawVal) { // Cleared current digit const newDigits = [...digits]; newDigits[index] = ""; onChange(newDigits.join("")); return; } const digit = rawVal.slice(-1); const newDigits = [...digits]; newDigits[index] = digit; const combined = newDigits.join(""); onChange(combined); // Auto advance focus to the next input if (index < length - 1) { inputsRef.current[index + 1]?.focus(); inputsRef.current[index + 1]?.select(); } }; const handleKeyDown = ( e: React.KeyboardEvent, index: number ) => { if (e.key === "Backspace") { if (!digits[index] && index > 0) { // Current box is empty, jump to previous and clear it const newDigits = [...digits]; newDigits[index - 1] = ""; onChange(newDigits.join("")); inputsRef.current[index - 1]?.focus(); } } else if (e.key === "ArrowLeft" && index > 0) { inputsRef.current[index - 1]?.focus(); } else if (e.key === "ArrowRight" && index < length - 1) { inputsRef.current[index + 1]?.focus(); } }; const handlePaste = (e: React.ClipboardEvent) => { e.preventDefault(); const pasted = e.clipboardData .getData("text") .replace(/\D/g, "") .slice(0, length); if (!pasted) return; onChange(pasted); const nextIndex = Math.min(pasted.length, length - 1); inputsRef.current[nextIndex]?.focus(); }; return (
{digits.map((digit, index) => ( { inputsRef.current[index] = el; }} type="text" inputMode="numeric" pattern="[0-9]*" maxLength={1} value={digit} disabled={disabled} onChange={(e) => handleChange(e, index)} onKeyDown={(e) => handleKeyDown(e, index)} onPaste={handlePaste} className="otp-digit-input" autoComplete="one-time-code" /> ))}
); }