Files
dmenu-plus-front/src/components/input/MultiInputField.tsx
T
2025-07-02 20:22:28 +03:30

79 lines
3.0 KiB
TypeScript

'use client';
import React, { createRef, useEffect, useRef, useState } from 'react'
type Props = {
htmlFor: string;
labelText?: React.ReactNode;
value?: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
maxLength?: number;
className?: string;
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "id">;
function OTPInputField({ onChange, maxLength = 6, htmlFor, labelText, className, autoFocus, value = '', ...restProps }: Props) {
const values = value?.toString().split('').slice(0, maxLength);
const inputRefs = useRef(Array.from({ length: maxLength }, () => createRef<HTMLInputElement>()));
const [prevLength, setPrevLength] = useState(0);
useEffect(() => {
const valLength = values.length;
const focusIndex = valLength < prevLength
? Math.max(0, valLength)
: Math.min(valLength, maxLength - 1);
inputRefs.current[focusIndex]?.current?.focus();
setPrevLength(valLength);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
const keyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const val = String(value);
const target = e.target as HTMLInputElement;
const index = +target.id.split('-')[2];
if (e.key == "Backspace") {
let i = Math.max(0, val.length - 1)
if (val.length >= maxLength && index < maxLength) {
i = maxLength - 1;
}
inputRefs.current[i]?.current?.focus();
setPrevLength(() => i)
} else {
const i = Math.max(0, val.length)
inputRefs.current[i]?.current?.focus();
setPrevLength(() => i)
}
}
return (
<div className={`${className} h-14 inline-flex relative border-none w-full`}>
<label
className='absolute right-0 -top-6 px-2 bg-background text-[12px] text-foreground'
htmlFor={htmlFor}>
{labelText}
</label>
<div id={htmlFor} className='inline-flex justify-between items-center gap-2'>
{inputRefs?.current?.map((ref, i) => {
return (
<input
autoFocus={i == 0 && autoFocus}
{...restProps}
key={i}
onKeyDown={keyDown}
onChange={onChange}
name={`${htmlFor}-data-${i}`}
readOnly={i != maxLength - 1 && Math.max(0, values?.length) != i && prevLength != i}
ref={ref}
value={values?.at(i) ?? ''}
maxLength={1}
className='pt-1 outline-none text-center inline-flex items-center justify-center h-full relative border border-border w-full rounded-normal group focus-within:border focus:primary' />
)
})}
</div>
</div>
)
}
export default OTPInputField