79 lines
3.0 KiB
TypeScript
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 |