@snack-uikit/fields 0.14.2 → 0.15.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 +16 -0
- package/README.md +15 -14
- package/dist/components/FieldDecorator/Header.js +1 -1
- package/dist/components/FieldDecorator/styles.module.css +7 -1
- package/dist/components/FieldSelect/FieldSelect.d.ts +1 -7
- package/dist/components/FieldSelect/FieldSelect.js +9 -21
- package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +17 -0
- package/dist/components/FieldSelect/FieldSelectMultiple.js +118 -0
- package/dist/components/FieldSelect/FieldSelectSingle.d.ts +9 -28
- package/dist/components/FieldSelect/FieldSelectSingle.js +69 -55
- package/dist/components/FieldSelect/hooks.d.ts +30 -0
- package/dist/components/FieldSelect/hooks.js +72 -0
- package/dist/components/FieldSelect/index.d.ts +2 -1
- package/dist/components/FieldSelect/index.js +1 -1
- package/dist/components/FieldSelect/styles.module.css +129 -27
- package/dist/components/FieldSelect/types.d.ts +42 -37
- package/dist/components/FieldSelect/utils.d.ts +19 -0
- package/dist/components/FieldSelect/utils.js +112 -0
- package/dist/helperComponents/FieldContainerPrivate/styles.module.css +30 -6
- package/package.json +5 -3
- package/src/components/FieldDecorator/Header.tsx +6 -1
- package/src/components/FieldDecorator/styles.module.scss +38 -30
- package/src/components/FieldSelect/FieldSelect.tsx +13 -21
- package/src/components/FieldSelect/FieldSelectMultiple.tsx +255 -0
- package/src/components/FieldSelect/FieldSelectSingle.tsx +159 -99
- package/src/components/FieldSelect/hooks.ts +125 -0
- package/src/components/FieldSelect/index.ts +12 -1
- package/src/components/FieldSelect/styles.module.scss +71 -31
- package/src/components/FieldSelect/types.ts +55 -40
- package/src/components/FieldSelect/utils.ts +163 -0
- package/src/helperComponents/FieldContainerPrivate/FieldContainerPrivate.tsx +2 -0
- package/src/helperComponents/FieldContainerPrivate/styles.module.scss +32 -11
- package/dist/components/FieldSelect/FieldSelectBase.d.ts +0 -31
- package/dist/components/FieldSelect/FieldSelectBase.js +0 -51
- package/dist/components/FieldSelect/FieldSelectMulti.d.ts +0 -37
- package/dist/components/FieldSelect/FieldSelectMulti.js +0 -89
- package/dist/components/FieldSelect/constants.d.ts +0 -7
- package/dist/components/FieldSelect/constants.js +0 -6
- package/dist/components/FieldSelect/helpers/getArrowIcon.d.ts +0 -8
- package/dist/components/FieldSelect/helpers/getArrowIcon.js +0 -8
- package/dist/components/FieldSelect/helpers/getDisplayedValue.d.ts +0 -10
- package/dist/components/FieldSelect/helpers/getDisplayedValue.js +0 -12
- package/dist/components/FieldSelect/helpers/index.d.ts +0 -2
- package/dist/components/FieldSelect/helpers/index.js +0 -2
- package/dist/components/FieldSelect/hooks/index.d.ts +0 -3
- package/dist/components/FieldSelect/hooks/index.js +0 -3
- package/dist/components/FieldSelect/hooks/useFilteredOptions.d.ts +0 -7
- package/dist/components/FieldSelect/hooks/useFilteredOptions.js +0 -6
- package/dist/components/FieldSelect/hooks/useList.d.ts +0 -37
- package/dist/components/FieldSelect/hooks/useList.js +0 -52
- package/dist/components/FieldSelect/hooks/useListNavigation.d.ts +0 -26
- package/dist/components/FieldSelect/hooks/useListNavigation.js +0 -48
- package/src/components/FieldSelect/FieldSelectBase.tsx +0 -222
- package/src/components/FieldSelect/FieldSelectMulti.tsx +0 -163
- package/src/components/FieldSelect/constants.ts +0 -9
- package/src/components/FieldSelect/helpers/getArrowIcon.ts +0 -9
- package/src/components/FieldSelect/helpers/getDisplayedValue.ts +0 -25
- package/src/components/FieldSelect/helpers/index.ts +0 -2
- package/src/components/FieldSelect/hooks/index.ts +0 -3
- package/src/components/FieldSelect/hooks/useFilteredOptions.ts +0 -23
- package/src/components/FieldSelect/hooks/useList.ts +0 -87
- package/src/components/FieldSelect/hooks/useListNavigation.ts +0 -81
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
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.15.0 (2024-02-23)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **FF-4221:** fix remove disabled & checked items by backspace ([4f0b945](https://github.com/cloud-ru-tech/snack-uikit/commit/4f0b945f4b1878923e5231298474393d6ad412d5))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
* **FF-4221:** replace list package; FieldMultiple select as TagSelect ([10333e9](https://github.com/cloud-ru-tech/snack-uikit/commit/10333e9ca00cb445f7617148ee82c2acfea678e6))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## 0.14.2 (2024-02-22)
|
|
7
23
|
|
|
8
24
|
|
package/README.md
CHANGED
|
@@ -302,23 +302,15 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
302
302
|
### Props
|
|
303
303
|
| name | type | default value | description |
|
|
304
304
|
|------|------|---------------|-------------|
|
|
305
|
-
| options* | `
|
|
306
|
-
| selectionMode | "single" \| "multi" | single | |
|
|
307
|
-
| open | `boolean` | - | Открыт ли выпадающий список |
|
|
308
|
-
| onOpenChange | `(value: boolean) => void` | - | Колбек открытия выпадающего списка |
|
|
309
|
-
| searchable | `boolean` | - | Можно ли искать опции внутри списка |
|
|
310
|
-
| showCopyButton | `boolean` | - | Отображение кнопки Копировать для поля (актуально только для `readonly = true`) |
|
|
311
|
-
| showClearButton | `boolean` | true | Отображение кнопки очистки поля |
|
|
312
|
-
| prefixIcon | `ReactElement<any, string \| JSXElementConstructor<any>>` | - | Иконка-префикс для поля |
|
|
313
|
-
| noDataText | `string` | - | Текст отсутствия доступных значений |
|
|
314
|
-
| locale | `Locale` | - | Текущая локаль |
|
|
305
|
+
| options* | `OptionProps[]` | - | |
|
|
315
306
|
| disabled | `boolean` | false | Является ли поле деактивированным |
|
|
316
|
-
| readonly | `boolean` | false | Является ли поле доступным только для чтения |
|
|
307
|
+
| readonly | `boolean` | false false | Является ли поле доступным только для чтения |
|
|
317
308
|
| id | `string` | - | Значение html-атрибута id |
|
|
318
309
|
| name | `string` | - | Значение html-атрибута name |
|
|
319
310
|
| placeholder | `string` | - | Значение плейсхолдера |
|
|
320
311
|
| onFocus | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки получения фокуса |
|
|
321
312
|
| onBlur | `FocusEventHandler<HTMLInputElement>` | - | Колбек обработки потери фокуса |
|
|
313
|
+
| onKeyDown | `KeyboardEventHandler<HTMLInputElement>` | - | Колбек обработки нажатия клавиши клавиатуры |
|
|
322
314
|
| className | `string` | - | CSS-класс |
|
|
323
315
|
| label | `string` | - | Лейбл |
|
|
324
316
|
| labelTooltip | `string` | - | Всплывающая подсказка лейбла |
|
|
@@ -328,11 +320,20 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
328
320
|
| hint | `string` | - | Подсказка внизу |
|
|
329
321
|
| validationState | enum ValidationState: `"default"`, `"error"`, `"warning"`, `"success"` | - | Состояние валидации |
|
|
330
322
|
| showHintIcon | `boolean` | - | Отображать иконку подсказки |
|
|
331
|
-
|
|
|
332
|
-
|
|
|
323
|
+
| loading | `boolean` | - | |
|
|
324
|
+
| value | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Controlled состояние |
|
|
325
|
+
| onChange | `((value: any) => void) \| ((value: any) => void)` | - | Controlled обработчик измения состояния |
|
|
326
|
+
| defaultValue | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Начальное состояние |
|
|
327
|
+
| searchable | `boolean` | - | |
|
|
328
|
+
| showCopyButton | `boolean` | - | Отображение кнопки Копировать для поля (актуально только для `readonly = true`) |
|
|
329
|
+
| showClearButton | `boolean` | true | Отображение кнопки очистки поля |
|
|
330
|
+
| prefixIcon | `ReactElement<any, string \| JSXElementConstructor<any>>` | - | Иконка-префикс для поля |
|
|
331
|
+
| footer | `ReactNode` | - | |
|
|
332
|
+
| search | `SearchState` | - | |
|
|
333
|
+
| autocomplete | `boolean` | - | |
|
|
334
|
+
| selection | "single" \| "multiple" | - | |
|
|
333
335
|
| 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
336
|
| key | `Key` | - | |
|
|
335
|
-
| getSelectedItemsText | `(amount: number) => string` | - | Колбек формирования текста |
|
|
336
337
|
## FieldStepper
|
|
337
338
|
### Props
|
|
338
339
|
| name | type | default value | description |
|
|
@@ -4,5 +4,5 @@ import { Tooltip } from '@snack-uikit/tooltip';
|
|
|
4
4
|
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
5
5
|
import styles from './styles.module.css';
|
|
6
6
|
export function Header({ label = '', labelTooltip, labelFor, size, required = false, labelTooltipPlacement = 'top', }) {
|
|
7
|
-
return (_jsx("span", { className: styles.header, "data-size": size, children: label && (_jsxs("span", { className: styles.labelLayout, children: [_jsx("label", { className: styles.label, htmlFor: labelFor, "data-test-id": 'field-decorator__label', children: _jsx(TruncateString, { text: label }) }), required && _jsx("span", { "data-test-id": 'field-decorator__required-sign', children: "*" }), labelTooltip && (_jsx(Tooltip, { tip: labelTooltip, placement: labelTooltipPlacement, "data-test-id": 'field-decorator__label-tooltip', children: _jsx(QuestionSVG, { size: 16, className: styles.icon, "data-test-id": 'field-decorator__label-tooltip-trigger' }) }))] })) }));
|
|
7
|
+
return (_jsx("span", { className: styles.header, "data-size": size, children: label && (_jsxs("span", { className: styles.labelLayout, children: [_jsx("label", { className: styles.label, htmlFor: labelFor, "data-test-id": 'field-decorator__label', children: _jsx(TruncateString, { text: label }) }), required && _jsx("span", { "data-test-id": 'field-decorator__required-sign', children: "*" }), labelTooltip && (_jsx(Tooltip, { tip: labelTooltip, placement: labelTooltipPlacement, "data-test-id": 'field-decorator__label-tooltip', triggerClassName: styles.labelTooltipTrigger, children: _jsx(QuestionSVG, { size: 16, className: styles.icon, "data-test-id": 'field-decorator__label-tooltip-trigger' }) }))] })) }));
|
|
8
8
|
}
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
.footer{
|
|
59
|
+
gap:var(--space-fields-hint-container-gap, 4px);
|
|
59
60
|
display:flex;
|
|
60
|
-
gap:var(--dimension-050m, 4px);
|
|
61
61
|
justify-content:space-between;
|
|
62
62
|
box-sizing:border-box;
|
|
63
63
|
}
|
|
@@ -178,4 +178,10 @@
|
|
|
178
178
|
}
|
|
179
179
|
.counterCurrentValue[data-limit-exceeded][data-validation=success]{
|
|
180
180
|
color:var(--sys-green-text-light, #67ba6e);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.labelTooltipTrigger{
|
|
184
|
+
display:flex;
|
|
185
|
+
align-items:center;
|
|
186
|
+
height:100%;
|
|
181
187
|
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import {
|
|
3
|
-
import { FieldSelectMultiProps, FieldSelectSingleProps } from './types';
|
|
4
|
-
export type FieldSelectProps = ({
|
|
5
|
-
selectionMode?: typeof SELECTION_MODE.Single;
|
|
6
|
-
} & FieldSelectSingleProps) | ({
|
|
7
|
-
selectionMode: typeof SELECTION_MODE.Multi;
|
|
8
|
-
} & FieldSelectMultiProps);
|
|
2
|
+
import { FieldSelectProps } from './types';
|
|
9
3
|
export declare const FieldSelect: import("react").ForwardRefExoticComponent<FieldSelectProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -1,26 +1,14 @@
|
|
|
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
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
2
|
import { forwardRef } from 'react';
|
|
14
|
-
import {
|
|
15
|
-
import { FieldSelectMulti } from './FieldSelectMulti';
|
|
3
|
+
import { FieldSelectMultiple } from './FieldSelectMultiple';
|
|
16
4
|
import { FieldSelectSingle } from './FieldSelectSingle';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return _jsx(FieldSelectMulti, Object.assign({}, props, { ref: ref }));
|
|
22
|
-
case SELECTION_MODE.Single:
|
|
23
|
-
default:
|
|
24
|
-
return _jsx(FieldSelectSingle, Object.assign({}, props, { ref: ref }));
|
|
5
|
+
import { isFieldSelectMultipleProps, isFieldSelectSingleProps } from './utils';
|
|
6
|
+
export const FieldSelect = forwardRef((props, ref) => {
|
|
7
|
+
if (isFieldSelectMultipleProps(props)) {
|
|
8
|
+
return _jsx(FieldSelectMultiple, Object.assign({}, props, { ref: ref }));
|
|
25
9
|
}
|
|
10
|
+
if (isFieldSelectSingleProps(props)) {
|
|
11
|
+
return _jsx(FieldSelectSingle, Object.assign({}, props, { ref: ref }));
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
26
14
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export declare const FieldSelectMultiple: import("react").ForwardRefExoticComponent<import("./types").InputProps & import("./types").WrapperProps & {
|
|
3
|
+
options: import("./types").OptionProps[];
|
|
4
|
+
loading?: boolean | undefined;
|
|
5
|
+
} & Omit<import("@snack-uikit/list").SelectionMultipleState, "mode"> & {
|
|
6
|
+
'data-test-id'?: string | undefined;
|
|
7
|
+
} & import("react").AriaAttributes & {
|
|
8
|
+
options: import("./types").OptionProps[];
|
|
9
|
+
searchable?: boolean | undefined;
|
|
10
|
+
showCopyButton?: boolean | undefined;
|
|
11
|
+
showClearButton?: boolean | undefined;
|
|
12
|
+
readonly?: boolean | undefined;
|
|
13
|
+
prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
14
|
+
footer?: import("react").ReactNode;
|
|
15
|
+
search?: import("./types").SearchState | undefined;
|
|
16
|
+
autocomplete?: boolean | undefined;
|
|
17
|
+
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,118 @@
|
|
|
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, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
|
+
import cn from 'classnames';
|
|
14
|
+
import mergeRefs from 'merge-refs';
|
|
15
|
+
import { forwardRef, useMemo, useRef, useState } from 'react';
|
|
16
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
17
|
+
import { InputPrivate } from '@snack-uikit/input-private';
|
|
18
|
+
import { Droplist, useFuzzySearch } from '@snack-uikit/list';
|
|
19
|
+
import { Tag } from '@snack-uikit/tag';
|
|
20
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
21
|
+
import { FieldContainerPrivate } from '../../helperComponents';
|
|
22
|
+
import { FieldDecorator } from '../FieldDecorator';
|
|
23
|
+
import { useButtons, useHandleDeleteItem, useHandleOnKeyDown, useSearchInput } from './hooks';
|
|
24
|
+
import styles from './styles.module.css';
|
|
25
|
+
import { extractSelectedMultipleOptions, getArrowIcon, transformOptionsToItems } from './utils';
|
|
26
|
+
const BASE_MIN_WIDTH = 4;
|
|
27
|
+
export const FieldSelectMultiple = forwardRef((_a, ref) => {
|
|
28
|
+
var _b;
|
|
29
|
+
var { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp, loading, disabled = false, readonly = false, searchable = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, label, labelTooltip, labelTooltipPlacement, required = false, hint, showHintIcon, validationState = 'default', footer, search, autocomplete = false, prefixIcon } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "loading", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "label", "labelTooltip", "labelTooltipPlacement", "required", "hint", "showHintIcon", "validationState", "footer", "search", "autocomplete", "prefixIcon"]);
|
|
30
|
+
const localRef = useRef(null);
|
|
31
|
+
const inputPlugRef = useRef(null);
|
|
32
|
+
const contentRef = useRef(null);
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const items = useMemo(() => transformOptionsToItems(options), [options]);
|
|
35
|
+
const [value, setValue] = useUncontrolledProp(valueProp, defaultValue, onChangeProp);
|
|
36
|
+
const selectedOption = useMemo(() => {
|
|
37
|
+
const notSortSelectedOption = extractSelectedMultipleOptions(options, value);
|
|
38
|
+
if (notSortSelectedOption) {
|
|
39
|
+
return notSortSelectedOption.sort((a, b) => {
|
|
40
|
+
if (b.disabled && !a.disabled) {
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
if (a.disabled && !b.disabled) {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [options, value]);
|
|
50
|
+
const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: String(selectedOption !== null && selectedOption !== void 0 ? selectedOption : '') }));
|
|
51
|
+
const onClear = () => {
|
|
52
|
+
var _a;
|
|
53
|
+
setValue(undefined);
|
|
54
|
+
onInputValueChange('');
|
|
55
|
+
(_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
56
|
+
setOpen(true);
|
|
57
|
+
};
|
|
58
|
+
const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
|
|
59
|
+
const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
|
|
60
|
+
readonly,
|
|
61
|
+
size,
|
|
62
|
+
showClearButton: showClearButton && Boolean(value),
|
|
63
|
+
showCopyButton,
|
|
64
|
+
inputRef: localRef,
|
|
65
|
+
onClear,
|
|
66
|
+
valueToCopy: String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.map(option => option.option).join(', ')) !== null && _b !== void 0 ? _b : ''),
|
|
67
|
+
});
|
|
68
|
+
const commonHandleOnKeyDown = useHandleOnKeyDown({
|
|
69
|
+
inputKeyDownNavigationHandler,
|
|
70
|
+
onInputKeyDownProp,
|
|
71
|
+
setOpen,
|
|
72
|
+
});
|
|
73
|
+
const handleItemDelete = useHandleDeleteItem(setValue);
|
|
74
|
+
const handleOnKeyDown = (onKeyDown) => (e) => {
|
|
75
|
+
if (e.code === 'Backspace' && inputValue === '') {
|
|
76
|
+
if ((selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.length) && !selectedOption.slice(-1)[0].disabled) {
|
|
77
|
+
handleItemDelete(selectedOption.pop())();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!open && prevInputValue.current !== inputValue) {
|
|
81
|
+
setOpen(true);
|
|
82
|
+
}
|
|
83
|
+
commonHandleOnKeyDown(onKeyDown)(e);
|
|
84
|
+
};
|
|
85
|
+
const handleOpenChange = (open) => {
|
|
86
|
+
if (!readonly && !disabled && !buttonsRefs.includes(document.activeElement)) {
|
|
87
|
+
setOpen(open);
|
|
88
|
+
if (!open) {
|
|
89
|
+
prevInputValue.current = inputValue;
|
|
90
|
+
}
|
|
91
|
+
if (open) {
|
|
92
|
+
prevInputValue.current = '';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const handleBlur = (e) => {
|
|
97
|
+
var _a;
|
|
98
|
+
if (!open) {
|
|
99
|
+
onInputValueChange('');
|
|
100
|
+
}
|
|
101
|
+
(_a = rest === null || rest === void 0 ? void 0 : rest.onBlur) === null || _a === void 0 ? void 0 : _a.call(rest, e);
|
|
102
|
+
};
|
|
103
|
+
const fuzzySearch = useFuzzySearch(items);
|
|
104
|
+
const result = autocomplete ? items : fuzzySearch(prevInputValue.current !== inputValue ? inputValue : '');
|
|
105
|
+
return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), { required: required, readonly: readonly, label: label, labelTooltip: labelTooltip, labelTooltipPlacement: labelTooltipPlacement, labelFor: id, hint: hint, disabled: disabled, showHintIcon: showHintIcon, size: size, validationState: validationState, children: _jsx(Droplist, { trigger: 'clickAndFocusVisible', placement: 'bottom', "data-test-id": 'field-select__list', items: result, triggerElemRef: localRef, scroll: true, marker: true, footer: footer, selection: {
|
|
106
|
+
mode: 'multiple',
|
|
107
|
+
value: value,
|
|
108
|
+
onChange: setValue,
|
|
109
|
+
}, size: size, open: !disabled && !readonly && open, onOpenChange: handleOpenChange, loading: loading, children: ({ onKeyDown }) => {
|
|
110
|
+
var _a, _b, _c, _d;
|
|
111
|
+
return (_jsx(FieldContainerPrivate, { className: cn(styles.container, styles.tagContainer), validationState: validationState, disabled: disabled, readonly: readonly, focused: open, variant: 'single-line-container', inputRef: localRef, size: size, prefix: prefixIcon, children: _jsxs(_Fragment, { children: [_jsxs("div", { className: styles.contentWrapper, ref: contentRef, children: [selectedOption &&
|
|
112
|
+
selectedOption.map(option => (_jsx(Tag, { size: size === 'l' ? 's' : 'xs', tabIndex: -1, label: String(option.option), onDelete: !option.disabled ? handleItemDelete(option) : undefined }, option.value))), _jsx("div", { className: styles.inputWrapper, style: {
|
|
113
|
+
minWidth: value
|
|
114
|
+
? Math.min((_b = (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : BASE_MIN_WIDTH, (_d = (_c = inputPlugRef.current) === null || _c === void 0 ? void 0 : _c.clientWidth) !== null && _d !== void 0 ? _d : BASE_MIN_WIDTH)
|
|
115
|
+
: '100%',
|
|
116
|
+
}, children: _jsx(InputPrivate, { id: id, name: name, type: 'text', disabled: disabled, placeholder: !selectedOption ? placeholder : undefined, ref: mergeRefs(ref, localRef), onChange: searchable ? onInputValueChange : undefined, value: searchable ? inputValue : '', readonly: !searchable || readonly, "data-test-id": 'field-select__input', onKeyDown: handleOnKeyDown(onKeyDown), onBlur: handleBlur, className: styles.input }) })] }), _jsxs("div", { className: styles.postfix, children: [buttons, _jsx(ArrowIcon, { size: arrowIconSize, className: styles.arrowIcon })] }), _jsx("span", { ref: inputPlugRef, className: styles.inputPlug, children: inputValue })] }) }));
|
|
117
|
+
} }) })));
|
|
118
|
+
});
|
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
export declare const FieldSelectSingle: import("react").ForwardRefExoticComponent<import("./types").InputProps & import("./types").WrapperProps & {
|
|
3
|
+
options: import("./types").OptionProps[];
|
|
4
|
+
loading?: boolean | undefined;
|
|
5
|
+
} & Omit<import("@snack-uikit/list").SelectionSingleState, "mode"> & {
|
|
4
6
|
'data-test-id'?: string | undefined;
|
|
5
7
|
} & import("react").AriaAttributes & {
|
|
6
|
-
options:
|
|
7
|
-
open?: boolean | undefined;
|
|
8
|
-
onOpenChange?(value: boolean): void;
|
|
8
|
+
options: import("./types").OptionProps[];
|
|
9
9
|
searchable?: boolean | undefined;
|
|
10
10
|
showCopyButton?: boolean | undefined;
|
|
11
11
|
showClearButton?: boolean | undefined;
|
|
12
|
-
prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
13
|
-
noDataText?: string | undefined;
|
|
14
|
-
locale?: Intl.Locale | undefined;
|
|
15
|
-
} & {
|
|
16
|
-
disabled?: boolean | undefined;
|
|
17
12
|
readonly?: boolean | undefined;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
onBlur?: import("react").FocusEventHandler<HTMLInputElement> | undefined;
|
|
23
|
-
} & {
|
|
24
|
-
showHintIcon?: boolean | undefined;
|
|
25
|
-
validationState?: import("../../types").ValidationState | undefined;
|
|
26
|
-
hint?: string | undefined;
|
|
27
|
-
size?: import("@snack-uikit/input-private").Size | undefined;
|
|
28
|
-
label?: string | undefined;
|
|
29
|
-
className?: string | undefined;
|
|
30
|
-
labelTooltip?: string | undefined;
|
|
31
|
-
required?: boolean | undefined;
|
|
32
|
-
labelTooltipPlacement?: import("@snack-uikit/popover-private/dist/types").Placement | undefined;
|
|
33
|
-
} & {
|
|
34
|
-
value?: string | undefined;
|
|
35
|
-
onChange?(value: string): void;
|
|
13
|
+
prefixIcon?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
14
|
+
footer?: import("react").ReactNode;
|
|
15
|
+
search?: import("./types").SearchState | undefined;
|
|
16
|
+
autocomplete?: boolean | undefined;
|
|
36
17
|
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -9,68 +9,82 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
-
import
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import mergeRefs from 'merge-refs';
|
|
14
|
+
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
|
|
14
15
|
import { useUncontrolledProp } from 'uncontrollable';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
16
|
+
import { InputPrivate } from '@snack-uikit/input-private';
|
|
17
|
+
import { Droplist, useFuzzySearch } from '@snack-uikit/list';
|
|
18
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
19
|
+
import { FieldContainerPrivate } from '../../helperComponents';
|
|
20
|
+
import { FieldDecorator } from '../FieldDecorator';
|
|
21
|
+
import { useButtons, useHandleOnKeyDown, useSearchInput } from './hooks';
|
|
22
|
+
import styles from './styles.module.css';
|
|
23
|
+
import { extractSelectedOptions, getArrowIcon, transformOptionsToItems } from './utils';
|
|
20
24
|
export const FieldSelectSingle = forwardRef((_a, ref) => {
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
inputValue,
|
|
35
|
-
setInputValue,
|
|
36
|
-
searchable,
|
|
37
|
-
options,
|
|
38
|
-
isChecked,
|
|
39
|
-
showCopyButton: showCopyButtonProp,
|
|
40
|
-
showClearButton: showClearButtonProp,
|
|
41
|
-
showAdditionalButton,
|
|
42
|
-
});
|
|
43
|
-
const handleOpenChange = (isOpen) => {
|
|
44
|
-
if (isOpen) {
|
|
45
|
-
searchable && selectAll(localRef.current);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
setInputValue(displayedValue);
|
|
49
|
-
}
|
|
50
|
-
setIsOpen(isOpen);
|
|
51
|
-
};
|
|
52
|
-
const handleClear = () => {
|
|
25
|
+
var _b;
|
|
26
|
+
var { id, name, placeholder, size = 's', options, value: valueProp, defaultValue, onChange: onChangeProp, loading, disabled = false, readonly = false, searchable = true, showCopyButton = true, showClearButton = true, onKeyDown: onInputKeyDownProp, label, labelTooltip, labelTooltipPlacement, required = false, hint, showHintIcon, validationState = 'default', footer, search, autocomplete = false, prefixIcon } = _a, rest = __rest(_a, ["id", "name", "placeholder", "size", "options", "value", "defaultValue", "onChange", "loading", "disabled", "readonly", "searchable", "showCopyButton", "showClearButton", "onKeyDown", "label", "labelTooltip", "labelTooltipPlacement", "required", "hint", "showHintIcon", "validationState", "footer", "search", "autocomplete", "prefixIcon"]);
|
|
27
|
+
const localRef = useRef(null);
|
|
28
|
+
const [open, setOpen] = useState(false);
|
|
29
|
+
const [value, setValue] = useUncontrolledProp(valueProp, defaultValue, onChangeProp);
|
|
30
|
+
const items = useMemo(() => transformOptionsToItems(options), [options]);
|
|
31
|
+
const selectedOption = useMemo(() => extractSelectedOptions(options, value), [options, value]);
|
|
32
|
+
const { inputValue, onInputValueChange, prevInputValue } = useSearchInput(Object.assign(Object.assign({}, search), { defaultValue: String(selectedOption !== null && selectedOption !== void 0 ? selectedOption : '') }));
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
var _a;
|
|
35
|
+
!open && onInputValueChange(String((_a = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _a !== void 0 ? _a : ''));
|
|
36
|
+
}, [onInputValueChange, open, selectedOption]);
|
|
37
|
+
useEffect(() => {
|
|
53
38
|
var _a, _b;
|
|
39
|
+
onInputValueChange(String((_a = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _a !== void 0 ? _a : ''));
|
|
40
|
+
prevInputValue.current = String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _b !== void 0 ? _b : '');
|
|
41
|
+
}, [prevInputValue, onInputValueChange, selectedOption]);
|
|
42
|
+
const onClear = () => {
|
|
43
|
+
var _a;
|
|
54
44
|
setValue('');
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
onInputValueChange('');
|
|
46
|
+
(_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
47
|
+
setOpen(true);
|
|
48
|
+
};
|
|
49
|
+
const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
|
|
50
|
+
const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
|
|
51
|
+
readonly,
|
|
52
|
+
size,
|
|
53
|
+
showClearButton: showClearButton && Boolean(value),
|
|
54
|
+
showCopyButton,
|
|
55
|
+
inputRef: localRef,
|
|
56
|
+
onClear,
|
|
57
|
+
valueToCopy: String((_b = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.option) !== null && _b !== void 0 ? _b : ''),
|
|
58
|
+
});
|
|
59
|
+
const commonHandleOnKeyDown = useHandleOnKeyDown({
|
|
60
|
+
inputKeyDownNavigationHandler,
|
|
61
|
+
onInputKeyDownProp,
|
|
62
|
+
setOpen,
|
|
63
|
+
});
|
|
64
|
+
const handleOnKeyDown = (onKeyDown) => (e) => {
|
|
65
|
+
if (!open && prevInputValue.current !== inputValue) {
|
|
66
|
+
setOpen(true);
|
|
63
67
|
}
|
|
68
|
+
commonHandleOnKeyDown(onKeyDown)(e);
|
|
64
69
|
};
|
|
65
|
-
const
|
|
70
|
+
const handleSelectionChange = (newValue) => {
|
|
66
71
|
var _a;
|
|
67
|
-
setValue(
|
|
68
|
-
setInputValue(option.label);
|
|
69
|
-
setIsOpen(false);
|
|
72
|
+
setValue(newValue);
|
|
70
73
|
(_a = localRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
74
|
+
if (newValue) {
|
|
75
|
+
setOpen(false);
|
|
76
|
+
}
|
|
71
77
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
const handleOpenChange = (open) => {
|
|
79
|
+
if (!readonly && !disabled && !buttonsRefs.includes(document.activeElement)) {
|
|
80
|
+
setOpen(open);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const fuzzySearch = useFuzzySearch(items);
|
|
84
|
+
const result = autocomplete ? items : fuzzySearch(prevInputValue.current !== inputValue ? inputValue : '');
|
|
85
|
+
return (_jsx(FieldDecorator, Object.assign({}, extractSupportProps(rest), { required: required, readonly: readonly, label: label, labelTooltip: labelTooltip, labelTooltipPlacement: labelTooltipPlacement, labelFor: id, hint: hint, disabled: disabled, showHintIcon: showHintIcon, size: size, validationState: validationState, children: _jsx(Droplist, { trigger: 'clickAndFocusVisible', placement: 'bottom', "data-test-id": 'field-select__list', items: result, triggerElemRef: localRef, scroll: true, marker: true, footer: footer, selection: {
|
|
86
|
+
mode: 'single',
|
|
87
|
+
value: value,
|
|
88
|
+
onChange: handleSelectionChange,
|
|
89
|
+
}, size: size, open: open, onOpenChange: handleOpenChange, loading: loading, children: ({ onKeyDown }) => (_jsxs(FieldContainerPrivate, { className: styles.container, validationState: validationState, disabled: disabled, readonly: readonly, focused: open, variant: 'single-line-container', inputRef: localRef, size: size, prefix: prefixIcon, children: [_jsx(InputPrivate, { id: id, name: name, type: 'text', disabled: disabled, placeholder: placeholder, ref: mergeRefs(ref, localRef), onChange: searchable ? onInputValueChange : undefined, value: inputValue, readonly: !searchable || readonly, "data-test-id": 'field-select__input', onKeyDown: handleOnKeyDown(onKeyDown) }), _jsxs("div", { className: styles.postfix, children: [buttons, _jsx(ArrowIcon, { size: arrowIconSize, className: styles.arrowIcon })] })] })) }) })));
|
|
76
90
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { KeyboardEvent, KeyboardEventHandler, RefObject } from 'react';
|
|
2
|
+
import { Handler } from 'uncontrollable';
|
|
3
|
+
import { OptionProps, SearchState } from './types';
|
|
4
|
+
type UseHandleOnKeyDownProps = {
|
|
5
|
+
inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
|
|
6
|
+
onInputKeyDownProp: KeyboardEventHandler<HTMLInputElement> | undefined;
|
|
7
|
+
setOpen(open: boolean): void;
|
|
8
|
+
};
|
|
9
|
+
export declare function useHandleOnKeyDown({ setOpen, inputKeyDownNavigationHandler, onInputKeyDownProp, }: UseHandleOnKeyDownProps): (onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLInputElement>) => void;
|
|
10
|
+
type UseButtonsProps = {
|
|
11
|
+
readonly: boolean;
|
|
12
|
+
showClearButton: boolean;
|
|
13
|
+
showCopyButton: boolean;
|
|
14
|
+
size: 's' | 'm' | 'l';
|
|
15
|
+
onClear(): void;
|
|
16
|
+
inputRef: RefObject<HTMLInputElement>;
|
|
17
|
+
valueToCopy?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function useButtons({ readonly, showClearButton, showCopyButton, size, onClear, inputRef, valueToCopy, }: UseButtonsProps): {
|
|
20
|
+
buttons: import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
inputKeyDownNavigationHandler: KeyboardEventHandler<HTMLInputElement>;
|
|
22
|
+
buttonsRefs: (Element | null)[];
|
|
23
|
+
};
|
|
24
|
+
export declare function useSearchInput({ value, onChange, defaultValue }: SearchState): {
|
|
25
|
+
inputValue: string;
|
|
26
|
+
onInputValueChange: Handler;
|
|
27
|
+
prevInputValue: import("react").MutableRefObject<string>;
|
|
28
|
+
};
|
|
29
|
+
export declare function useHandleDeleteItem(setValue: Handler): (option?: OptionProps) => () => void;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
2
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
3
|
+
import { useButtonNavigation, useClearButton } from '@snack-uikit/input-private';
|
|
4
|
+
import { extractChildIds } from '@snack-uikit/list/dist/utils';
|
|
5
|
+
import { useCopyButton } from '../../hooks';
|
|
6
|
+
import { isAccordionOptionProps, isBaseOptionProps, isNextListOptionProps, transformOptionsToItems } from './utils';
|
|
7
|
+
export function useHandleOnKeyDown({ setOpen, inputKeyDownNavigationHandler, onInputKeyDownProp, }) {
|
|
8
|
+
return useCallback((onKeyDown) => (e) => {
|
|
9
|
+
if (e.code === 'Space') {
|
|
10
|
+
e.stopPropagation();
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
|
|
14
|
+
}
|
|
15
|
+
if (e.code === 'ArrowUp') {
|
|
16
|
+
setOpen(false);
|
|
17
|
+
}
|
|
18
|
+
if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
}
|
|
21
|
+
if (e.key === 'Tab') {
|
|
22
|
+
setOpen(false);
|
|
23
|
+
}
|
|
24
|
+
inputKeyDownNavigationHandler(e);
|
|
25
|
+
onInputKeyDownProp === null || onInputKeyDownProp === void 0 ? void 0 : onInputKeyDownProp(e);
|
|
26
|
+
}, [inputKeyDownNavigationHandler, onInputKeyDownProp, setOpen]);
|
|
27
|
+
}
|
|
28
|
+
export function useButtons({ readonly, showClearButton, showCopyButton, size, onClear, inputRef, valueToCopy = '', }) {
|
|
29
|
+
const clearButtonRef = useRef(null);
|
|
30
|
+
const copyButtonRef = useRef(null);
|
|
31
|
+
const buttonsRefs = [copyButtonRef.current, clearButtonRef.current];
|
|
32
|
+
const clearButtonSettings = useClearButton({
|
|
33
|
+
clearButtonRef,
|
|
34
|
+
showClearButton: !readonly && showClearButton,
|
|
35
|
+
size,
|
|
36
|
+
onClear,
|
|
37
|
+
});
|
|
38
|
+
const copyButtonSettings = useCopyButton({
|
|
39
|
+
copyButtonRef,
|
|
40
|
+
showCopyButton: readonly && showCopyButton,
|
|
41
|
+
size,
|
|
42
|
+
valueToCopy,
|
|
43
|
+
});
|
|
44
|
+
const { onInputKeyDown: inputKeyDownNavigationHandler, buttons } = useButtonNavigation({
|
|
45
|
+
inputRef,
|
|
46
|
+
buttons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
|
|
47
|
+
onButtonKeyDown: undefined,
|
|
48
|
+
readonly,
|
|
49
|
+
submitKeys: ['Enter', 'Space', 'Tab'],
|
|
50
|
+
});
|
|
51
|
+
return { buttons, inputKeyDownNavigationHandler, buttonsRefs };
|
|
52
|
+
}
|
|
53
|
+
export function useSearchInput({ value, onChange, defaultValue }) {
|
|
54
|
+
const [inputValue, onInputValueChange] = useUncontrolledProp(value, defaultValue !== null && defaultValue !== void 0 ? defaultValue : '', onChange);
|
|
55
|
+
const prevInputValue = useRef(inputValue);
|
|
56
|
+
return { inputValue, onInputValueChange, prevInputValue };
|
|
57
|
+
}
|
|
58
|
+
export function useHandleDeleteItem(setValue) {
|
|
59
|
+
return useCallback((option) => () => {
|
|
60
|
+
if (!option) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (isAccordionOptionProps(option) || isNextListOptionProps(option)) {
|
|
64
|
+
const removeIds = extractChildIds({ items: transformOptionsToItems(option.options) }).concat(option.value);
|
|
65
|
+
setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => !removeIds.includes(v !== null && v !== void 0 ? v : '')));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (isBaseOptionProps(option)) {
|
|
69
|
+
setValue((value) => value === null || value === void 0 ? void 0 : value.filter(v => v !== option.value));
|
|
70
|
+
}
|
|
71
|
+
}, [setValue]);
|
|
72
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { FieldSelect } from './FieldSelect';
|
|
2
|
+
export type { FieldSelectSingleProps, FieldSelectMultipleProps, FieldSelectProps, OptionProps, BaseOptionProps, AccordionOptionProps, NestListOptionProps, GroupOptionProps, } from './types';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { FieldSelect } from './FieldSelect';
|