import * as React from "react"; import { cn } from "@/lib/utils"; import { Input } from "./input"; type NumberInputProps = Omit< React.ComponentProps, "type" | "value" | "onChange" > & { value: number | undefined | null; onChange: (value: number | undefined) => void; integer?: boolean; }; const NumberInput = React.forwardRef( ({ value, onChange, integer = true, className, ...props }, ref) => { const [display, setDisplay] = React.useState( value == null ? "" : String(value) ); // Track the last value we saw from props so we only resync the // visible string when the prop changes externally. This lets the // user clear the field even when the parent keeps the previous // numeric value (because our onChange(undefined) was ignored). const lastPropRef = React.useRef(value); React.useEffect(() => { if (value !== lastPropRef.current) { lastPropRef.current = value; setDisplay(value == null ? "" : String(value)); } }, [value]); return ( { const next = e.target.value; setDisplay(next); if (next === "") { onChange(undefined); return; } const n = integer ? parseInt(next, 10) : parseFloat(next); if (Number.isFinite(n)) onChange(n); }} className={cn( "[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:m-0", className )} {...props} /> ); } ); NumberInput.displayName = "NumberInput"; export { NumberInput };