@snack-uikit/chips 0.12.9 → 0.13.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/CHANGELOG.md +31 -0
- package/README.md +71 -20
- package/dist/components/ChipAssist/ChipAssist.js +1 -1
- package/dist/components/ChipAssist/styles.module.css +1 -1
- package/dist/components/ChipChoice/components/ChipChoiceBase/ChipChoiceBase.d.ts +20 -0
- package/dist/components/ChipChoice/components/ChipChoiceBase/ChipChoiceBase.js +65 -0
- package/dist/components/ChipChoice/components/ChipChoiceBase/index.d.ts +1 -0
- package/dist/components/ChipChoice/components/ChipChoiceBase/index.js +1 -0
- package/dist/components/ChipChoice/components/{ChipChoiceCustom → ChipChoiceBase}/styles.module.css +18 -10
- package/dist/components/ChipChoice/components/ChipChoiceCustom.d.ts +25 -0
- package/dist/components/ChipChoice/components/ChipChoiceCustom.js +38 -0
- package/dist/components/ChipChoice/components/ChipChoiceDate.d.ts +3 -2
- package/dist/components/ChipChoice/components/ChipChoiceDate.js +19 -9
- package/dist/components/ChipChoice/components/ChipChoiceDateRange.d.ts +3 -2
- package/dist/components/ChipChoice/components/ChipChoiceDateRange.js +18 -8
- package/dist/components/ChipChoice/components/ChipChoiceMultiple.d.ts +8 -0
- package/dist/components/ChipChoice/components/ChipChoiceMultiple.js +88 -0
- package/dist/components/ChipChoice/components/ChipChoiceSingle.d.ts +7 -13
- package/dist/components/ChipChoice/components/ChipChoiceSingle.js +64 -22
- package/dist/components/ChipChoice/components/index.d.ts +1 -1
- package/dist/components/ChipChoice/components/index.js +1 -1
- package/dist/components/ChipChoice/constants.d.ts +3 -2
- package/dist/components/ChipChoice/constants.js +2 -1
- package/dist/components/ChipChoice/hooks.d.ts +11 -0
- package/dist/components/ChipChoice/hooks.js +35 -0
- package/dist/components/ChipChoice/index.d.ts +5 -4
- package/dist/components/ChipChoice/index.js +3 -2
- package/dist/components/ChipChoice/styles.module.css +9 -5
- package/dist/components/ChipChoice/types.d.ts +55 -7
- package/dist/components/ChipChoice/utils/index.d.ts +2 -0
- package/dist/components/ChipChoice/utils/index.js +2 -0
- package/dist/components/ChipChoice/utils/options.d.ts +9 -0
- package/dist/components/ChipChoice/utils/options.js +32 -0
- package/dist/components/ChipChoice/utils/typeGuards.d.ts +6 -0
- package/dist/components/ChipChoice/utils/typeGuards.js +15 -0
- package/dist/components/ChipChoice/utils/utils.d.ts +18 -0
- package/dist/components/ChipChoice/utils/utils.js +29 -0
- package/dist/components/ChipChoiceRow/components/constants.d.ts +2 -1
- package/dist/components/ChipChoiceRow/components/constants.js +2 -1
- package/dist/components/ChipChoiceRow/types.d.ts +11 -5
- package/dist/components/ChipToggle/styles.module.css +1 -1
- package/package.json +13 -7
- package/src/components/ChipAssist/ChipAssist.tsx +1 -0
- package/src/components/ChipChoice/components/ChipChoiceBase/ChipChoiceBase.tsx +149 -0
- package/src/components/ChipChoice/components/ChipChoiceBase/index.ts +1 -0
- package/src/components/ChipChoice/components/{ChipChoiceCustom → ChipChoiceBase}/styles.module.scss +11 -5
- package/src/components/ChipChoice/components/ChipChoiceCustom.tsx +89 -0
- package/src/components/ChipChoice/components/ChipChoiceDate.tsx +42 -18
- package/src/components/ChipChoice/components/ChipChoiceDateRange.tsx +43 -18
- package/src/components/ChipChoice/components/ChipChoiceMultiple.tsx +157 -0
- package/src/components/ChipChoice/components/ChipChoiceSingle.tsx +123 -62
- package/src/components/ChipChoice/components/index.ts +1 -1
- package/src/components/ChipChoice/constants.ts +3 -2
- package/src/components/ChipChoice/hooks.ts +61 -0
- package/src/components/ChipChoice/index.ts +7 -13
- package/src/components/ChipChoice/styles.module.scss +11 -5
- package/src/components/ChipChoice/types.ts +105 -7
- package/src/components/ChipChoice/utils/index.ts +3 -0
- package/src/components/ChipChoice/utils/options.tsx +58 -0
- package/src/components/ChipChoice/utils/typeGuards.ts +35 -0
- package/src/components/ChipChoice/utils/utils.ts +60 -0
- package/src/components/ChipChoiceRow/components/constants.ts +2 -1
- package/src/components/ChipChoiceRow/types.ts +18 -10
- package/dist/components/ChipChoice/components/ChipChoiceCustom/ChipChoiceCustom.d.ts +0 -23
- package/dist/components/ChipChoice/components/ChipChoiceCustom/ChipChoiceCustom.js +0 -75
- package/dist/components/ChipChoice/components/ChipChoiceCustom/index.d.ts +0 -1
- package/dist/components/ChipChoice/components/ChipChoiceCustom/index.js +0 -1
- package/dist/components/ChipChoice/components/ChipChoiceMulti.d.ts +0 -14
- package/dist/components/ChipChoice/components/ChipChoiceMulti.js +0 -44
- package/dist/components/ChipChoice/utils.d.ts +0 -14
- package/dist/components/ChipChoice/utils.js +0 -26
- package/src/components/ChipChoice/components/ChipChoiceCustom/ChipChoiceCustom.tsx +0 -188
- package/src/components/ChipChoice/components/ChipChoiceCustom/index.ts +0 -1
- package/src/components/ChipChoice/components/ChipChoiceMulti.tsx +0 -90
- package/src/components/ChipChoice/utils.ts +0 -48
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Droplist, ItemId, SelectionSingleValueType } from '@snack-uikit/list';
|
|
4
|
+
import { useLocale } from '@snack-uikit/locale';
|
|
5
|
+
import { useValueControl } from '@snack-uikit/utils';
|
|
6
|
+
|
|
7
|
+
import { CHIP_CHOICE_TEST_IDS, SIZE } from '../../../constants';
|
|
8
|
+
import { DROPLIST_SIZE_MAP } from '../constants';
|
|
9
|
+
import { useFuzzySearch, useHandleOnKeyDown } from '../hooks';
|
|
10
|
+
import { ChipChoiceMultipleProps, ContentRenderProps } from '../types';
|
|
11
|
+
import { FlattenOption, kindFlattenOptions } from '../utils';
|
|
12
|
+
import { transformOptionsToItems } from '../utils/options';
|
|
13
|
+
import { ChipChoiceBase } from './ChipChoiceBase';
|
|
14
|
+
|
|
15
|
+
export type ChipChoiceMultipleValueFormatterProps<T extends ContentRenderProps = ContentRenderProps> = {
|
|
16
|
+
value: FlattenOption<T>[];
|
|
17
|
+
total: number;
|
|
18
|
+
allLabel: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const defaultMultiValueLabelFormatter = ({ value, total, allLabel }: ChipChoiceMultipleValueFormatterProps): ItemId => {
|
|
22
|
+
const len = value.length;
|
|
23
|
+
|
|
24
|
+
if ([0, total].includes(len) && total !== len) {
|
|
25
|
+
return allLabel;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (len === 1) {
|
|
29
|
+
return value[0].label;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${len.toString()}/${total}`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function ChipChoiceMultiple<T extends ContentRenderProps = ContentRenderProps>({
|
|
36
|
+
value: valueProp,
|
|
37
|
+
defaultValue,
|
|
38
|
+
options,
|
|
39
|
+
onChange: onChangeProp,
|
|
40
|
+
valueRender,
|
|
41
|
+
size = SIZE.S,
|
|
42
|
+
label,
|
|
43
|
+
searchable,
|
|
44
|
+
contentRender,
|
|
45
|
+
dropDownClassName,
|
|
46
|
+
showClearButton = true,
|
|
47
|
+
...rest
|
|
48
|
+
}: ChipChoiceMultipleProps<T>) {
|
|
49
|
+
const [value, setValue] = useValueControl<SelectionSingleValueType[]>({
|
|
50
|
+
value: valueProp,
|
|
51
|
+
defaultValue,
|
|
52
|
+
onChange: onChangeProp,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const flattenOptions = useMemo(() => {
|
|
56
|
+
const { flattenOptions } = kindFlattenOptions<T>({ options });
|
|
57
|
+
|
|
58
|
+
return flattenOptions;
|
|
59
|
+
}, [options]);
|
|
60
|
+
|
|
61
|
+
const [searchValue = '', setSearchValue] = useState<string>('');
|
|
62
|
+
|
|
63
|
+
const { t } = useLocale('Chips');
|
|
64
|
+
|
|
65
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
66
|
+
const handleOnKeyDown = useHandleOnKeyDown({ setOpen });
|
|
67
|
+
|
|
68
|
+
const flatMapOptions = useMemo(() => Object.values(flattenOptions), [flattenOptions]);
|
|
69
|
+
|
|
70
|
+
const selectedOptions = useMemo(
|
|
71
|
+
() => (value && value.length ? value.map(id => flattenOptions[id]).filter(Boolean) : ([] as FlattenOption<T>[])),
|
|
72
|
+
[flattenOptions, value],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const valueToRender = valueRender
|
|
76
|
+
? valueRender(selectedOptions)
|
|
77
|
+
: defaultMultiValueLabelFormatter({
|
|
78
|
+
value: selectedOptions ?? [],
|
|
79
|
+
total: Object.keys(flattenOptions).length,
|
|
80
|
+
allLabel: t('allLabel'),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const fuzzySearch = useFuzzySearch(options, flatMapOptions);
|
|
84
|
+
|
|
85
|
+
const result = useMemo(
|
|
86
|
+
() => (!searchable || valueToRender === searchValue ? options : fuzzySearch(searchValue)),
|
|
87
|
+
[fuzzySearch, options, searchValue, searchable, valueToRender],
|
|
88
|
+
);
|
|
89
|
+
const items = useMemo(() => transformOptionsToItems<T>(result, contentRender), [contentRender, result]);
|
|
90
|
+
|
|
91
|
+
const clearValue = () => setValue([]);
|
|
92
|
+
const chipRef = useRef<HTMLDivElement>(null);
|
|
93
|
+
|
|
94
|
+
const handleSelectionChange = useCallback(
|
|
95
|
+
(newValue?: SelectionSingleValueType) => {
|
|
96
|
+
if (newValue !== undefined) {
|
|
97
|
+
setValue(newValue);
|
|
98
|
+
setSearchValue('');
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
[setValue],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (searchValue && !open) {
|
|
106
|
+
setSearchValue('');
|
|
107
|
+
}
|
|
108
|
+
}, [searchable, open, searchValue]);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Droplist
|
|
112
|
+
{...rest}
|
|
113
|
+
items={items}
|
|
114
|
+
selection={{
|
|
115
|
+
value,
|
|
116
|
+
onChange: handleSelectionChange,
|
|
117
|
+
mode: 'multiple',
|
|
118
|
+
}}
|
|
119
|
+
trigger='clickAndFocusVisible'
|
|
120
|
+
placement='bottom-start'
|
|
121
|
+
widthStrategy='gte'
|
|
122
|
+
size={DROPLIST_SIZE_MAP[size]}
|
|
123
|
+
data-test-id={CHIP_CHOICE_TEST_IDS.droplist}
|
|
124
|
+
open={open}
|
|
125
|
+
triggerElemRef={chipRef}
|
|
126
|
+
onOpenChange={open => {
|
|
127
|
+
if (!open) {
|
|
128
|
+
setSearchValue('');
|
|
129
|
+
}
|
|
130
|
+
setOpen(open);
|
|
131
|
+
}}
|
|
132
|
+
scroll
|
|
133
|
+
className={dropDownClassName}
|
|
134
|
+
search={
|
|
135
|
+
searchable
|
|
136
|
+
? {
|
|
137
|
+
value: searchValue,
|
|
138
|
+
onChange: setSearchValue,
|
|
139
|
+
}
|
|
140
|
+
: undefined
|
|
141
|
+
}
|
|
142
|
+
>
|
|
143
|
+
<ChipChoiceBase
|
|
144
|
+
{...rest}
|
|
145
|
+
ref={chipRef}
|
|
146
|
+
onClearButtonClick={clearValue}
|
|
147
|
+
value={value}
|
|
148
|
+
showClearButton={showClearButton && !(Array.isArray(value) && [0].includes(value.length))}
|
|
149
|
+
valueToRender={valueToRender}
|
|
150
|
+
label={label}
|
|
151
|
+
loading={rest.loading}
|
|
152
|
+
size={size}
|
|
153
|
+
onKeyDown={handleOnKeyDown()}
|
|
154
|
+
/>
|
|
155
|
+
</Droplist>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -1,82 +1,143 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { useUncontrolledProp } from 'uncontrollable';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
2
|
|
|
4
|
-
import { Droplist } from '@snack-uikit/
|
|
3
|
+
import { Droplist, ItemId, SelectionSingleValueType } from '@snack-uikit/list';
|
|
5
4
|
import { useLocale } from '@snack-uikit/locale';
|
|
5
|
+
import { useValueControl } from '@snack-uikit/utils';
|
|
6
6
|
|
|
7
|
-
import { SIZE } from '../../../constants';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
defaultValue?: string;
|
|
19
|
-
/** Колбек смены значения */
|
|
20
|
-
onChange?(value: string): void;
|
|
21
|
-
/** Колбек формирующий строковое представление выбранного значения. Принимает выбранное значение. По умолчанию для отображения используется FilterOption.label */
|
|
22
|
-
valueFormatter?(option?: FilterOption): string;
|
|
7
|
+
import { CHIP_CHOICE_TEST_IDS, SIZE } from '../../../constants';
|
|
8
|
+
import { DROPLIST_SIZE_MAP } from '../constants';
|
|
9
|
+
import { useFuzzySearch, useHandleOnKeyDown } from '../hooks';
|
|
10
|
+
import { ChipChoiceSingleProps, ContentRenderProps } from '../types';
|
|
11
|
+
import { FlattenOption, kindFlattenOptions } from '../utils';
|
|
12
|
+
import { transformOptionsToItems } from '../utils/options';
|
|
13
|
+
import { ChipChoiceBase } from './ChipChoiceBase';
|
|
14
|
+
|
|
15
|
+
export type ChipChoiceSingleValueFormatterProps = {
|
|
16
|
+
label?: ItemId;
|
|
17
|
+
allLabel?: string;
|
|
23
18
|
};
|
|
24
19
|
|
|
25
|
-
export function
|
|
26
|
-
|
|
20
|
+
export function defaultSingleValueFormatter({ label, allLabel }: ChipChoiceSingleValueFormatterProps) {
|
|
21
|
+
return label ?? allLabel;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ChipChoiceSingle<T extends ContentRenderProps = ContentRenderProps>({
|
|
25
|
+
value: valueProp,
|
|
27
26
|
defaultValue,
|
|
28
27
|
options,
|
|
29
|
-
onChange,
|
|
30
|
-
|
|
28
|
+
onChange: onChangeProp,
|
|
29
|
+
valueRender,
|
|
31
30
|
size = SIZE.S,
|
|
32
|
-
|
|
31
|
+
label,
|
|
32
|
+
searchable,
|
|
33
|
+
contentRender,
|
|
34
|
+
dropDownClassName,
|
|
33
35
|
...rest
|
|
34
|
-
}: ChipChoiceSingleProps) {
|
|
35
|
-
const [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
onChange,
|
|
39
|
-
);
|
|
36
|
+
}: ChipChoiceSingleProps<T>) {
|
|
37
|
+
const [value, setValue] = useValueControl<SelectionSingleValueType>({
|
|
38
|
+
value: valueProp,
|
|
39
|
+
defaultValue,
|
|
40
|
+
onChange: onChangeProp,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const flattenOptions = useMemo(() => {
|
|
44
|
+
const { flattenOptions } = kindFlattenOptions<T>({ options });
|
|
45
|
+
|
|
46
|
+
return flattenOptions;
|
|
47
|
+
}, [options]);
|
|
40
48
|
|
|
41
49
|
const { t } = useLocale('Chips');
|
|
42
50
|
|
|
43
|
-
const
|
|
51
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
52
|
+
const handleOnKeyDown = useHandleOnKeyDown({ setOpen });
|
|
53
|
+
|
|
54
|
+
const flatMapOptions = useMemo(() => Object.values(flattenOptions), [flattenOptions]);
|
|
55
|
+
|
|
56
|
+
const selectedOption = useMemo(
|
|
57
|
+
() => (value ? flattenOptions[value] : ({} as FlattenOption<T>)),
|
|
58
|
+
[flattenOptions, value],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const [searchValue, setSearchValue] = useState<string>('');
|
|
44
62
|
|
|
45
|
-
const valueToRender =
|
|
46
|
-
?
|
|
47
|
-
: defaultSingleValueFormatter({
|
|
63
|
+
const valueToRender = valueRender
|
|
64
|
+
? valueRender(selectedOption)
|
|
65
|
+
: defaultSingleValueFormatter({ label: selectedOption?.label, allLabel: t('allLabel') });
|
|
48
66
|
|
|
49
|
-
const
|
|
67
|
+
const fuzzySearch = useFuzzySearch(options, flatMapOptions);
|
|
68
|
+
|
|
69
|
+
const result = useMemo(
|
|
70
|
+
() => (!searchable || valueToRender === searchValue ? options : fuzzySearch(searchValue)),
|
|
71
|
+
[fuzzySearch, options, searchValue, searchable, valueToRender],
|
|
72
|
+
);
|
|
73
|
+
const items = useMemo(() => transformOptionsToItems<T>(result, contentRender), [contentRender, result]);
|
|
74
|
+
|
|
75
|
+
const clearValue = () => setValue(undefined);
|
|
76
|
+
const chipRef = useRef<HTMLDivElement>(null);
|
|
77
|
+
|
|
78
|
+
const handleSelectionChange = useCallback(
|
|
79
|
+
(newValue?: SelectionSingleValueType) => {
|
|
80
|
+
if (newValue !== undefined) {
|
|
81
|
+
chipRef.current?.focus();
|
|
82
|
+
|
|
83
|
+
setOpen(false);
|
|
84
|
+
setValue(newValue);
|
|
85
|
+
setSearchValue('');
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
[setSearchValue, setValue],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (searchValue && !open) {
|
|
93
|
+
setSearchValue('');
|
|
94
|
+
}
|
|
95
|
+
}, [searchable, open, searchValue]);
|
|
50
96
|
|
|
51
97
|
return (
|
|
52
|
-
<
|
|
53
|
-
onClearButtonClick={clearValue}
|
|
54
|
-
value={selectedValue}
|
|
55
|
-
valueToRender={valueToRender}
|
|
56
|
-
data-test-id={dataTestId}
|
|
57
|
-
size={size}
|
|
98
|
+
<Droplist
|
|
58
99
|
{...rest}
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
items={items}
|
|
101
|
+
selection={{
|
|
102
|
+
value,
|
|
103
|
+
onChange: handleSelectionChange,
|
|
104
|
+
mode: 'single',
|
|
105
|
+
}}
|
|
106
|
+
data-test-id={CHIP_CHOICE_TEST_IDS.droplist}
|
|
107
|
+
size={DROPLIST_SIZE_MAP[size]}
|
|
108
|
+
trigger='click'
|
|
109
|
+
placement='bottom-start'
|
|
110
|
+
className={dropDownClassName}
|
|
111
|
+
widthStrategy='gte'
|
|
112
|
+
open={open}
|
|
113
|
+
triggerElemRef={chipRef}
|
|
114
|
+
onOpenChange={open => {
|
|
115
|
+
if (!open) {
|
|
116
|
+
setSearchValue('');
|
|
117
|
+
}
|
|
118
|
+
setOpen(open);
|
|
119
|
+
}}
|
|
120
|
+
scroll
|
|
121
|
+
search={
|
|
122
|
+
searchable
|
|
123
|
+
? {
|
|
124
|
+
value: searchValue,
|
|
125
|
+
onChange: setSearchValue,
|
|
126
|
+
}
|
|
127
|
+
: undefined
|
|
79
128
|
}
|
|
80
|
-
|
|
129
|
+
>
|
|
130
|
+
<ChipChoiceBase
|
|
131
|
+
{...rest}
|
|
132
|
+
ref={chipRef}
|
|
133
|
+
onClearButtonClick={clearValue}
|
|
134
|
+
value={value}
|
|
135
|
+
valueToRender={valueToRender}
|
|
136
|
+
label={label}
|
|
137
|
+
loading={rest.loading}
|
|
138
|
+
size={size}
|
|
139
|
+
onKeyDown={handleOnKeyDown()}
|
|
140
|
+
/>
|
|
141
|
+
</Droplist>
|
|
81
142
|
);
|
|
82
143
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CalendarProps } from '@snack-uikit/calendar';
|
|
2
|
-
import { DroplistProps } from '@snack-uikit/
|
|
2
|
+
import { DroplistProps } from '@snack-uikit/list';
|
|
3
3
|
|
|
4
4
|
import { BUTTON_SIZE, SIZE } from '../../constants';
|
|
5
5
|
import { Size } from '../../types';
|
|
@@ -26,8 +26,9 @@ export const DROPLIST_SIZE_MAP: Record<Size, DroplistProps['size']> = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export const CHIP_CHOICE_TYPE = {
|
|
29
|
-
|
|
29
|
+
Multiple: 'multiple',
|
|
30
30
|
Date: 'date',
|
|
31
31
|
DateRange: 'date-range',
|
|
32
32
|
Single: 'single',
|
|
33
|
+
Custom: 'custom',
|
|
33
34
|
} as const;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import FuzzySearch from 'fuzzy-search';
|
|
2
|
+
import { KeyboardEvent, KeyboardEventHandler, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
import { AccordionOption, BaseOption, ContentRenderProps, FilterOption, NestListOption } from './types';
|
|
5
|
+
|
|
6
|
+
type UseHandleOnKeyDownProps = {
|
|
7
|
+
setOpen(open: boolean): void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function useHandleOnKeyDown({ setOpen }: UseHandleOnKeyDownProps) {
|
|
11
|
+
return useCallback(
|
|
12
|
+
(onKeyDown?: KeyboardEventHandler<HTMLElement>) => (e: KeyboardEvent<HTMLDivElement>) => {
|
|
13
|
+
if (e.code === 'Space') {
|
|
14
|
+
e.stopPropagation();
|
|
15
|
+
} else {
|
|
16
|
+
onKeyDown?.(e);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (['ArrowDown'].includes(e.key)) {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
setOpen(true);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (['ArrowUp'].includes(e.key)) {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
setOpen(false);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (e.key === 'Tab') {
|
|
30
|
+
setOpen(false);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
[setOpen],
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 2;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Нечеткий поиск среди айтемов по полям 'content.option', 'content.caption', 'content.description', 'label'
|
|
41
|
+
*/
|
|
42
|
+
export function useFuzzySearch<T extends ContentRenderProps = ContentRenderProps>(
|
|
43
|
+
options: FilterOption<T>[],
|
|
44
|
+
flatMapOptions: (BaseOption<T> | AccordionOption<T> | NestListOption<T>)[],
|
|
45
|
+
minSearchInputLength?: number,
|
|
46
|
+
) {
|
|
47
|
+
return useCallback(
|
|
48
|
+
(search: string) => {
|
|
49
|
+
const searcher = new FuzzySearch(
|
|
50
|
+
flatMapOptions,
|
|
51
|
+
['label', 'contentRenderProps.description', 'contentRenderProps.caption'],
|
|
52
|
+
{},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return search.length > (minSearchInputLength ?? DEFAULT_MIN_SEARCH_INPUT_LENGTH)
|
|
56
|
+
? searcher.search(search)
|
|
57
|
+
: options;
|
|
58
|
+
},
|
|
59
|
+
[flatMapOptions, minSearchInputLength, options],
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -5,26 +5,20 @@ import {
|
|
|
5
5
|
ChipChoiceDateProps,
|
|
6
6
|
ChipChoiceDateRange,
|
|
7
7
|
ChipChoiceDateRangeProps,
|
|
8
|
-
|
|
9
|
-
ChipChoiceMultiProps,
|
|
8
|
+
ChipChoiceMultiple,
|
|
10
9
|
ChipChoiceSingle,
|
|
11
|
-
|
|
10
|
+
CustomContentRenderProps,
|
|
12
11
|
} from './components';
|
|
13
12
|
|
|
14
|
-
export type { FilterOption } from './types';
|
|
15
|
-
|
|
16
|
-
export type {
|
|
17
|
-
ChipChoiceCustomProps,
|
|
18
|
-
ChipChoiceMultiProps,
|
|
19
|
-
ChipChoiceSingleProps,
|
|
20
|
-
ChipChoiceDateProps,
|
|
21
|
-
ChipChoiceDateRangeProps,
|
|
22
|
-
};
|
|
13
|
+
export type { FilterOption, ChipChoiceMultipleProps, ChipChoiceSingleProps, ContentRenderProps } from './types';
|
|
14
|
+
export type { ChipChoiceCustomProps, ChipChoiceDateProps, ChipChoiceDateRangeProps, CustomContentRenderProps };
|
|
23
15
|
|
|
24
16
|
export namespace ChipChoice {
|
|
25
17
|
export const Custom = ChipChoiceCustom;
|
|
26
18
|
export const Single = ChipChoiceSingle;
|
|
27
|
-
export const
|
|
19
|
+
export const Multiple = ChipChoiceMultiple;
|
|
28
20
|
export const Date = ChipChoiceDate;
|
|
29
21
|
export const DateRange = ChipChoiceDateRange;
|
|
30
22
|
}
|
|
23
|
+
|
|
24
|
+
export { isAccordionOption, isBaseOption, isGroupOption, isGroupSelectOption, isNextListOption } from './utils';
|
|
@@ -18,7 +18,8 @@ $valueTypography: (
|
|
|
18
18
|
'l': $sans-label-l,
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
.label,
|
|
21
|
+
.label,
|
|
22
|
+
.value {
|
|
22
23
|
display: inline-flex;
|
|
23
24
|
align-items: center;
|
|
24
25
|
}
|
|
@@ -30,7 +31,8 @@ $valueTypography: (
|
|
|
30
31
|
background-color: $sys-neutral-background1-level;
|
|
31
32
|
border-color: $sys-neutral-decor-default;
|
|
32
33
|
|
|
33
|
-
.label,
|
|
34
|
+
.label,
|
|
35
|
+
.value {
|
|
34
36
|
color: $sys-neutral-text-support;
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -46,7 +48,9 @@ $valueTypography: (
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
&:hover,
|
|
51
|
+
&:hover,
|
|
52
|
+
&:active,
|
|
53
|
+
&:focus-visible {
|
|
50
54
|
background-color: $sys-neutral-background2-level;
|
|
51
55
|
border-color: $sys-neutral-decor-hovered;
|
|
52
56
|
|
|
@@ -68,7 +72,8 @@ $valueTypography: (
|
|
|
68
72
|
background-color: $sys-neutral-background;
|
|
69
73
|
border-color: $sys-neutral-decor-disabled;
|
|
70
74
|
|
|
71
|
-
.label,
|
|
75
|
+
.label,
|
|
76
|
+
.value {
|
|
72
77
|
color: $sys-neutral-text-light;
|
|
73
78
|
}
|
|
74
79
|
}
|
|
@@ -79,7 +84,8 @@ $valueTypography: (
|
|
|
79
84
|
background-color: $sys-neutral-background;
|
|
80
85
|
border-color: $sys-neutral-decor-activated;
|
|
81
86
|
|
|
82
|
-
.label,
|
|
87
|
+
.label,
|
|
88
|
+
.value {
|
|
83
89
|
color: $sys-neutral-text-support;
|
|
84
90
|
}
|
|
85
91
|
}
|
|
@@ -1,13 +1,71 @@
|
|
|
1
|
-
import { MouseEventHandler } from 'react';
|
|
1
|
+
import { MouseEventHandler, ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DropdownProps } from '@snack-uikit/dropdown';
|
|
4
|
+
import {
|
|
5
|
+
BaseItemProps,
|
|
6
|
+
DroplistProps,
|
|
7
|
+
GroupItemProps,
|
|
8
|
+
GroupSelectItemProps,
|
|
9
|
+
ItemContentProps,
|
|
10
|
+
ItemId,
|
|
11
|
+
NextListItemProps,
|
|
12
|
+
SelectionMultipleState,
|
|
13
|
+
SelectionSingleState,
|
|
14
|
+
} from '@snack-uikit/list';
|
|
4
15
|
import { WithSupportProps } from '@snack-uikit/utils';
|
|
5
16
|
|
|
6
17
|
import { BaseChipProps, Size } from '../../types';
|
|
7
18
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
export type AnyType = any;
|
|
21
|
+
|
|
22
|
+
export type ContentRenderProps = Omit<ItemContentProps, 'option' | 'disabled'>;
|
|
23
|
+
|
|
24
|
+
export type FilterOption<T extends ContentRenderProps = ContentRenderProps> =
|
|
25
|
+
// eslint-disable-next-line no-use-before-define
|
|
26
|
+
| BaseOption<T>
|
|
27
|
+
// eslint-disable-next-line no-use-before-define
|
|
28
|
+
| AccordionOption<T>
|
|
29
|
+
// eslint-disable-next-line no-use-before-define
|
|
30
|
+
| GroupOption<T>
|
|
31
|
+
// eslint-disable-next-line no-use-before-define
|
|
32
|
+
| GroupSelectOption<T>
|
|
33
|
+
// eslint-disable-next-line no-use-before-define
|
|
34
|
+
| NestListOption<T>;
|
|
35
|
+
|
|
36
|
+
export type BaseOption<T extends ContentRenderProps = ContentRenderProps> = Omit<BaseItemProps, 'content' | 'id'> & {
|
|
37
|
+
value: ItemId;
|
|
38
|
+
label: ItemId;
|
|
39
|
+
contentRenderProps?: T;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AccordionOption<T extends ContentRenderProps = ContentRenderProps> = Omit<
|
|
43
|
+
BaseOption<T>,
|
|
44
|
+
'switch' | 'inactive' | 'value'
|
|
45
|
+
> & {
|
|
46
|
+
id?: ItemId;
|
|
47
|
+
type: 'collapse';
|
|
48
|
+
options: FilterOption<T>[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type GroupOption<T extends ContentRenderProps = ContentRenderProps> = Omit<GroupItemProps, 'items'> & {
|
|
52
|
+
options: FilterOption<T>[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type GroupSelectOption<T extends ContentRenderProps = ContentRenderProps> = Omit<
|
|
56
|
+
GroupSelectItemProps,
|
|
57
|
+
'items'
|
|
58
|
+
> & {
|
|
59
|
+
options: FilterOption<T>[];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type NestListOption<T extends ContentRenderProps = ContentRenderProps> = Omit<
|
|
63
|
+
NextListItemProps,
|
|
64
|
+
'items' | 'content'
|
|
65
|
+
> & {
|
|
66
|
+
label: ItemId;
|
|
67
|
+
contentRenderProps?: T;
|
|
68
|
+
options: FilterOption<T>[];
|
|
11
69
|
};
|
|
12
70
|
|
|
13
71
|
export type ChipChoiceCommonProps = WithSupportProps<
|
|
@@ -19,7 +77,7 @@ export type ChipChoiceCommonProps = WithSupportProps<
|
|
|
19
77
|
/** Отображение кнопки очистки значения @default true*/
|
|
20
78
|
showClearButton?: boolean;
|
|
21
79
|
/** Расположение выпадающего меню */
|
|
22
|
-
placement?:
|
|
80
|
+
placement?: DropdownProps['placement'];
|
|
23
81
|
/**
|
|
24
82
|
* Стратегия управления шириной контейнера поповера
|
|
25
83
|
* <br> - `auto` - соответствует ширине контента,
|
|
@@ -27,6 +85,46 @@ export type ChipChoiceCommonProps = WithSupportProps<
|
|
|
27
85
|
* <br> - `eq` - Equal, строго равен ширине таргета.
|
|
28
86
|
* @default gte
|
|
29
87
|
*/
|
|
30
|
-
widthStrategy?:
|
|
88
|
+
widthStrategy?: DropdownProps['widthStrategy'];
|
|
89
|
+
dropDownClassName?: string;
|
|
31
90
|
}
|
|
32
91
|
>;
|
|
92
|
+
|
|
93
|
+
export type ChipChoiceSelectCommonProps<T extends ContentRenderProps = ContentRenderProps> = ChipChoiceCommonProps & {
|
|
94
|
+
options: FilterOption<T>[];
|
|
95
|
+
|
|
96
|
+
contentRender?(option: { label: ItemId; value?: ItemId; contentRenderProps?: T }): ReactNode;
|
|
97
|
+
filterFn?(option: { label: ItemId; value?: ItemId; contentRenderProps?: T }): boolean;
|
|
98
|
+
|
|
99
|
+
searchable?: boolean;
|
|
100
|
+
} & Pick<
|
|
101
|
+
DroplistProps,
|
|
102
|
+
| 'selection'
|
|
103
|
+
| 'scrollRef'
|
|
104
|
+
| 'scrollContainerRef'
|
|
105
|
+
| 'noDataState'
|
|
106
|
+
| 'footer'
|
|
107
|
+
| 'footerActiveElementsRefs'
|
|
108
|
+
| 'dataError'
|
|
109
|
+
| 'errorDataState'
|
|
110
|
+
| 'dataFiltered'
|
|
111
|
+
| 'noResultsState'
|
|
112
|
+
| 'loading'
|
|
113
|
+
>;
|
|
114
|
+
|
|
115
|
+
export type ChipChoiceSingleProps<T extends ContentRenderProps = ContentRenderProps> = ChipChoiceSelectCommonProps<T> &
|
|
116
|
+
Omit<SelectionSingleState, 'mode'> & {
|
|
117
|
+
/** Массив опций */
|
|
118
|
+
options: FilterOption<T>[];
|
|
119
|
+
/** Колбек формирующий отображение выбранного значения. Принимает выбранное значение. По умолчанию для отображения используется FilterOption.label */
|
|
120
|
+
valueRender?(option?: BaseOption<T>): ReactNode;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type ChipChoiceMultipleProps<T extends ContentRenderProps = ContentRenderProps> =
|
|
124
|
+
ChipChoiceSelectCommonProps<T> &
|
|
125
|
+
Omit<SelectionMultipleState, 'mode'> & {
|
|
126
|
+
/** Массив опций */
|
|
127
|
+
options: FilterOption<T>[];
|
|
128
|
+
/** Колбек формирующий отображение выбранного значения. Принимает выбранное значение. По умолчанию для отображения используется FilterOption.label */
|
|
129
|
+
valueRender?(option?: BaseOption<T>[]): ReactNode;
|
|
130
|
+
};
|