import { InputNumber } from 'exchange-elements/v2';
import { FormEvent, FocusEvent, memo, SyntheticEvent, useCallback, useEffect, useRef, useState } from 'react';
import styles from './index.module.scss';
interface NumberFieldProps {
label: string;
value?: number;
initialValue?: number;
min?: number;
max?: number;
onValueChange: (value: number) => void;
}
type HandleChange = {
(event: FormEvent<HTMLInputElement>): void;
(item: number, event?: SyntheticEvent): void;
};
export const NumberField = memo(({ label, value, initialValue, min, max, onValueChange }: NumberFieldProps) => {
const initialFieldValue = initialValue ?? value;
const [localValue, setLocalValue] = useState<number | undefined>(initialFieldValue);
const lastValidValueRef = useRef<number | undefined>(initialFieldValue);
useEffect(() => {
if (value !== undefined) {
setLocalValue(value);
lastValidValueRef.current = value;
}
}, [value]);
const handleChange = useCallback<HandleChange>(
(valueOrEvent: number | FormEvent<HTMLInputElement>) => {
if (typeof valueOrEvent !== 'number' || Number.isNaN(valueOrEvent)) {
setLocalValue(undefined);
return;
}
setLocalValue(valueOrEvent);
},
[],
);
const handleBlur = useCallback(
(_event: FocusEvent<HTMLInputElement>) => {
const fallbackValue = lastValidValueRef.current ?? initialFieldValue ?? min ?? 0;
const normalizedValue = normalizeValue(localValue, fallbackValue, min, max);
setLocalValue(normalizedValue);
lastValidValueRef.current = normalizedValue;
onValueChange(normalizedValue);
},
[initialFieldValue, localValue, min, max, onValueChange],
);
return (
<InputNumber
className={styles.input}
value={localValue}
onChange={handleChange}
onBlur={handleBlur}
label={label}
labelPos="top"
size="sm"
/>
);
});
function normalizeValue(value: number | undefined, fallbackValue: number, min?: number, max?: number): number {
if (value === undefined || Number.isNaN(value)) {
return fallbackValue;
}
if (min !== undefined && value < min) {
return min;
}
if (max !== undefined && value > max) {
return max;
}
return value;
}