@alfalab/core-components-number-input 1.3.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Component.desktop.d.ts +1 -1
- package/Component.desktop.js +9 -1
- package/Component.mobile.d.ts +1 -1
- package/Component.mobile.js +9 -1
- package/Component.responsive.d.ts +1 -1
- package/Component.responsive.js +14 -1
- package/components/number-input/Component.d.ts +21 -46
- package/components/number-input/Component.js +97 -84
- package/components/number-input/default.css +24 -0
- package/components/number-input/index.css +23 -0
- package/components/number-input/index.js +8 -0
- package/components/number-input/inverted.css +24 -0
- package/components/steppers/Component.d.ts +15 -0
- package/components/steppers/Component.js +43 -0
- package/components/steppers/default.css +20 -0
- package/components/steppers/index.css +30 -0
- package/components/steppers/index.d.ts +1 -0
- package/components/steppers/index.js +15 -0
- package/components/steppers/inverted.css +20 -0
- package/cssm/Component.desktop.d.ts +1 -1
- package/cssm/Component.desktop.js +15 -1
- package/cssm/Component.mobile.d.ts +1 -1
- package/cssm/Component.mobile.js +15 -1
- package/cssm/Component.responsive.d.ts +1 -1
- package/cssm/Component.responsive.js +20 -1
- package/cssm/components/number-input/Component.d.ts +21 -46
- package/cssm/components/number-input/Component.js +97 -84
- package/cssm/components/number-input/default.module.css +23 -0
- package/cssm/components/number-input/index.js +14 -0
- package/cssm/components/number-input/index.module.css +22 -0
- package/cssm/components/number-input/inverted.module.css +23 -0
- package/cssm/components/steppers/Component.d.ts +15 -0
- package/cssm/components/steppers/Component.js +40 -0
- package/cssm/components/steppers/default.module.css +19 -0
- package/cssm/components/steppers/index.d.ts +1 -0
- package/cssm/components/steppers/index.js +18 -0
- package/cssm/components/steppers/index.module.css +29 -0
- package/cssm/components/steppers/inverted.module.css +19 -0
- package/cssm/desktop/index.js +14 -0
- package/cssm/index.js +15 -0
- package/cssm/mobile/index.js +14 -0
- package/cssm/utils.d.ts +19 -9
- package/cssm/utils.js +244 -21
- package/desktop/index.js +8 -0
- package/esm/Component.desktop.d.ts +1 -1
- package/esm/Component.desktop.js +9 -1
- package/esm/Component.mobile.d.ts +1 -1
- package/esm/Component.mobile.js +9 -1
- package/esm/Component.responsive.d.ts +1 -1
- package/esm/Component.responsive.js +15 -2
- package/esm/components/number-input/Component.d.ts +21 -46
- package/esm/components/number-input/Component.js +98 -86
- package/esm/components/number-input/default.css +24 -0
- package/esm/components/number-input/index.css +23 -0
- package/esm/components/number-input/index.js +8 -0
- package/esm/components/number-input/inverted.css +24 -0
- package/esm/components/steppers/Component.d.ts +15 -0
- package/esm/components/steppers/Component.js +34 -0
- package/esm/components/steppers/default.css +20 -0
- package/esm/components/steppers/index.css +30 -0
- package/esm/components/steppers/index.d.ts +1 -0
- package/esm/components/steppers/index.js +7 -0
- package/esm/components/steppers/inverted.css +20 -0
- package/esm/desktop/index.js +8 -0
- package/esm/index.js +9 -0
- package/esm/mobile/index.js +8 -0
- package/esm/utils.d.ts +19 -9
- package/esm/utils.js +236 -19
- package/index.js +9 -0
- package/mobile/index.js +8 -0
- package/modern/Component.desktop.d.ts +1 -1
- package/modern/Component.desktop.js +9 -1
- package/modern/Component.mobile.d.ts +1 -1
- package/modern/Component.mobile.js +9 -1
- package/modern/Component.responsive.d.ts +1 -1
- package/modern/Component.responsive.js +13 -1
- package/modern/components/number-input/Component.d.ts +21 -46
- package/modern/components/number-input/Component.js +94 -86
- package/modern/components/number-input/default.css +24 -0
- package/modern/components/number-input/index.css +23 -0
- package/modern/components/number-input/index.js +8 -0
- package/modern/components/number-input/inverted.css +24 -0
- package/modern/components/steppers/Component.d.ts +15 -0
- package/modern/components/steppers/Component.js +33 -0
- package/modern/components/steppers/default.css +20 -0
- package/modern/components/steppers/index.css +30 -0
- package/modern/components/steppers/index.d.ts +1 -0
- package/modern/components/steppers/index.js +7 -0
- package/modern/components/steppers/inverted.css +20 -0
- package/modern/desktop/index.js +8 -0
- package/modern/index.js +9 -0
- package/modern/mobile/index.js +8 -0
- package/modern/utils.d.ts +19 -9
- package/modern/utils.js +223 -18
- package/package.json +9 -2
- package/src/Component.desktop.tsx +2 -2
- package/src/Component.mobile.tsx +2 -2
- package/src/Component.responsive.tsx +16 -2
- package/src/components/number-input/Component.tsx +204 -128
- package/src/components/number-input/default.module.css +10 -0
- package/src/components/number-input/index.module.css +9 -0
- package/src/components/number-input/inverted.module.css +10 -0
- package/src/components/steppers/Component.tsx +75 -0
- package/src/components/steppers/default.module.css +5 -0
- package/src/components/steppers/index.module.css +20 -0
- package/src/components/steppers/index.ts +1 -0
- package/src/components/steppers/inverted.module.css +5 -0
- package/src/utils.ts +302 -24
- package/utils.d.ts +19 -9
- package/utils.js +244 -21
|
@@ -1,20 +1,52 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
ChangeEvent,
|
|
3
|
+
FC,
|
|
4
|
+
FocusEvent,
|
|
5
|
+
forwardRef,
|
|
6
|
+
Ref,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
2
11
|
import mergeRefs from 'react-merge-refs';
|
|
12
|
+
import { MaskitoOptions, maskitoTransform } from '@maskito/core';
|
|
13
|
+
import { useMaskito } from '@maskito/react';
|
|
14
|
+
import cn from 'classnames';
|
|
3
15
|
|
|
4
16
|
import type { InputProps } from '@alfalab/core-components-input';
|
|
17
|
+
import { fnUtils, os } from '@alfalab/core-components-shared';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
createMaskOptions,
|
|
21
|
+
MAX_DIGITS,
|
|
22
|
+
MAX_SAFE_INTEGER,
|
|
23
|
+
MIN_SAFE_INTEGER,
|
|
24
|
+
MINUS_SIGN,
|
|
25
|
+
parseNumber,
|
|
26
|
+
stringifyNumberWithoutExp,
|
|
27
|
+
} from '../../utils';
|
|
28
|
+
import { Steppers } from '../steppers';
|
|
29
|
+
|
|
30
|
+
import defaultColors from './default.module.css';
|
|
31
|
+
import styles from './index.module.css';
|
|
32
|
+
import invertedColors from './inverted.module.css';
|
|
33
|
+
|
|
34
|
+
const colorStyles = {
|
|
35
|
+
default: defaultColors,
|
|
36
|
+
inverted: invertedColors,
|
|
37
|
+
};
|
|
5
38
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type NumberInputProps = Omit<InputProps, 'value' | 'onChange' | 'type'> & {
|
|
39
|
+
export interface NumberInputProps
|
|
40
|
+
extends Omit<InputProps, 'value' | 'onChange' | 'type' | 'defaultValue'> {
|
|
9
41
|
/**
|
|
10
42
|
* Значение поля ввода
|
|
11
43
|
*/
|
|
12
44
|
value?: string | number | null;
|
|
13
45
|
|
|
14
46
|
/**
|
|
15
|
-
*
|
|
47
|
+
* Значение по-умолчанию
|
|
16
48
|
*/
|
|
17
|
-
|
|
49
|
+
defaultValue?: string | number | null;
|
|
18
50
|
|
|
19
51
|
/**
|
|
20
52
|
* Разделитель ',' или '.'
|
|
@@ -23,178 +55,222 @@ export type NumberInputProps = Omit<InputProps, 'value' | 'onChange' | 'type'> &
|
|
|
23
55
|
|
|
24
56
|
/**
|
|
25
57
|
* Количество символов после разделителя
|
|
58
|
+
* Если указан проп step, то всегда 0
|
|
26
59
|
*/
|
|
27
60
|
fractionLength?: number;
|
|
28
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Шаг инкремента/декремента. Используйте только целочисленные значения
|
|
64
|
+
*/
|
|
65
|
+
step?: number;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Минимальное значение
|
|
69
|
+
* @default Number.MIN_SAFE_INTEGER
|
|
70
|
+
*/
|
|
71
|
+
min?: number;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Максимальное значение
|
|
75
|
+
* @default Number.MAX_SAFE_INTEGER
|
|
76
|
+
*/
|
|
77
|
+
max?: number;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Отображение компонента в мобильном или десктопном виде
|
|
81
|
+
*/
|
|
82
|
+
view?: 'desktop' | 'mobile';
|
|
83
|
+
|
|
29
84
|
/**
|
|
30
85
|
* Компонент инпута
|
|
31
86
|
*/
|
|
32
|
-
Input:
|
|
87
|
+
Input: FC<InputProps & { ref?: Ref<HTMLInputElement> }>;
|
|
33
88
|
|
|
34
89
|
/**
|
|
35
90
|
* Обработчик события изменения значения
|
|
36
91
|
*/
|
|
37
|
-
onChange?: (
|
|
38
|
-
|
|
39
|
-
payload: {
|
|
40
|
-
/**
|
|
41
|
-
* Числовое значение инпута
|
|
42
|
-
*/
|
|
43
|
-
value: number | null;
|
|
44
|
-
/**
|
|
45
|
-
* Строковое значение инпута
|
|
46
|
-
* Используйте для изменения значения инпута
|
|
47
|
-
*/
|
|
48
|
-
valueString: string;
|
|
49
|
-
},
|
|
50
|
-
) => void;
|
|
51
|
-
};
|
|
92
|
+
onChange?: (e: ChangeEvent<HTMLInputElement> | null, payload: { value: number | null }) => void;
|
|
93
|
+
}
|
|
52
94
|
|
|
53
|
-
export const NumberInput =
|
|
95
|
+
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
54
96
|
(
|
|
55
97
|
{
|
|
56
98
|
value: propValue,
|
|
57
99
|
onChange,
|
|
58
|
-
onBlur,
|
|
59
|
-
allowSigns = true,
|
|
60
100
|
separator = ',',
|
|
61
|
-
fractionLength,
|
|
101
|
+
fractionLength = MAX_DIGITS,
|
|
62
102
|
defaultValue,
|
|
63
103
|
Input,
|
|
104
|
+
min: minProp,
|
|
105
|
+
max: maxProp,
|
|
106
|
+
rightAddons,
|
|
107
|
+
dataTestId,
|
|
108
|
+
disabled,
|
|
109
|
+
onBlur,
|
|
110
|
+
onFocus,
|
|
111
|
+
view,
|
|
112
|
+
step: stepProp,
|
|
113
|
+
size = 's',
|
|
114
|
+
disableUserInput,
|
|
115
|
+
clear: clearProp,
|
|
116
|
+
colors = 'default',
|
|
64
117
|
...restProps
|
|
65
118
|
},
|
|
66
119
|
ref,
|
|
67
120
|
) => {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
121
|
+
const min = Math.max(MIN_SAFE_INTEGER, minProp ?? MIN_SAFE_INTEGER);
|
|
122
|
+
const max = Math.min(MAX_SAFE_INTEGER, maxProp ?? MAX_SAFE_INTEGER);
|
|
123
|
+
const withStepper = stepProp !== undefined;
|
|
124
|
+
|
|
125
|
+
const maskOptions: MaskitoOptions = useMemo(
|
|
126
|
+
() =>
|
|
127
|
+
createMaskOptions({
|
|
128
|
+
separator,
|
|
129
|
+
fractionLength: withStepper ? 0 : fractionLength,
|
|
130
|
+
min,
|
|
131
|
+
max,
|
|
132
|
+
}),
|
|
133
|
+
[separator, fractionLength, min, max, withStepper],
|
|
134
|
+
);
|
|
71
135
|
|
|
72
|
-
const
|
|
73
|
-
|
|
136
|
+
const [focused, setFocused] = useState(false);
|
|
137
|
+
const [value, setValue] = useState(() => {
|
|
138
|
+
if (defaultValue == null) {
|
|
139
|
+
return withStepper ? fnUtils.clamp(0, min, max).toString() : '';
|
|
140
|
+
}
|
|
74
141
|
|
|
75
|
-
|
|
76
|
-
|
|
142
|
+
return fnUtils
|
|
143
|
+
.clamp(
|
|
144
|
+
parseNumber(maskitoTransform(defaultValue.toString(), maskOptions)),
|
|
145
|
+
min,
|
|
146
|
+
max,
|
|
147
|
+
)
|
|
148
|
+
.toString();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const maskRef = useMaskito({ options: maskOptions });
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (propValue !== undefined) {
|
|
155
|
+
setValue((prev) => {
|
|
156
|
+
const parsedNumber = parseNumber(propValue);
|
|
157
|
+
|
|
158
|
+
if (parsedNumber !== parseNumber(prev)) {
|
|
159
|
+
return maskitoTransform(
|
|
160
|
+
stringifyNumberWithoutExp(parsedNumber),
|
|
161
|
+
maskOptions,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return prev;
|
|
166
|
+
});
|
|
77
167
|
}
|
|
168
|
+
}, [maskOptions, propValue, separator]);
|
|
78
169
|
|
|
79
|
-
|
|
80
|
-
|
|
170
|
+
const getMaxLength = (valueString: string) => {
|
|
171
|
+
const hasSeparator = valueString?.includes(separator);
|
|
172
|
+
const hasSigns = valueString?.startsWith(MINUS_SIGN);
|
|
81
173
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const input = target;
|
|
85
|
-
const positionCursor = input.selectionStart || 0;
|
|
86
|
-
const isEndPosition = input.value.length === positionCursor;
|
|
174
|
+
return MAX_DIGITS + (hasSeparator ? 1 : 0) + (hasSigns ? 1 : 0);
|
|
175
|
+
};
|
|
87
176
|
|
|
88
|
-
|
|
89
|
-
const enteredSeparator = SEPARATORS.filter((s) => s !== separator).some(
|
|
90
|
-
(s) => s === input.value[positionCursor - 1],
|
|
91
|
-
);
|
|
177
|
+
const getStep = () => Math.round(stepProp ?? 1);
|
|
92
178
|
|
|
93
|
-
|
|
179
|
+
const changeValue = (
|
|
180
|
+
event: ChangeEvent<HTMLInputElement> | null,
|
|
181
|
+
newValue: number | null,
|
|
182
|
+
) => {
|
|
183
|
+
const isNaNValue = Number.isNaN(newValue);
|
|
184
|
+
const valueString = event?.target.value ?? newValue?.toString() ?? '';
|
|
94
185
|
|
|
95
|
-
|
|
96
|
-
input.selectionStart = positionCursor;
|
|
97
|
-
input.selectionEnd = positionCursor;
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
};
|
|
186
|
+
setValue(valueString);
|
|
101
187
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const allowedValue = getAllowedValue({
|
|
107
|
-
value: newValue,
|
|
108
|
-
fractionLength,
|
|
109
|
-
allowSigns,
|
|
110
|
-
separator,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (onChange) {
|
|
114
|
-
onChange(event, {
|
|
115
|
-
value: getNumberValueFromStr(allowedValue),
|
|
116
|
-
valueString: allowedValue,
|
|
188
|
+
if (valueString === '' || !isNaNValue) {
|
|
189
|
+
onChange?.(event, {
|
|
190
|
+
value: isNaNValue ? null : newValue,
|
|
117
191
|
});
|
|
118
192
|
}
|
|
119
|
-
|
|
120
|
-
if (uncontrolled) {
|
|
121
|
-
setValue(allowedValue);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
restoreCaret(input);
|
|
125
193
|
};
|
|
126
194
|
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const oneKeyPress = !event.altKey && !event.metaKey && !event.ctrlKey;
|
|
130
|
-
const target = event.target as HTMLInputElement;
|
|
131
|
-
|
|
132
|
-
// Запрещаем вводить неразрешенные символы за исключением комбинаций клавиш
|
|
133
|
-
if (oneKeyPress && event.key.length === 1 && disallowedSymbols.test(event.key)) {
|
|
134
|
-
return event.preventDefault();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const val = target.value;
|
|
195
|
+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
196
|
+
const valueString = event.target.value;
|
|
138
197
|
|
|
139
|
-
|
|
198
|
+
changeValue(event, parseNumber(valueString));
|
|
199
|
+
};
|
|
140
200
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
201
|
+
const handleIncrement = () => {
|
|
202
|
+
const parsed = parseNumber(value);
|
|
203
|
+
const nextValue = maskitoTransform(
|
|
204
|
+
(Number.isNaN(parsed) ? min : parsed + getStep()).toString(),
|
|
205
|
+
maskOptions,
|
|
206
|
+
);
|
|
145
207
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
(!allowSigns || SIGNS.some((s) => s === val[0])) &&
|
|
149
|
-
SIGNS.some((s) => s === event.key)
|
|
150
|
-
) {
|
|
151
|
-
return event.preventDefault();
|
|
152
|
-
}
|
|
208
|
+
changeValue(null, parseNumber(nextValue));
|
|
209
|
+
};
|
|
153
210
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
event.key.length === 1 &&
|
|
161
|
-
selectionStart > val.indexOf(separator) &&
|
|
162
|
-
val.split(separator)[1].length >= fractionLength
|
|
163
|
-
) {
|
|
164
|
-
return event.preventDefault();
|
|
165
|
-
}
|
|
211
|
+
const handleDecrement = () => {
|
|
212
|
+
const parsed = parseNumber(value);
|
|
213
|
+
const nextValue = maskitoTransform(
|
|
214
|
+
(Number.isNaN(parsed) ? max : parsed - getStep()).toString(),
|
|
215
|
+
maskOptions,
|
|
216
|
+
);
|
|
166
217
|
|
|
167
|
-
|
|
218
|
+
changeValue(null, parseNumber(nextValue));
|
|
168
219
|
};
|
|
169
220
|
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (onChange) {
|
|
174
|
-
onChange(event, {
|
|
175
|
-
value: getNumberValueFromStr(valueBlur),
|
|
176
|
-
valueString: valueBlur,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
221
|
+
const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
|
|
222
|
+
onFocus?.(e);
|
|
179
223
|
|
|
180
|
-
if (
|
|
181
|
-
|
|
224
|
+
if (!disableUserInput) {
|
|
225
|
+
setFocused(true);
|
|
182
226
|
}
|
|
183
|
-
|
|
184
|
-
if (onBlur) onBlur(event);
|
|
185
227
|
};
|
|
186
228
|
|
|
187
|
-
const
|
|
229
|
+
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
|
|
230
|
+
onBlur?.(e);
|
|
231
|
+
setFocused(false);
|
|
232
|
+
};
|
|
188
233
|
|
|
189
234
|
return (
|
|
190
235
|
<Input
|
|
191
|
-
|
|
192
|
-
value={visibleValue}
|
|
193
|
-
onBlur={handleBlur}
|
|
194
|
-
onChange={handleChange}
|
|
195
|
-
onKeyDown={handleKeyDown}
|
|
196
|
-
inputMode='decimal'
|
|
236
|
+
maxLength={getMaxLength(value)}
|
|
197
237
|
{...restProps}
|
|
238
|
+
// В iOS в цифровой клавиатуре невозможно ввести минус.
|
|
239
|
+
inputMode={min < 0 && os.isIOS() ? 'text' : 'decimal'}
|
|
240
|
+
ref={mergeRefs([ref, maskRef])}
|
|
241
|
+
value={value}
|
|
242
|
+
onInput={handleChange}
|
|
243
|
+
dataTestId={dataTestId}
|
|
244
|
+
colors={colors}
|
|
245
|
+
disabled={disabled}
|
|
246
|
+
onFocus={handleFocus}
|
|
247
|
+
onBlur={handleBlur}
|
|
248
|
+
size={size}
|
|
249
|
+
disableUserInput={disableUserInput}
|
|
250
|
+
clear={clearProp && /\d/.test(value)}
|
|
251
|
+
rightAddons={
|
|
252
|
+
withStepper ? (
|
|
253
|
+
<React.Fragment>
|
|
254
|
+
{rightAddons}
|
|
255
|
+
<Steppers
|
|
256
|
+
colors={colors}
|
|
257
|
+
dataTestId={dataTestId}
|
|
258
|
+
disabled={disabled}
|
|
259
|
+
value={parseNumber(value)}
|
|
260
|
+
min={min}
|
|
261
|
+
max={max}
|
|
262
|
+
className={cn(colorStyles[colors].steppers, styles[size], {
|
|
263
|
+
[colorStyles[colors].steppersFocused]: focused,
|
|
264
|
+
[colorStyles[colors].steppersDisabled]: disabled,
|
|
265
|
+
})}
|
|
266
|
+
onIncrement={handleIncrement}
|
|
267
|
+
onDecrement={handleDecrement}
|
|
268
|
+
/>
|
|
269
|
+
</React.Fragment>
|
|
270
|
+
) : (
|
|
271
|
+
rightAddons
|
|
272
|
+
)
|
|
273
|
+
}
|
|
198
274
|
/>
|
|
199
275
|
);
|
|
200
276
|
},
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
|
|
4
|
+
import { IconButton } from '@alfalab/core-components-icon-button';
|
|
5
|
+
import { getDataTestId } from '@alfalab/core-components-shared';
|
|
6
|
+
import { MinusMIcon } from '@alfalab/icons-glyph/MinusMIcon';
|
|
7
|
+
import { PlusMediumMIcon } from '@alfalab/icons-glyph/PlusMediumMIcon';
|
|
8
|
+
|
|
9
|
+
import defaultColors from './default.module.css';
|
|
10
|
+
import styles from './index.module.css';
|
|
11
|
+
import invertedColors from './inverted.module.css';
|
|
12
|
+
|
|
13
|
+
export type SteppersProps = {
|
|
14
|
+
value: number;
|
|
15
|
+
min: number;
|
|
16
|
+
max: number;
|
|
17
|
+
className?: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
onIncrement: () => void;
|
|
20
|
+
onDecrement: () => void;
|
|
21
|
+
dataTestId?: string;
|
|
22
|
+
colors: 'default' | 'inverted';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const colorStyles = {
|
|
26
|
+
default: defaultColors,
|
|
27
|
+
inverted: invertedColors,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function preventDefault(e: React.MouseEvent<HTMLElement>) {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const Steppers: React.FC<SteppersProps> = ({
|
|
35
|
+
className,
|
|
36
|
+
onIncrement,
|
|
37
|
+
onDecrement,
|
|
38
|
+
value,
|
|
39
|
+
min,
|
|
40
|
+
max,
|
|
41
|
+
disabled,
|
|
42
|
+
dataTestId,
|
|
43
|
+
colors,
|
|
44
|
+
}) => {
|
|
45
|
+
const decButtonDisabled = disabled || value <= min;
|
|
46
|
+
const incButtonDisabled = disabled || value >= max;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className={cn(styles.component, className)}>
|
|
50
|
+
<IconButton
|
|
51
|
+
colors={colors}
|
|
52
|
+
disabled={decButtonDisabled}
|
|
53
|
+
className={styles.button}
|
|
54
|
+
icon={<MinusMIcon />}
|
|
55
|
+
aria-label='уменьшить'
|
|
56
|
+
onMouseDown={preventDefault}
|
|
57
|
+
onClick={onDecrement}
|
|
58
|
+
dataTestId={getDataTestId(dataTestId, 'decrement-button')}
|
|
59
|
+
view='secondary'
|
|
60
|
+
/>
|
|
61
|
+
<div className={cn(styles.separator, colorStyles[colors].separator)} />
|
|
62
|
+
<IconButton
|
|
63
|
+
colors={colors}
|
|
64
|
+
disabled={incButtonDisabled}
|
|
65
|
+
className={styles.button}
|
|
66
|
+
icon={<PlusMediumMIcon />}
|
|
67
|
+
aria-label='увеличить'
|
|
68
|
+
onMouseDown={preventDefault}
|
|
69
|
+
onClick={onIncrement}
|
|
70
|
+
dataTestId={getDataTestId(dataTestId, 'increment-button')}
|
|
71
|
+
view='secondary'
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@import '@alfalab/core-components-themes/src/default.css';
|
|
2
|
+
|
|
3
|
+
.component {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-flow: row nowrap;
|
|
6
|
+
align-items: center;
|
|
7
|
+
border-radius: 5px;
|
|
8
|
+
transition: background-color 0.2s ease;
|
|
9
|
+
overflow: visible;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.separator {
|
|
13
|
+
height: 18px;
|
|
14
|
+
width: 1px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.button {
|
|
18
|
+
width: 40px;
|
|
19
|
+
height: 40px;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Component';
|