66 lines
1.5 KiB
TypeScript
66 lines
1.5 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { Input } from '@/components/ui/input';
|
|
import { PIN_MAX_LENGTH, sanitizePinInput } from '@/lib/pin-input';
|
|
|
|
const PIN_ALLOWED_KEYS = new Set([
|
|
'Backspace',
|
|
'Delete',
|
|
'Tab',
|
|
'Escape',
|
|
'Enter',
|
|
'ArrowLeft',
|
|
'ArrowRight',
|
|
'Home',
|
|
'End',
|
|
]);
|
|
|
|
export interface PinInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'inputMode'> {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
maxLength?: number;
|
|
}
|
|
|
|
export function PinInput({
|
|
value,
|
|
onChange,
|
|
maxLength = PIN_MAX_LENGTH,
|
|
onKeyDown,
|
|
onPaste,
|
|
type = 'password',
|
|
...props
|
|
}: PinInputProps) {
|
|
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
onChange(sanitizePinInput(event.target.value, maxLength));
|
|
}
|
|
|
|
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
|
|
if (!PIN_ALLOWED_KEYS.has(event.key) && !event.ctrlKey && !event.metaKey && !/^\d$/.test(event.key)) {
|
|
event.preventDefault();
|
|
}
|
|
onKeyDown?.(event);
|
|
}
|
|
|
|
function handlePaste(event: React.ClipboardEvent<HTMLInputElement>) {
|
|
event.preventDefault();
|
|
onChange(sanitizePinInput(event.clipboardData.getData('text'), maxLength));
|
|
onPaste?.(event);
|
|
}
|
|
|
|
return (
|
|
<Input
|
|
{...props}
|
|
type={type}
|
|
inputMode="numeric"
|
|
autoComplete="one-time-code"
|
|
pattern="\d*"
|
|
maxLength={maxLength}
|
|
value={value}
|
|
onChange={handleChange}
|
|
onKeyDown={handleKeyDown}
|
|
onPaste={handlePaste}
|
|
/>
|
|
);
|
|
}
|