feat(desktop): shared PinInput component
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
onChange: (next: string) => void;
|
||||
length?: number;
|
||||
autoFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
ariaLabel: string;
|
||||
onSubmit?: () => void;
|
||||
}
|
||||
|
||||
export function PinInput({ value, onChange, length = 6, autoFocus, disabled, ariaLabel, onSubmit }: Props) {
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
useEffect(() => { if (autoFocus) ref.current?.focus(); }, [autoFocus]);
|
||||
return (
|
||||
<div className="relative flex justify-center" onClick={() => ref.current?.focus()}>
|
||||
<input
|
||||
ref={ref}
|
||||
aria-label={ariaLabel}
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
pattern="\d*"
|
||||
maxLength={length}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value.replace(/\D/g, '').slice(0, length))}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' && value.length === length) onSubmit?.(); }}
|
||||
className="absolute h-px w-px overflow-hidden p-0 opacity-0"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
{Array.from({ length }).map((_, i) => {
|
||||
const filled = i < value.length;
|
||||
return (
|
||||
<span key={i}
|
||||
className={
|
||||
'flex h-12 w-10 items-center justify-center rounded-lg border text-lg font-semibold ' +
|
||||
(filled
|
||||
? 'border-brand-400 bg-brand-500/10 text-white'
|
||||
: 'border-white/10 bg-ink-800 text-neutral-500')
|
||||
}
|
||||
>{filled ? '•' : ''}</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user