1 import type { ChangeEvent, FC, KeyboardEvent, KeyboardEventHandler } from 'react';
2 import { useEffect, useReducer } from 'react';
4 import { Button } from '@proton/atoms';
5 import { Icon, InputFieldTwo } from '@proton/components';
6 import clsx from '@proton/utils/clsx';
8 import './IncrementableInput.scss';
12 disabledDecrement?: boolean;
13 disabledIncrement?: boolean;
17 Increment = 'INCREMENT',
18 Decrement = 'DECREMENT',
19 SetValue = 'SET_VALUE',
22 type Action = { type: Actions.Increment } | { type: Actions.Decrement } | { type: Actions.SetValue; payload: number };
24 const getInitialState = ({ value }: State): State => ({ value, disabledDecrement: false, disabledIncrement: false });
27 (min: number, max: number) =>
28 (state: State, action: Action): State => {
29 switch (action.type) {
30 case Actions.Increment: {
31 if (state.value >= max) return { ...state, disabledIncrement: true };
33 const newValue = state.value + 1;
37 disabledDecrement: false,
38 disabledIncrement: newValue >= max,
41 case Actions.Decrement: {
42 if (state.value <= min) return { ...state, disabledDecrement: true };
44 const newValue = state.value - 1;
48 disabledIncrement: false,
49 disabledDecrement: newValue <= min,
52 case Actions.SetValue: {
53 const { payload } = action;
57 disabledDecrement: payload <= min,
58 disabledIncrement: payload >= max,
72 onChange: (value: number) => void;
73 onPressEnter?: KeyboardEventHandler<HTMLInputElement>;
76 export const IncrementableInput: FC<Props> = ({
79 max = Number.MAX_SAFE_INTEGER,
80 min = Number.MIN_SAFE_INTEGER,
85 const [state, dispatch] = useReducer(reducer(min, max), getInitialState({ value }));
87 const onDecrement = () => dispatch({ type: Actions.Decrement });
88 const onIncrement = () => dispatch({ type: Actions.Increment });
90 const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
91 if (event.key === 'Enter' && onPressEnter) onPressEnter(event);
92 if (event.key === 'ArrowDown') onDecrement();
93 if (event.key === 'ArrowUp') onIncrement();
96 const onChangeValue = ({ target: { value: newValue } }: ChangeEvent<HTMLInputElement>) => {
97 const maxReads = parseInt(newValue);
98 if (!maxReads || maxReads < min || maxReads > max) return;
100 dispatch({ type: Actions.SetValue, payload: maxReads });
103 useEffect(() => onChange(state.value), [state.value]);
106 <div className={clsx(['flex flex-nowrap justify-center items-center incrementable-input relative', className])}>
108 className="bg-weak -mr-8 z-1"
109 onClick={onDecrement}
114 disabled={state.disabledDecrement}
116 <Icon name="minus" alt="Decrement value" />
119 rootClassName="flex items-center bg-weak"
120 inputClassName="py-0 text-center"
122 onChange={onChangeValue}
126 onKeyDown={onKeyDown}
131 className="bg-weak z-1"
132 onClick={onIncrement}
137 disabled={state.disabledIncrement}
139 <Icon name="plus" alt="Increment value" />