feat(desktop): shared PinInput component

This commit is contained in:
byGalax
2026-05-15 22:54:58 +02:00
parent 20216b37c6
commit 02c4bb1c9b
+48
View File
@@ -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>
);
}