Spaces:
Sleeping
Sleeping
| 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 }; | |