wallets-api / client /src /components /ui /AmountInput.tsx
z1amez's picture
v.1
2dddd1f
import * as React from "react"
import { Input } from "./input"
interface AmountInputProps extends Omit<React.ComponentProps<typeof Input>, 'value' | 'onChange'> {
value?: string | number;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const AmountInput = React.forwardRef<HTMLInputElement, AmountInputProps>(
({ value, onChange, name, ...props }, ref) => {
const formatNumber = (val: string) => {
if (!val) return "";
// Remove all commas first
const clean = val.replace(/,/g, "");
const parts = clean.split(".");
// Format the integer part
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
};
const [cursor, setCursor] = React.useState<number | null>(null);
const inputRef = React.useRef<HTMLInputElement | null>(null);
// Sync ref
React.useImperativeHandle(ref, () => inputRef.current!);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputVal = e.target.value;
// Allow only numbers, dots and commas
if (/[^\d.,]/.test(inputVal)) return;
const stripped = inputVal.replace(/,/g, "");
const formatted = formatNumber(stripped);
// Track cursor position changes due to formatting
const selectionStart = e.target.selectionStart || 0;
const commasBefore = (inputVal.substring(0, selectionStart).match(/,/g) || []).length;
const commasAfter = (formatted.substring(0, selectionStart).match(/,/g) || []).length;
const diff = commasAfter - commasBefore;
setCursor(selectionStart + diff);
if (onChange) {
// We pass the stripped value back to handlers (like react-hook-form)
// so they can treat it as a normal numeric string
const event = {
...e,
target: {
...e.target,
name: name,
value: stripped
}
} as React.ChangeEvent<HTMLInputElement>;
onChange(event);
}
};
// Restore cursor position after render
React.useLayoutEffect(() => {
if (inputRef.current && cursor !== null) {
inputRef.current.setSelectionRange(cursor, cursor);
}
});
const displayValue = formatNumber(value?.toString() || "");
return (
<Input
{...props}
ref={inputRef}
type="text"
inputMode="decimal"
value={displayValue}
onChange={handleChange}
/>
);
}
);
AmountInput.displayName = "AmountInput";
export { AmountInput };