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