import React, { useState, useCallback, useEffect } from 'react'; import IconButton from '../IconButton'; import Icon from '../Icon'; import './InputNumber.css'; import Label from '../Label'; import getMaxDigits from '../../utils/getMaxDigits'; /** * React Number Input component' * it has two props, value and onChange * value is a number value * onChange is a function that will be called when the number input is changed * it can get changed by clicking on the up and down buttons * or by typing a number in the input */ const sizesClasses = { sm: 'w-[45px] h-[28px]', lg: 'w-[206px] h-[35px]', }; const InputNumber: React.FC<{ value: number; onChange: (value: number) => void; minValue?: number; maxValue?: number; step?: number; size?: 'sm' | 'lg'; className?: string; labelClassName?: string; label?: string; showAdjustmentArrows?: boolean; }> = ({ value, onChange, step = 1, className, size = 'sm', minValue = 0, maxValue = 100, labelClassName, label, showAdjustmentArrows = true, }) => { const [numberValue, setNumberValue] = useState(value); const [isFocused, setIsFocused] = useState(false); const maxDigits = getMaxDigits(maxValue, step); const inputWidth = Math.max(maxDigits * 10, showAdjustmentArrows ? 20 : 28); const arrowWidth = showAdjustmentArrows ? 20 : 0; const containerWidth = `${inputWidth + arrowWidth}px`; const decimalPlaces = Number.isInteger(step) ? 0 : step.toString().split('.')[1].length; useEffect(() => { setNumberValue(value); }, [value]); const handleMinMax = useCallback( (val: number) => Math.min(Math.max(val, minValue), maxValue), [maxValue, minValue] ); const handleChange = (e: React.ChangeEvent) => { const inputValue = e.target.value; // Allow negative sign, empty string, or single decimal point for user flexibility if (inputValue === '-' || inputValue === '' || inputValue === '.') { setNumberValue(inputValue); return; } const number = Number(inputValue); // Filter out invalid inputs like 'NaN' if (!isNaN(number)) { updateValue(number); } }; const updateValue = (val: number) => { const newValue = handleMinMax(val); setNumberValue(newValue); onChange(newValue); }; const handleFocus = () => { setIsFocused(true); }; const handleBlur = () => { setIsFocused(false); setNumberValue(parseFloat(numberValue).toFixed(decimalPlaces)); }; const increment = () => updateValue(parseFloat(numberValue) + step); const decrement = () => updateValue(parseFloat(numberValue) - step); return (
{label && (
); }; const ArrowButton = ({ onClick, rotate = false }: { onClick: () => void; rotate?: boolean }) => ( ); export default InputNumber;