+ ) => {
+ if (inputLocked) return
+
+ const value = e.target.value
+ const nativeIsComposing =
+ typeof (e.nativeEvent as InputEvent).isComposing === 'boolean'
+ ? (e.nativeEvent as InputEvent).isComposing
+ : false
+ const composing = isComposing || nativeIsComposing
+ const newInput = [...userInput]
+
+ if (composing) {
+ newInput[index] = value
+ onInputChange(newInput)
+ return
+ }
+
+ if (value.length > 1) {
+ value
+ .split('')
+ .slice(0, userInput.length - index)
+ .forEach((char, offset) => {
+ newInput[index + offset] = char
+ })
+ onInputChange(newInput)
+ inputRefs.current[Math.min(index + value.length, userInput.length - 1)]?.focus()
+ } else {
+ newInput[index] = value
+ onInputChange(newInput)
+ if (value && index < userInput.length - 1) {
+ inputRefs.current[index + 1]?.focus()
+ }
+ }
+ }
+
+ const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
+ if (inputLocked) return
+
+ if (e.key === 'Backspace') {
+ e.preventDefault()
+ const newInput = [...userInput]
+ if (newInput[index]) {
+ newInput[index] = ''
+ onInputChange(newInput)
+ } else if (index > 0) {
+ newInput[index - 1] = ''
+ onInputChange(newInput)
+ inputRefs.current[index - 1]?.focus()
+ }
+ } else if (e.key === 'ArrowLeft' && index > 0) {
+ e.preventDefault()
+ inputRefs.current[index - 1]?.focus()
+ } else if (e.key === 'ArrowRight' && index < userInput.length - 1) {
+ e.preventDefault()
+ inputRefs.current[index + 1]?.focus()
+ }
+ }
+
+ return (
+
+ {userInput.map((char, index) => {
+ const status = charStatuses[index] || 'pending'
+ const isError = status === 'error'
+ const isCorrect = status === 'correct'
+
+ return (
+
+ (inputRefs.current[index] = el)}
+ type="text"
+ value={char}
+ onChange={(e) => handleCharInput(index, e)}
+ onKeyDown={(e) => handleKeyDown(index, e)}
+ onCompositionStart={onCompositionStart}
+ onCompositionEnd={(e) => onCompositionEnd(index, e.currentTarget.value)}
+ onPaste={onPaste}
+ disabled={inputLocked}
+ placeholder="_"
+ className={clsx(
+ 'h-12 w-10 border-b-2 bg-transparent text-center text-2xl font-medium transition-all duration-200',
+ 'text-gray-900 placeholder:text-gray-400 focus:outline-none dark:text-gray-100 dark:placeholder:text-gray-500',
+ isComposing && 'border-gray-300 opacity-70 dark:border-gray-600',
+ !isComposing && !char && 'border-gray-400 dark:border-gray-500',
+ !isComposing && isCorrect && 'border-green-500',
+ !isComposing && isError && 'border-red-500',
+ inputLocked && 'cursor-not-allowed opacity-50'
+ )}
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ spellCheck="false"
+ />
+
+ )
+ })}
+
+ )
+}