import {
type ChangeEvent,
type InputHTMLAttributes,
type ForwardedRef,
type ComponentProps,
useCallback,
forwardRef,
} from 'react';
import { useDebounce } from '../hooks/useDebounce';
import { cn } from '@/styles/theme';
type TextInputProps = Omit, 'onChange'> & {
delayMs?: number;
/** specify 'decimal' to trigger numeric keyboards on mobile devices */
inputMode?: InputHTMLAttributes['inputMode'];
onChange: (s: string) => void;
placeholder: string;
setValue?: (s: string) => void;
inputValidator?: (s: string) => boolean;
/** specify 'error' to show error state (change in color), field is used for a11y purposes, not actually rendered currently, can be either boolean flag or string error message */
error?: string | boolean;
};
export const TextInput = forwardRef(
(
{
'aria-label': ariaLabel,
className,
delayMs = 0,
disabled = false,
onBlur,
onChange,
onFocus,
placeholder,
setValue,
inputMode,
value,
inputValidator = () => true,
error,
...rest
}: TextInputProps,
ref: ForwardedRef,
) => {
const handleDebounce = useDebounce((value: string) => {
onChange(value);
}, delayMs);
const handleChange = useCallback(
(evt: ChangeEvent) => {
const value = evt.target.value;
if (inputValidator(value)) {
setValue?.(value);
if (delayMs > 0) {
handleDebounce(value);
} else {
onChange(value);
}
}
},
[onChange, handleDebounce, delayMs, setValue, inputValidator],
);
return (
);
},
);
TextInput.displayName = 'TextInput';