dstpabuaran.com/resources/js/components/number-input.tsx

104 lines
2.6 KiB
TypeScript

import { useCallback, useRef, useState } from 'react';
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from '@/components/ui/input-group';
type NumberInputProps = {
id?: string;
name?: string;
defaultValue?: number;
value?: number;
onValueChange?: (value: number) => void;
placeholder?: string;
disabled?: boolean;
min?: number;
max?: number;
suffix?: string;
className?: string;
};
function formatDisplay(value: number): string {
return value.toLocaleString('id-ID');
}
function parseText(value: string): number {
const cleaned = value.replace(/[^0-9]/g, '');
return cleaned === '' ? 0 : parseInt(cleaned, 10);
}
export function NumberInput({
id,
name,
defaultValue = 0,
value,
onValueChange,
placeholder = '0',
disabled = false,
min,
max,
suffix,
className,
}: NumberInputProps) {
const isControlled = value !== undefined;
const [displayValue, setDisplayValue] = useState(
formatDisplay(isControlled ? value : defaultValue),
);
const lastValidRef = useRef(isControlled ? value : defaultValue);
const [prevValue, setPrevValue] = useState(value);
if (isControlled && value !== prevValue) {
setPrevValue(value);
setDisplayValue(formatDisplay(value));
}
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const raw = parseText(e.target.value);
let clamped = raw;
if (min !== undefined && raw < min) {
clamped = min;
}
if (max !== undefined && raw > max) {
clamped = max;
}
lastValidRef.current = clamped;
setDisplayValue(formatDisplay(clamped));
onValueChange?.(clamped);
},
[min, max, onValueChange],
);
const handleBlur = useCallback(() => {
setDisplayValue(formatDisplay(lastValidRef.current));
}, []);
return (
<InputGroup className={className}>
<InputGroupInput
id={id}
name={name}
type="text"
inputMode="numeric"
value={displayValue}
onChange={handleChange}
onBlur={handleBlur}
placeholder={placeholder}
disabled={disabled}
autoComplete="off"
/>
{suffix && (
<InputGroupAddon align="inline-end">
<InputGroupText>{suffix}</InputGroupText>
</InputGroupAddon>
)}
</InputGroup>
);
}