import { isNumber, trim } from 'lodash'; import { MinusIcon, PlusIcon } from 'lucide-react'; import React, { FocusEventHandler, forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { InputProps } from '../ui/input'; interface NumberInputProps { className?: string; value?: number; onChange?: (value: number) => void; height?: number | string; min?: number; max?: number; } const NumberInput = forwardRef( function NumberInput( { className, value: initialValue, onChange, height, min = 0, max = Infinity, }, ref, ) { const [value, setValue] = useState(() => { return initialValue ?? 0; }); const valueRef = useRef(); useEffect(() => { if (initialValue !== undefined) { setValue(initialValue); } }, [initialValue]); const handleDecrement = () => { if (isNumber(value) && value > min) { setValue(value - 1); onChange?.(value - 1); } }; const handleIncrement = () => { if (!isNumber(value)) { return; } if (value > max - 1) { return; } setValue(value + 1); onChange?.(value + 1); }; const handleChange = (e: React.ChangeEvent) => { const currentValue = e.target.value; const newValue = Number(currentValue); if (trim(currentValue) === '') { if (isNumber(value)) { valueRef.current = value; } setValue(''); return; } if (!isNaN(newValue)) { if (newValue > max || newValue < min) { return; } setValue(newValue); onChange?.(newValue); } }; const handleBlur: FocusEventHandler = useCallback(() => { if (isNumber(value)) { onChange?.(value); } else { const previousValue = valueRef.current ?? min; setValue(previousValue); onChange?.(previousValue); } }, [min, onChange, value]); const style = useMemo( () => ({ height: height ? `${height.toString().replace('px', '')}px` : 'auto', }), [height], ); return (
); }, ); export default NumberInput;