@snack-uikit/fields 0.12.2 → 0.13.0
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/CHANGELOG.md +20 -0
- package/README.md +29 -2
- package/dist/components/FieldStepper/FieldStepper.d.ts +21 -0
- package/dist/components/FieldStepper/FieldStepper.js +113 -0
- package/dist/components/FieldStepper/index.d.ts +1 -0
- package/dist/components/FieldStepper/index.js +1 -0
- package/dist/components/FieldStepper/styles.module.css +10 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/package.json +6 -4
- package/src/components/FieldStepper/FieldStepper.tsx +268 -0
- package/src/components/FieldStepper/index.ts +1 -0
- package/src/components/FieldStepper/styles.module.scss +13 -0
- package/src/components/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 0.13.0 (2024-02-08)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **FF-4211:** add field-stepper ([168e84e](https://github.com/cloud-ru-tech/snack-uikit/commit/168e84ec225c960c441cc73b85ceb53fc68b0234))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## 0.12.3 (2024-02-07)
|
|
18
|
+
|
|
19
|
+
### Only dependencies have been changed
|
|
20
|
+
* [@snack-uikit/calendar@0.7.1](https://github.com/cloud-ru-tech/snack-uikit/blob/master/packages/calendar/CHANGELOG.md)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## 0.12.2 (2024-02-06)
|
|
7
27
|
|
|
8
28
|
### Only dependencies have been changed
|
package/README.md
CHANGED
|
@@ -191,7 +191,7 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
191
191
|
| id | `string` | - | Значение html-атрибута id |
|
|
192
192
|
| name | `string` | - | Значение html-атрибута name |
|
|
193
193
|
| placeholder | `string` | - | Значение плейсхолдера |
|
|
194
|
-
| maxLength | `number` | - |
|
|
194
|
+
| maxLength | `number` | - | Максимальная длина вводимого значения |
|
|
195
195
|
| onFocus | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки получения фокуса |
|
|
196
196
|
| onBlur | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки потери фокуса |
|
|
197
197
|
| className | `string` | - | CSS-класс |
|
|
@@ -251,7 +251,7 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
251
251
|
| id | `string` | - | Значение html-атрибута id |
|
|
252
252
|
| name | `string` | - | Значение html-атрибута name |
|
|
253
253
|
| placeholder | `string` | - | Значение плейсхолдера |
|
|
254
|
-
| maxLength | `number` | - |
|
|
254
|
+
| maxLength | `number` | - | Максимальная длина вводимого значения |
|
|
255
255
|
| onFocus | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки получения фокуса |
|
|
256
256
|
| onBlur | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки потери фокуса |
|
|
257
257
|
| className | `string` | - | CSS-класс |
|
|
@@ -333,6 +333,33 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
333
333
|
| ref | `Ref<HTMLInputElement>` | - | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom |
|
|
334
334
|
| key | `Key` | - | |
|
|
335
335
|
| getSelectedItemsText | `(amount: number) => string` | - | Колбек формирования текста |
|
|
336
|
+
## FieldStepper
|
|
337
|
+
### Props
|
|
338
|
+
| name | type | default value | description |
|
|
339
|
+
|------|------|---------------|-------------|
|
|
340
|
+
| value | `number` | - | Значение поля |
|
|
341
|
+
| onChange | `(value: number, e?: ChangeEvent<HTMLInputElement>) => void` | - | Колбек смены значения |
|
|
342
|
+
| step | `number` | 1 | Шаг поля |
|
|
343
|
+
| allowMoreThanLimits | `boolean` | true | Можно ли вводить c клавиатуры числа, выходящие за пределы min/max |
|
|
344
|
+
| disabled | `boolean` | - | Является ли поле деактивированным |
|
|
345
|
+
| readonly | `boolean` | - | Является ли поле доступным только для чтения |
|
|
346
|
+
| id | `string` | - | Значение html-атрибута id |
|
|
347
|
+
| name | `string` | - | Значение html-атрибута name |
|
|
348
|
+
| onFocus | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки получения фокуса |
|
|
349
|
+
| onBlur | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки потери фокуса |
|
|
350
|
+
| min | `number` | - | Минимальное значение поля |
|
|
351
|
+
| max | `number` | - | Максимальное значение поля |
|
|
352
|
+
| className | `string` | - | CSS-класс |
|
|
353
|
+
| label | `string` | - | Лейбл |
|
|
354
|
+
| labelTooltip | `string` | - | Всплывающая подсказка лейбла |
|
|
355
|
+
| required | `boolean` | - | Является ли поле обязательным |
|
|
356
|
+
| size | enum Size: `"s"`, `"m"`, `"l"` | SIZE.S | Размер |
|
|
357
|
+
| labelTooltipPlacement | enum Placement: `"left"`, `"left-start"`, `"left-end"`, `"right"`, `"right-start"`, `"right-end"`, `"top"`, `"top-start"`, `"top-end"`, `"bottom"`, `"bottom-start"`, `"bottom-end"` | top | Расположение подсказки лейбла |
|
|
358
|
+
| hint | `string` | - | Подсказка внизу |
|
|
359
|
+
| validationState | enum ValidationState: `"default"`, `"error"`, `"warning"`, `"success"` | default | Состояние валидации |
|
|
360
|
+
| showHintIcon | `boolean` | - | Отображать иконку подсказки |
|
|
361
|
+
| ref | `Ref<HTMLInputElement>` | - | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom |
|
|
362
|
+
| key | `Key` | - | |
|
|
336
363
|
|
|
337
364
|
|
|
338
365
|
[//]: DOCUMENTATION_SECTION_END
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ChangeEvent } from 'react';
|
|
2
|
+
import { InputPrivateProps } from '@snack-uikit/input-private';
|
|
3
|
+
import { WithSupportProps } from '@snack-uikit/utils';
|
|
4
|
+
import { FieldDecoratorProps } from '../FieldDecorator';
|
|
5
|
+
type InputProps = Pick<InputPrivateProps, 'id' | 'name' | 'disabled' | 'readonly' | 'onFocus' | 'onBlur' | 'min' | 'max'>;
|
|
6
|
+
type WrapperProps = Pick<FieldDecoratorProps, 'className' | 'label' | 'labelTooltip' | 'required' | 'hint' | 'showHintIcon' | 'size' | 'validationState' | 'labelTooltipPlacement'>;
|
|
7
|
+
type FieldStepperOwnProps = {
|
|
8
|
+
/** Значение поля */
|
|
9
|
+
value?: number;
|
|
10
|
+
/** Колбек смены значения */
|
|
11
|
+
onChange?(value: number, e?: ChangeEvent<HTMLInputElement>): void;
|
|
12
|
+
/** Шаг поля */
|
|
13
|
+
step?: number;
|
|
14
|
+
/** Можно ли вводить c клавиатуры числа, выходящие за пределы min/max */
|
|
15
|
+
allowMoreThanLimits?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type FieldStepperProps = WithSupportProps<FieldStepperOwnProps & InputProps & WrapperProps>;
|
|
18
|
+
export declare const FieldStepper: import("react").ForwardRefExoticComponent<{
|
|
19
|
+
'data-test-id'?: string | undefined;
|
|
20
|
+
} & import("react").AriaAttributes & FieldStepperOwnProps & InputProps & WrapperProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import mergeRefs from 'merge-refs';
|
|
14
|
+
import { forwardRef, useEffect, useRef, useState, } from 'react';
|
|
15
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
16
|
+
import { ButtonFunction } from '@snack-uikit/button';
|
|
17
|
+
import { MinusSVG, PlusSVG } from '@snack-uikit/icons';
|
|
18
|
+
import { InputPrivate, SIZE } from '@snack-uikit/input-private';
|
|
19
|
+
import { useLocale } from '@snack-uikit/locale';
|
|
20
|
+
import { Tooltip } from '@snack-uikit/tooltip';
|
|
21
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
22
|
+
import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
|
|
23
|
+
import { FieldContainerPrivate } from '../../helperComponents';
|
|
24
|
+
import { FieldDecorator } from '../FieldDecorator';
|
|
25
|
+
import styles from './styles.module.css';
|
|
26
|
+
const TOOLTIP_TIMEOUT = 2000;
|
|
27
|
+
export const FieldStepper = forwardRef((_a, ref) => {
|
|
28
|
+
var { id, name, value: valueProp, min, max, step = 1, disabled = false, readonly = false, allowMoreThanLimits = true, onChange: onChangeProp, onFocus, onBlur, className, label, labelTooltip, labelTooltipPlacement, required = false, hint, showHintIcon, size = SIZE.S, validationState = VALIDATION_STATE.Default } = _a, rest = __rest(_a, ["id", "name", "value", "min", "max", "step", "disabled", "readonly", "allowMoreThanLimits", "onChange", "onFocus", "onBlur", "className", "label", "labelTooltip", "labelTooltipPlacement", "required", "hint", "showHintIcon", "size", "validationState"]);
|
|
29
|
+
const [locales] = useLocale('Fields');
|
|
30
|
+
const [value, setValue] = useUncontrolledProp(valueProp, 0, onChangeProp);
|
|
31
|
+
const [tooltipOpen, setTooltipOpen] = useState(false);
|
|
32
|
+
const [tooltip, setTooltip] = useState('');
|
|
33
|
+
const timerRef = useRef();
|
|
34
|
+
const inputRef = useRef(null);
|
|
35
|
+
const minusButtonRef = useRef(null);
|
|
36
|
+
const plusButtonRef = useRef(null);
|
|
37
|
+
const isMinusButtonDisabled = value === min || readonly || disabled;
|
|
38
|
+
const isPlusButtonDisabled = value === max || readonly || disabled;
|
|
39
|
+
const adjustValue = (value) => {
|
|
40
|
+
setValue(value);
|
|
41
|
+
setTooltipOpen(true);
|
|
42
|
+
timerRef.current = setTimeout(() => {
|
|
43
|
+
setTooltipOpen(false);
|
|
44
|
+
setTooltip('');
|
|
45
|
+
}, TOOLTIP_TIMEOUT);
|
|
46
|
+
};
|
|
47
|
+
useEffect(() => () => {
|
|
48
|
+
clearTimeout(timerRef.current);
|
|
49
|
+
}, []);
|
|
50
|
+
const handleInputBlur = (event) => {
|
|
51
|
+
if (!allowMoreThanLimits) {
|
|
52
|
+
if (max !== undefined && value > max) {
|
|
53
|
+
setTooltip(`${locales.limitTooltip.max}${max}`);
|
|
54
|
+
adjustValue(max);
|
|
55
|
+
}
|
|
56
|
+
if (min !== undefined && value < min) {
|
|
57
|
+
setTooltip(`${locales.limitTooltip.min}${min}`);
|
|
58
|
+
adjustValue(min);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
62
|
+
};
|
|
63
|
+
const handleInputFocus = (event) => {
|
|
64
|
+
clearTimeout(timerRef.current);
|
|
65
|
+
setTooltipOpen(false);
|
|
66
|
+
setTooltip('');
|
|
67
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
68
|
+
};
|
|
69
|
+
const handleInputChange = (value, event) => setValue(Number(value), event);
|
|
70
|
+
const handleInputKeyDown = event => {
|
|
71
|
+
var _a, _b;
|
|
72
|
+
if (readonly || disabled) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (event.key === 'ArrowRight' && !isPlusButtonDisabled) {
|
|
76
|
+
(_a = plusButtonRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
77
|
+
}
|
|
78
|
+
if (event.key === 'ArrowLeft' && !isMinusButtonDisabled) {
|
|
79
|
+
(_b = minusButtonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const handleMinusButtonClick = (e) => {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
setValue(min !== undefined ? Math.max(min, value - step) : value - step);
|
|
86
|
+
};
|
|
87
|
+
const handlePlusButtonClick = (e) => {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
setValue(max !== undefined ? Math.min(max, value + step) : value + step);
|
|
91
|
+
};
|
|
92
|
+
const handleMinusButtonKeyDown = event => {
|
|
93
|
+
var _a;
|
|
94
|
+
if (readonly || disabled) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (event.key === 'ArrowRight') {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const handlePlusButtonKeyDown = event => {
|
|
103
|
+
var _a;
|
|
104
|
+
if (readonly || disabled) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (event.key === 'ArrowLeft') {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return (_jsx(FieldDecorator, Object.assign({ className: className, label: label, labelTooltip: labelTooltip, labelTooltipPlacement: labelTooltipPlacement, labelFor: id, required: required, hint: hint, disabled: disabled, readonly: readonly, showHintIcon: showHintIcon, size: size, validationState: validationState }, extractSupportProps(rest), { children: _jsx(Tooltip, { tip: tooltip, open: allowMoreThanLimits ? false : tooltipOpen, "data-test-id": 'field-stepper__limit-tooltip', children: _jsx(FieldContainerPrivate, { size: size, validationState: validationState, disabled: disabled, readonly: readonly, variant: CONTAINER_VARIANT.SingleLine, inputRef: inputRef, prefix: _jsx(ButtonFunction, { tabIndex: -1, ref: minusButtonRef, size: 'xs', className: styles.button, icon: _jsx(MinusSVG, {}), onClick: handleMinusButtonClick, onKeyDown: handleMinusButtonKeyDown, disabled: isMinusButtonDisabled, "data-test-id": 'field-stepper__minus-button' }), postfix: _jsx(ButtonFunction, { ref: plusButtonRef, tabIndex: -1, size: 'xs', className: styles.button, icon: _jsx(PlusSVG, {}), onClick: handlePlusButtonClick, onKeyDown: handlePlusButtonKeyDown, disabled: isPlusButtonDisabled, "data-test-id": 'field-stepper__plus-button' }), children: _jsx(InputPrivate, { ref: mergeRefs(ref, inputRef), className: styles.stepper, "data-size": size, value: String(value), tabIndex: 0, onKeyDown: handleInputKeyDown, onChange: handleInputChange, onBlur: handleInputBlur, onFocus: handleInputFocus, disabled: disabled, readonly: readonly, type: 'number', id: id, name: name, min: min, max: max, "data-test-id": 'field-stepper__input' }) }) }) })));
|
|
113
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FieldStepper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FieldStepper';
|
package/dist/components/index.js
CHANGED
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Fields",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.13.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
"license": "Apache-2.0",
|
|
33
33
|
"scripts": {},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@snack-uikit/
|
|
35
|
+
"@snack-uikit/button": "0.16.0",
|
|
36
|
+
"@snack-uikit/calendar": "0.7.2",
|
|
36
37
|
"@snack-uikit/droplist": "0.13.3",
|
|
37
38
|
"@snack-uikit/icons": "0.20.1",
|
|
38
|
-
"@snack-uikit/input-private": "3.0
|
|
39
|
+
"@snack-uikit/input-private": "3.1.0",
|
|
40
|
+
"@snack-uikit/locale": "0.2.0",
|
|
39
41
|
"@snack-uikit/scroll": "0.5.0",
|
|
40
42
|
"@snack-uikit/tooltip": "0.11.1",
|
|
41
43
|
"@snack-uikit/truncate-string": "0.4.7",
|
|
@@ -49,5 +51,5 @@
|
|
|
49
51
|
"devDependencies": {
|
|
50
52
|
"@types/merge-refs": "1.0.0"
|
|
51
53
|
},
|
|
52
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "eb20092ecd05743f00831dfa51a3f5db2416be8f"
|
|
53
55
|
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import mergeRefs from 'merge-refs';
|
|
2
|
+
import {
|
|
3
|
+
ChangeEvent,
|
|
4
|
+
FocusEvent,
|
|
5
|
+
forwardRef,
|
|
6
|
+
KeyboardEventHandler,
|
|
7
|
+
MouseEvent,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
13
|
+
|
|
14
|
+
import { ButtonFunction } from '@snack-uikit/button';
|
|
15
|
+
import { MinusSVG, PlusSVG } from '@snack-uikit/icons';
|
|
16
|
+
import { InputPrivate, InputPrivateProps, SIZE } from '@snack-uikit/input-private';
|
|
17
|
+
import { useLocale } from '@snack-uikit/locale';
|
|
18
|
+
import { Tooltip } from '@snack-uikit/tooltip';
|
|
19
|
+
import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
|
|
20
|
+
|
|
21
|
+
import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
|
|
22
|
+
import { FieldContainerPrivate } from '../../helperComponents';
|
|
23
|
+
import { FieldDecorator, FieldDecoratorProps } from '../FieldDecorator';
|
|
24
|
+
import styles from './styles.module.scss';
|
|
25
|
+
|
|
26
|
+
type InputProps = Pick<
|
|
27
|
+
InputPrivateProps,
|
|
28
|
+
'id' | 'name' | 'disabled' | 'readonly' | 'onFocus' | 'onBlur' | 'min' | 'max'
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
type WrapperProps = Pick<
|
|
32
|
+
FieldDecoratorProps,
|
|
33
|
+
| 'className'
|
|
34
|
+
| 'label'
|
|
35
|
+
| 'labelTooltip'
|
|
36
|
+
| 'required'
|
|
37
|
+
| 'hint'
|
|
38
|
+
| 'showHintIcon'
|
|
39
|
+
| 'size'
|
|
40
|
+
| 'validationState'
|
|
41
|
+
| 'labelTooltipPlacement'
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
type FieldStepperOwnProps = {
|
|
45
|
+
/** Значение поля */
|
|
46
|
+
value?: number;
|
|
47
|
+
/** Колбек смены значения */
|
|
48
|
+
onChange?(value: number, e?: ChangeEvent<HTMLInputElement>): void;
|
|
49
|
+
/** Шаг поля */
|
|
50
|
+
step?: number;
|
|
51
|
+
/** Можно ли вводить c клавиатуры числа, выходящие за пределы min/max */
|
|
52
|
+
allowMoreThanLimits?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type FieldStepperProps = WithSupportProps<FieldStepperOwnProps & InputProps & WrapperProps>;
|
|
56
|
+
|
|
57
|
+
const TOOLTIP_TIMEOUT = 2000;
|
|
58
|
+
|
|
59
|
+
export const FieldStepper = forwardRef<HTMLInputElement, FieldStepperProps>(
|
|
60
|
+
(
|
|
61
|
+
{
|
|
62
|
+
id,
|
|
63
|
+
name,
|
|
64
|
+
value: valueProp,
|
|
65
|
+
min,
|
|
66
|
+
max,
|
|
67
|
+
step = 1,
|
|
68
|
+
disabled = false,
|
|
69
|
+
readonly = false,
|
|
70
|
+
allowMoreThanLimits = true,
|
|
71
|
+
onChange: onChangeProp,
|
|
72
|
+
onFocus,
|
|
73
|
+
onBlur,
|
|
74
|
+
className,
|
|
75
|
+
label,
|
|
76
|
+
labelTooltip,
|
|
77
|
+
labelTooltipPlacement,
|
|
78
|
+
required = false,
|
|
79
|
+
hint,
|
|
80
|
+
showHintIcon,
|
|
81
|
+
size = SIZE.S,
|
|
82
|
+
validationState = VALIDATION_STATE.Default,
|
|
83
|
+
...rest
|
|
84
|
+
},
|
|
85
|
+
ref,
|
|
86
|
+
) => {
|
|
87
|
+
const [locales] = useLocale('Fields');
|
|
88
|
+
const [value, setValue] = useUncontrolledProp(valueProp, 0, onChangeProp);
|
|
89
|
+
const [tooltipOpen, setTooltipOpen] = useState(false);
|
|
90
|
+
const [tooltip, setTooltip] = useState('');
|
|
91
|
+
const timerRef = useRef<ReturnType<typeof setTimeout>>();
|
|
92
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
93
|
+
const minusButtonRef = useRef<HTMLButtonElement>(null);
|
|
94
|
+
const plusButtonRef = useRef<HTMLButtonElement>(null);
|
|
95
|
+
const isMinusButtonDisabled = value === min || readonly || disabled;
|
|
96
|
+
const isPlusButtonDisabled = value === max || readonly || disabled;
|
|
97
|
+
|
|
98
|
+
const adjustValue = (value: number) => {
|
|
99
|
+
setValue(value);
|
|
100
|
+
setTooltipOpen(true);
|
|
101
|
+
|
|
102
|
+
timerRef.current = setTimeout(() => {
|
|
103
|
+
setTooltipOpen(false);
|
|
104
|
+
setTooltip('');
|
|
105
|
+
}, TOOLTIP_TIMEOUT);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
useEffect(
|
|
109
|
+
() => () => {
|
|
110
|
+
clearTimeout(timerRef.current);
|
|
111
|
+
},
|
|
112
|
+
[],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const handleInputBlur = (event: FocusEvent<HTMLInputElement>) => {
|
|
116
|
+
if (!allowMoreThanLimits) {
|
|
117
|
+
if (max !== undefined && value > max) {
|
|
118
|
+
setTooltip(`${locales.limitTooltip.max}${max}`);
|
|
119
|
+
adjustValue(max);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (min !== undefined && value < min) {
|
|
123
|
+
setTooltip(`${locales.limitTooltip.min}${min}`);
|
|
124
|
+
adjustValue(min);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onBlur?.(event);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const handleInputFocus = (event: FocusEvent<HTMLInputElement>) => {
|
|
132
|
+
clearTimeout(timerRef.current);
|
|
133
|
+
setTooltipOpen(false);
|
|
134
|
+
setTooltip('');
|
|
135
|
+
|
|
136
|
+
onFocus?.(event);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const handleInputChange = (value: string, event: ChangeEvent<HTMLInputElement>) => setValue(Number(value), event);
|
|
140
|
+
|
|
141
|
+
const handleInputKeyDown: KeyboardEventHandler<HTMLInputElement> = event => {
|
|
142
|
+
if (readonly || disabled) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (event.key === 'ArrowRight' && !isPlusButtonDisabled) {
|
|
147
|
+
plusButtonRef.current?.focus();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (event.key === 'ArrowLeft' && !isMinusButtonDisabled) {
|
|
151
|
+
minusButtonRef.current?.focus();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const handleMinusButtonClick = (e: MouseEvent) => {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
e.stopPropagation();
|
|
158
|
+
setValue(min !== undefined ? Math.max(min, value - step) : value - step);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const handlePlusButtonClick = (e: MouseEvent) => {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
e.stopPropagation();
|
|
164
|
+
setValue(max !== undefined ? Math.min(max, value + step) : value + step);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleMinusButtonKeyDown: KeyboardEventHandler<HTMLInputElement> = event => {
|
|
168
|
+
if (readonly || disabled) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (event.key === 'ArrowRight') {
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
inputRef.current?.focus();
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const handlePlusButtonKeyDown: KeyboardEventHandler<HTMLInputElement> = event => {
|
|
179
|
+
if (readonly || disabled) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (event.key === 'ArrowLeft') {
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
inputRef.current?.focus();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<FieldDecorator
|
|
191
|
+
className={className}
|
|
192
|
+
label={label}
|
|
193
|
+
labelTooltip={labelTooltip}
|
|
194
|
+
labelTooltipPlacement={labelTooltipPlacement}
|
|
195
|
+
labelFor={id}
|
|
196
|
+
required={required}
|
|
197
|
+
hint={hint}
|
|
198
|
+
disabled={disabled}
|
|
199
|
+
readonly={readonly}
|
|
200
|
+
showHintIcon={showHintIcon}
|
|
201
|
+
size={size}
|
|
202
|
+
validationState={validationState}
|
|
203
|
+
{...extractSupportProps(rest)}
|
|
204
|
+
>
|
|
205
|
+
<Tooltip
|
|
206
|
+
tip={tooltip}
|
|
207
|
+
open={allowMoreThanLimits ? false : tooltipOpen}
|
|
208
|
+
data-test-id='field-stepper__limit-tooltip'
|
|
209
|
+
>
|
|
210
|
+
<FieldContainerPrivate
|
|
211
|
+
size={size}
|
|
212
|
+
validationState={validationState}
|
|
213
|
+
disabled={disabled}
|
|
214
|
+
readonly={readonly}
|
|
215
|
+
variant={CONTAINER_VARIANT.SingleLine}
|
|
216
|
+
inputRef={inputRef}
|
|
217
|
+
prefix={
|
|
218
|
+
<ButtonFunction
|
|
219
|
+
tabIndex={-1}
|
|
220
|
+
ref={minusButtonRef}
|
|
221
|
+
size='xs'
|
|
222
|
+
className={styles.button}
|
|
223
|
+
icon={<MinusSVG />}
|
|
224
|
+
onClick={handleMinusButtonClick}
|
|
225
|
+
onKeyDown={handleMinusButtonKeyDown}
|
|
226
|
+
disabled={isMinusButtonDisabled}
|
|
227
|
+
data-test-id='field-stepper__minus-button'
|
|
228
|
+
/>
|
|
229
|
+
}
|
|
230
|
+
postfix={
|
|
231
|
+
<ButtonFunction
|
|
232
|
+
ref={plusButtonRef}
|
|
233
|
+
tabIndex={-1}
|
|
234
|
+
size='xs'
|
|
235
|
+
className={styles.button}
|
|
236
|
+
icon={<PlusSVG />}
|
|
237
|
+
onClick={handlePlusButtonClick}
|
|
238
|
+
onKeyDown={handlePlusButtonKeyDown}
|
|
239
|
+
disabled={isPlusButtonDisabled}
|
|
240
|
+
data-test-id='field-stepper__plus-button'
|
|
241
|
+
/>
|
|
242
|
+
}
|
|
243
|
+
>
|
|
244
|
+
<InputPrivate
|
|
245
|
+
ref={mergeRefs(ref, inputRef)}
|
|
246
|
+
className={styles.stepper}
|
|
247
|
+
data-size={size}
|
|
248
|
+
value={String(value)}
|
|
249
|
+
tabIndex={0}
|
|
250
|
+
onKeyDown={handleInputKeyDown}
|
|
251
|
+
onChange={handleInputChange}
|
|
252
|
+
onBlur={handleInputBlur}
|
|
253
|
+
onFocus={handleInputFocus}
|
|
254
|
+
disabled={disabled}
|
|
255
|
+
readonly={readonly}
|
|
256
|
+
type='number'
|
|
257
|
+
id={id}
|
|
258
|
+
name={name}
|
|
259
|
+
min={min}
|
|
260
|
+
max={max}
|
|
261
|
+
data-test-id='field-stepper__input'
|
|
262
|
+
/>
|
|
263
|
+
</FieldContainerPrivate>
|
|
264
|
+
</Tooltip>
|
|
265
|
+
</FieldDecorator>
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FieldStepper';
|
package/src/components/index.ts
CHANGED