Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 10x 411x 411x 411x 202x 411x 136x 136x 136x 136x 96x 411x 136x 6x 130x 130x 411x 3x 3x 3x 12x 3x 3x 12x 3x 3x 12x 3x 3x 411x 1644x 3252x 136x 136x 3x | import { useState, useRef, useEffect } from "react";
interface CodeInputProps {
onCodeChange: (code: string[]) => void;
}
export const CodeInput: React.FC<CodeInputProps> = ({ onCodeChange }) => {
const [code, setCode] = useState<string[]>(["", "", "", ""]);
const inputRefs = useRef<(HTMLInputElement | null)[]>(Array(4).fill(null));
useEffect(() => {
onCodeChange(code);
}, [code, onCodeChange]);
const handleInputChange = (index: number, value: string) => {
const newCode = [...code];
newCode[index] = value.slice(0, 1);
setCode(newCode);
if (newCode[index] && index < 3) {
inputRefs.current[index + 1]?.focus();
}
};
const handleKeyDown = (
index: number,
e: React.KeyboardEvent<HTMLInputElement>,
) => {
if (e.key === "Backspace" && !code[index] && index > 0) {
inputRefs.current[index - 1]?.focus();
} elseI if (e.key === "ArrowRight" && index < 3) {
inputRefs.current[index + 1]?.focus();
} elseI if (e.key === "ArrowLeft" && index > 0) {
inputRefs.current[index - 1]?.focus();
}
};
const handlePaste = (
e: React.ClipboardEvent<HTMLInputElement>,
index: number,
) => {
e.preventDefault();
const pastedData = e.clipboardData.getData("text").slice(0, 4);
const chars = pastedData
.split("")
.filter((char) => /^[A-Za-z0-9]$/.test(char));
const newCode = [...code];
for (let i = 0; i < Math.min(chars.length, 4 - index); i++) {
newCode[index + i] = chars[i];
}
setCode(newCode);
const nextEmptyIndex = newCode.findIndex(
(char, idx) => char === "" && idx >= index,
);
if (neIxtEmptyIndex !== -1 && nextEmptyIndex < 4) {
inputRefs.current[nextEmptyIndex]?.focus();
} else {
inputRefs.current[3]?.focus();
}
};
return (
<div
className="flex justify-between gap-4"
role="group"
aria-label="Access code input"
>
{code.map((char, index) => (
<div key={index} className="relative flex-1">
<input
id={index === 0 ? "access-code" : undefined} // only first input gets ID
ref={(el) => {
inputRefs.current[index] = el;
}}
type="text"
className="input input-accent input-lg w-full h-16 text-center font-mono text-3xl font-semibold"
maxLength={1}
value={char}
onChange={(e) =>
handleInputChange(index, e.target.value)
}
onKeyDown={(e) => handleKeyDown(index, e)}
onPaste={(e) => handlePaste(e, index)}
pattern="[A-Za-z0-9]"
required
aria-label={`Character ${index + 1} of access code`}
aria-required="true"
aria-invalid={char === "" ? "true" : "false"}
inputMode="text"
autoComplete="one-time-code"
placeholder="•"
/>
</div>
))}
</div>
);
};
|