'use client';
import clsx from 'clsx';
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { PIN_LENGTH } from '@/libs/crypto/applock';
interface PinInputProps {
value: string;
onChange: (next: string) => void;
autoFocus?: boolean;
shake?: boolean;
disabled?: boolean;
ariaLabel: string;
/**
* When true, the component keeps focus pinned to the hidden input —
* used by the full-screen lock screen so the on-screen keyboard
* stays available even after taps elsewhere on the page.
*/
stickyFocus?: boolean;
/** Form-level autocomplete hint. */
autoComplete?: 'one-time-code' | 'current-password' | 'new-password';
}
export interface PinInputHandle {
focus: () => void;
}
const PinDot = ({ filled, active }: { filled: boolean; active: boolean }) => (
{active && !filled && (
)}
);
const PinInput = forwardRef(function PinInput(
{
value,
onChange,
autoFocus,
shake,
disabled,
ariaLabel,
stickyFocus,
autoComplete = 'one-time-code',
},
ref,
) {
const inputRef = useRef(null);
const [focused, setFocused] = useState(false);
useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus() }));
useEffect(() => {
if (autoFocus) {
const t = window.setTimeout(() => inputRef.current?.focus(), 0);
return () => window.clearTimeout(t);
}
return undefined;
}, [autoFocus]);
useEffect(() => {
if (!stickyFocus) return;
const focus = () => inputRef.current?.focus();
focus();
const t = window.setInterval(focus, 200);
return () => window.clearInterval(t);
}, [stickyFocus]);
const handleClick = () => inputRef.current?.focus();
const handleChange = (e: React.ChangeEvent) => {
const next = e.target.value.replace(/\D/g, '').slice(0, PIN_LENGTH);
onChange(next);
};
return (
);
});
export default PinInput;