@snack-uikit/list 0.3.5 → 0.4.1-preview-2484f987.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 +23 -0
- package/README.md +28 -10
- package/dist/components/Items/AccordionItem/AccordionItem.js +3 -6
- package/dist/components/Items/BaseItem/BaseItem.js +1 -1
- package/dist/components/Items/BaseItem/styles.module.css +7 -11
- package/dist/components/Items/hooks.js +10 -10
- package/dist/components/Items/styles.module.css +5 -5
- package/dist/components/Items/types.d.ts +1 -0
- package/dist/components/Items/utils.js +5 -4
- package/dist/components/Lists/Droplist/DropList.js +8 -11
- package/dist/components/Lists/List/List.d.ts +3 -5
- package/dist/components/Lists/List/List.js +1 -6
- package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +3 -5
- package/dist/components/Lists/ListPrivate/ListPrivate.js +10 -10
- package/dist/components/Lists/contexts/SelectionProvider.d.ts +3 -3
- package/dist/components/Lists/contexts/SelectionProvider.js +14 -11
- package/dist/components/Lists/index.d.ts +1 -1
- package/dist/components/Lists/styles.module.css +0 -14
- package/dist/components/Lists/types.d.ts +23 -10
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/helperComponents/ListEmptyState/ListEmptyState.d.ts +14 -0
- package/dist/helperComponents/ListEmptyState/ListEmptyState.js +9 -0
- package/dist/helperComponents/ListEmptyState/hooks.d.ts +10 -0
- package/dist/helperComponents/ListEmptyState/hooks.js +16 -0
- package/dist/helperComponents/ListEmptyState/index.d.ts +2 -0
- package/dist/helperComponents/ListEmptyState/index.js +2 -0
- package/dist/helperComponents/ListEmptyState/styles.module.css +8 -0
- package/dist/helperComponents/index.d.ts +1 -0
- package/dist/helperComponents/index.js +1 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.js +29 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +17 -0
- package/package.json +12 -3
- package/src/components/Items/AccordionItem/AccordionItem.tsx +4 -7
- package/src/components/Items/BaseItem/BaseItem.tsx +1 -0
- package/src/components/Items/BaseItem/styles.module.scss +3 -6
- package/src/components/Items/hooks.tsx +13 -10
- package/src/components/Items/styles.module.scss +2 -2
- package/src/components/Items/types.ts +2 -0
- package/src/components/Items/utils.ts +5 -3
- package/src/components/Lists/Droplist/DropList.tsx +13 -12
- package/src/components/Lists/List/List.tsx +1 -5
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +50 -48
- package/src/components/Lists/contexts/SelectionProvider.tsx +11 -8
- package/src/components/Lists/index.ts +3 -0
- package/src/components/Lists/styles.module.scss +0 -12
- package/src/components/Lists/types.ts +27 -10
- package/src/components/index.ts +2 -0
- package/src/helperComponents/ListEmptyState/ListEmptyState.tsx +35 -0
- package/src/helperComponents/ListEmptyState/hooks.ts +42 -0
- package/src/helperComponents/ListEmptyState/index.ts +2 -0
- package/src/helperComponents/ListEmptyState/styles.module.scss +11 -0
- package/src/helperComponents/index.ts +1 -0
- package/src/hooks.ts +45 -0
- package/src/index.ts +3 -0
- package/src/utils.ts +25 -0
|
@@ -3,9 +3,9 @@ import { ForwardedRef, forwardRef, RefObject, useMemo } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { Spinner } from '@snack-uikit/loaders';
|
|
5
5
|
import { Scroll } from '@snack-uikit/scroll';
|
|
6
|
-
import { ToggleGroup } from '@snack-uikit/toggles';
|
|
7
6
|
import { extractSupportProps } from '@snack-uikit/utils';
|
|
8
7
|
|
|
8
|
+
import { ListEmptyState, useEmptyState } from '../../../helperComponents';
|
|
9
9
|
import { PinBottomGroupItem, PinTopGroupItem, SearchItem, useRenderItems } from '../../Items';
|
|
10
10
|
import { ListContextProvider } from '../contexts';
|
|
11
11
|
import commonStyles from '../styles.module.scss';
|
|
@@ -30,13 +30,16 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
30
30
|
scrollContainerRef,
|
|
31
31
|
footer,
|
|
32
32
|
loading,
|
|
33
|
-
noData = 'No data',
|
|
34
|
-
noResults = 'No results',
|
|
35
33
|
size = 's',
|
|
36
|
-
marker,
|
|
34
|
+
marker = true,
|
|
37
35
|
limitedScrollHeight,
|
|
38
36
|
className,
|
|
39
37
|
parent = 'list',
|
|
38
|
+
noDataState,
|
|
39
|
+
noResultsState,
|
|
40
|
+
errorDataState,
|
|
41
|
+
dataError,
|
|
42
|
+
dataFiltered,
|
|
40
43
|
...props
|
|
41
44
|
},
|
|
42
45
|
ref,
|
|
@@ -44,7 +47,10 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
44
47
|
const itemsJSX = useRenderItems(items);
|
|
45
48
|
const itemsPinTopJSX = useRenderItems(pinTop ?? []);
|
|
46
49
|
const itemsPinBottomJSX = useRenderItems(pinBottom ?? []);
|
|
47
|
-
|
|
50
|
+
|
|
51
|
+
const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
|
|
52
|
+
|
|
53
|
+
const hasNoItems = items.length === 0;
|
|
48
54
|
|
|
49
55
|
const loadingJSX = useMemo(
|
|
50
56
|
() =>
|
|
@@ -68,19 +74,17 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
68
74
|
<div className={styles.content}>
|
|
69
75
|
{itemsJSX}
|
|
70
76
|
{loadingJSX}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
</div>
|
|
80
|
-
)}
|
|
77
|
+
|
|
78
|
+
<ListEmptyState
|
|
79
|
+
loading={loading}
|
|
80
|
+
dataError={dataError}
|
|
81
|
+
emptyStates={emptyStates}
|
|
82
|
+
itemsLength={items.length}
|
|
83
|
+
dataFiltered={dataFiltered ?? Boolean(search?.value)}
|
|
84
|
+
/>
|
|
81
85
|
</div>
|
|
82
86
|
),
|
|
83
|
-
[
|
|
87
|
+
[dataError, dataFiltered, emptyStates, items.length, itemsJSX, loading, loadingJSX, search?.value],
|
|
84
88
|
);
|
|
85
89
|
|
|
86
90
|
const listJSX = (
|
|
@@ -95,38 +99,36 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
95
99
|
role='menu'
|
|
96
100
|
{...extractSupportProps(props)}
|
|
97
101
|
>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
{footer && <div className={styles.footer}>{footer}</div>}
|
|
129
|
-
</ToggleGroup>
|
|
102
|
+
{(Number(pinTop?.length) > 0 || search) && (
|
|
103
|
+
<PinTopGroupItem>
|
|
104
|
+
{search && <SearchItem search={search} />}
|
|
105
|
+
|
|
106
|
+
{Number(pinTop?.length) > 0 && itemsPinTopJSX}
|
|
107
|
+
</PinTopGroupItem>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
{scroll ? (
|
|
111
|
+
<Scroll
|
|
112
|
+
className={cn({
|
|
113
|
+
[commonStyles.scrollContainerS]: scroll && limitedScrollHeight && size === 's',
|
|
114
|
+
[commonStyles.scrollContainerM]: scroll && limitedScrollHeight && size === 'm',
|
|
115
|
+
[commonStyles.scrollContainerL]: scroll && limitedScrollHeight && size === 'l',
|
|
116
|
+
})}
|
|
117
|
+
barHideStrategy='never'
|
|
118
|
+
size={size === 's' ? 's' : 'm'}
|
|
119
|
+
ref={scrollContainerRef}
|
|
120
|
+
>
|
|
121
|
+
{content}
|
|
122
|
+
|
|
123
|
+
<div className={styles.scrollStub} ref={scrollRef as RefObject<HTMLDivElement>} />
|
|
124
|
+
</Scroll>
|
|
125
|
+
) : (
|
|
126
|
+
<>{content}</>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{Number(pinBottom?.length) > 0 && <PinBottomGroupItem>{itemsPinBottomJSX}</PinBottomGroupItem>}
|
|
130
|
+
|
|
131
|
+
{footer && <div className={styles.footer}>{footer}</div>}
|
|
130
132
|
</ul>
|
|
131
133
|
);
|
|
132
134
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createContext, ReactNode, useCallback, useContext } from 'react';
|
|
2
2
|
import { useUncontrolledProp } from 'uncontrollable';
|
|
3
3
|
|
|
4
|
-
type SelectionSingleValueType = string | number | undefined;
|
|
4
|
+
export type SelectionSingleValueType = string | number | undefined;
|
|
5
5
|
|
|
6
|
-
type SelectionSingleState = {
|
|
6
|
+
export type SelectionSingleState = {
|
|
7
7
|
/** Начальное состояние */
|
|
8
8
|
defaultValue?: SelectionSingleValueType;
|
|
9
9
|
/** Controlled состояние */
|
|
@@ -24,7 +24,7 @@ export type SelectionSingleProps = {
|
|
|
24
24
|
isSelectionMultiple: false;
|
|
25
25
|
} & SelectionSingleState;
|
|
26
26
|
|
|
27
|
-
type SelectionMultipleState = {
|
|
27
|
+
export type SelectionMultipleState = {
|
|
28
28
|
/** Начальное состояние */
|
|
29
29
|
defaultValue?: SelectionSingleValueType[];
|
|
30
30
|
/** Controlled состояние */
|
|
@@ -128,12 +128,10 @@ function SelectionMultipleProvider({
|
|
|
128
128
|
onChange: onChangeProp,
|
|
129
129
|
children,
|
|
130
130
|
}: SelectionMultipleProps & Child) {
|
|
131
|
-
const [value, setValue] = useUncontrolledProp<SelectionSingleValueType[]>(valueProp, defaultValue,
|
|
132
|
-
onChangeProp?.(cb(value));
|
|
133
|
-
});
|
|
131
|
+
const [value, setValue] = useUncontrolledProp<SelectionSingleValueType[]>(valueProp, defaultValue, onChangeProp);
|
|
134
132
|
|
|
135
133
|
const onChange = useCallback(
|
|
136
|
-
(newValue: SelectionSingleValueType) =>
|
|
134
|
+
(newValue: SelectionSingleValueType) => {
|
|
137
135
|
setValue((oldValues: SelectionSingleValueType[]) => {
|
|
138
136
|
if (Array.isArray(oldValues)) {
|
|
139
137
|
if (oldValues.includes(newValue)) {
|
|
@@ -143,8 +141,13 @@ function SelectionMultipleProvider({
|
|
|
143
141
|
return oldValues.concat(newValue);
|
|
144
142
|
}
|
|
145
143
|
|
|
144
|
+
if (oldValues === undefined) {
|
|
145
|
+
return Array.isArray(newValue) ? newValue : [newValue];
|
|
146
|
+
}
|
|
147
|
+
|
|
146
148
|
return undefined;
|
|
147
|
-
})
|
|
149
|
+
});
|
|
150
|
+
},
|
|
148
151
|
[setValue],
|
|
149
152
|
);
|
|
150
153
|
|
|
@@ -3,8 +3,11 @@ export * from './List';
|
|
|
3
3
|
|
|
4
4
|
export type { ListProps, DroplistProps } from './types';
|
|
5
5
|
export type {
|
|
6
|
+
SelectionSingleState,
|
|
7
|
+
SelectionMultipleState,
|
|
6
8
|
SelectionSingleProps,
|
|
7
9
|
SelectionMultipleProps,
|
|
8
10
|
isSelectionSingleProps,
|
|
9
11
|
isSelectionMultipleProps,
|
|
12
|
+
SelectionSingleValueType,
|
|
10
13
|
} from './contexts';
|
|
@@ -64,15 +64,3 @@
|
|
|
64
64
|
height: auto;
|
|
65
65
|
max-height: calc($dimension-1m * 48); // 384px
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
.infoBlock {
|
|
69
|
-
@include composite-var($sans-body-s);
|
|
70
|
-
|
|
71
|
-
display: flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
justify-content: center;
|
|
74
|
-
|
|
75
|
-
padding: $dimension-1m;
|
|
76
|
-
|
|
77
|
-
color: $sys-neutral-text-support;
|
|
78
|
-
}
|
|
@@ -3,6 +3,7 @@ import { FocusEvent, KeyboardEvent, ReactNode, RefObject } from 'react';
|
|
|
3
3
|
import { DropdownProps } from '@snack-uikit/dropdown';
|
|
4
4
|
import { WithSupportProps } from '@snack-uikit/utils';
|
|
5
5
|
|
|
6
|
+
import { EmptyStateProps } from '../../helperComponents';
|
|
6
7
|
import { ScrollProps, SearchState } from '../../types';
|
|
7
8
|
import { ItemProps } from '../Items';
|
|
8
9
|
import { ListContextPrivateType, ListContextType, SelectionState } from './contexts';
|
|
@@ -13,6 +14,18 @@ type CollapseState = {
|
|
|
13
14
|
defaultValue?: (string | number)[];
|
|
14
15
|
};
|
|
15
16
|
|
|
17
|
+
export type EmptyState = {
|
|
18
|
+
dataFiltered?: boolean;
|
|
19
|
+
dataError?: boolean;
|
|
20
|
+
|
|
21
|
+
/** Экран при отстутствии данных */
|
|
22
|
+
noDataState?: EmptyStateProps;
|
|
23
|
+
/** Экран при отстутствии результатов поиска или фильтров */
|
|
24
|
+
noResultsState?: EmptyStateProps;
|
|
25
|
+
/** Экран при ошибке запроса */
|
|
26
|
+
errorDataState?: EmptyStateProps;
|
|
27
|
+
};
|
|
28
|
+
|
|
16
29
|
export type ListProps = WithSupportProps<
|
|
17
30
|
{
|
|
18
31
|
/** Основные элементы списка */
|
|
@@ -23,19 +36,14 @@ export type ListProps = WithSupportProps<
|
|
|
23
36
|
pinBottom?: ItemProps[];
|
|
24
37
|
/**
|
|
25
38
|
* Кастомизируемый элемент в конце списка
|
|
26
|
-
* @type
|
|
39
|
+
* @type ReactNode;
|
|
27
40
|
*/
|
|
28
41
|
footer?: ReactNode;
|
|
29
42
|
/** Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка */
|
|
30
43
|
footerActiveElementsRefs?: RefObject<HTMLElement>[];
|
|
31
44
|
/** Настройки поисковой строки */
|
|
32
45
|
search?: SearchState;
|
|
33
|
-
|
|
34
|
-
loading?: boolean;
|
|
35
|
-
/** Текст для состояния "Отсутсвие данных" */
|
|
36
|
-
noData?: string;
|
|
37
|
-
/** Текст для состояния "Отсутсвие результата" при поиске */
|
|
38
|
-
noResults?: string;
|
|
46
|
+
|
|
39
47
|
/** Tab Index */
|
|
40
48
|
tabIndex?: number;
|
|
41
49
|
/** Настройки раскрытия элементов */
|
|
@@ -43,16 +51,25 @@ export type ListProps = WithSupportProps<
|
|
|
43
51
|
/** CSS-класс */
|
|
44
52
|
className?: string;
|
|
45
53
|
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
54
|
+
|
|
55
|
+
/** Флаг, отвещающий за состояние загрузки списка */
|
|
56
|
+
loading?: boolean;
|
|
46
57
|
} & SelectionState &
|
|
47
58
|
ListContextType &
|
|
48
|
-
ScrollProps
|
|
59
|
+
ScrollProps &
|
|
60
|
+
EmptyState
|
|
49
61
|
>;
|
|
50
62
|
|
|
51
63
|
export type DroplistProps = {
|
|
52
64
|
/** Ссылка на элемент-триггер для дроплиста */
|
|
53
65
|
triggerElemRef?: RefObject<HTMLElement>;
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
|
|
67
|
+
/** Триггер для дроплиста
|
|
68
|
+
* @type ReactNode | ({onKeyDown}) => ReactNode
|
|
69
|
+
*
|
|
70
|
+
* Рендер функция принимает аргументы `onKeyDown` - хендлер ввода, для поддержки управления с клавиатуры
|
|
71
|
+
*/
|
|
72
|
+
children: ReactNode | ((params: { onKeyDown?: (e: KeyboardEvent<HTMLElement>) => void }) => ReactNode);
|
|
56
73
|
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> &
|
|
57
74
|
Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
|
|
58
75
|
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { InfoBlock, InfoBlockProps } from '@snack-uikit/info-block';
|
|
2
|
+
|
|
3
|
+
import styles from './styles.module.scss';
|
|
4
|
+
|
|
5
|
+
export type EmptyStateProps = Pick<InfoBlockProps, 'description' | 'icon' | 'data-test-id'>;
|
|
6
|
+
|
|
7
|
+
export type ListEmptyState = {
|
|
8
|
+
emptyStates: {
|
|
9
|
+
noDataState: EmptyStateProps;
|
|
10
|
+
noResultsState: EmptyStateProps;
|
|
11
|
+
errorDataState: EmptyStateProps;
|
|
12
|
+
};
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
dataError?: boolean;
|
|
15
|
+
dataFiltered?: boolean;
|
|
16
|
+
itemsLength: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function ListEmptyState({ dataError, dataFiltered, itemsLength, emptyStates, loading }: ListEmptyState) {
|
|
20
|
+
if (itemsLength || loading) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className={styles.listEmptyStateWrapper}>
|
|
26
|
+
{dataError && <InfoBlock {...emptyStates.errorDataState} size='s' align='vertical' />}
|
|
27
|
+
{!dataError && dataFiltered && (
|
|
28
|
+
<InfoBlock {...emptyStates.noResultsState} size='s' align='vertical' data-test-id='list__no-results' />
|
|
29
|
+
)}
|
|
30
|
+
{!dataError && !dataFiltered && (
|
|
31
|
+
<InfoBlock {...emptyStates.noDataState} size='s' align='vertical' data-test-id='list__no-data' />
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { CrossSVG } from '@snack-uikit/icons';
|
|
4
|
+
import { useLocale } from '@snack-uikit/locale';
|
|
5
|
+
|
|
6
|
+
import { EmptyStateProps } from './ListEmptyState';
|
|
7
|
+
|
|
8
|
+
export function useEmptyState({
|
|
9
|
+
noDataState: noDataStateProp,
|
|
10
|
+
noResultsState: noResultsStateProp,
|
|
11
|
+
errorDataState: errorDataStateProp,
|
|
12
|
+
}: {
|
|
13
|
+
noDataState?: EmptyStateProps;
|
|
14
|
+
noResultsState?: EmptyStateProps;
|
|
15
|
+
errorDataState?: EmptyStateProps;
|
|
16
|
+
}) {
|
|
17
|
+
const { t } = useLocale('List');
|
|
18
|
+
|
|
19
|
+
return useMemo(() => {
|
|
20
|
+
const noDataState: EmptyStateProps = {
|
|
21
|
+
description: t('noData.description'),
|
|
22
|
+
...noDataStateProp,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const noResultsState: EmptyStateProps = {
|
|
26
|
+
description: t('noResults.description'),
|
|
27
|
+
...noResultsStateProp,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const errorDataState: EmptyStateProps = {
|
|
31
|
+
icon: { icon: CrossSVG, appearance: 'red', decor: true },
|
|
32
|
+
description: t('errorData.description'),
|
|
33
|
+
...errorDataStateProp,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
noDataState,
|
|
38
|
+
noResultsState,
|
|
39
|
+
errorDataState,
|
|
40
|
+
};
|
|
41
|
+
}, [errorDataStateProp, noDataStateProp, noResultsStateProp, t]);
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
2
|
+
|
|
3
|
+
.listEmptyStateWrapper {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
padding: $dimension-1m;
|
|
11
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import FuzzySearch from 'fuzzy-search';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
import { isGroupItemProps, ItemProps } from './components/Items';
|
|
5
|
+
|
|
6
|
+
function flattenItems(items: ItemProps[]): ItemProps[] {
|
|
7
|
+
const flattenedItems: ItemProps[] = [];
|
|
8
|
+
|
|
9
|
+
function flatten(item: ItemProps) {
|
|
10
|
+
if (!isGroupItemProps(item)) {
|
|
11
|
+
flattenedItems.push(item);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if ('items' in item) {
|
|
15
|
+
for (const nestedItem of item.items) {
|
|
16
|
+
flatten(nestedItem);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const item of items) {
|
|
22
|
+
flatten(item);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return flattenedItems;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 2;
|
|
29
|
+
|
|
30
|
+
export function useFuzzySearch(items: ItemProps[], minSearchInputLength?: number) {
|
|
31
|
+
return useCallback(
|
|
32
|
+
(search: string) => {
|
|
33
|
+
const searcher = new FuzzySearch(
|
|
34
|
+
flattenItems(items),
|
|
35
|
+
['content.option', 'content.caption', 'content.description', 'label'],
|
|
36
|
+
{},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return search.length > (minSearchInputLength ?? DEFAULT_MIN_SEARCH_INPUT_LENGTH)
|
|
40
|
+
? searcher.search(search)
|
|
41
|
+
: items;
|
|
42
|
+
},
|
|
43
|
+
[items, minSearchInputLength],
|
|
44
|
+
);
|
|
45
|
+
}
|
package/src/index.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -113,3 +113,28 @@ export function extractChildIds({ items }: { items: ItemProps[] }): Array<string
|
|
|
113
113
|
[] as Array<string | number>,
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
export function extractAllChildIds({ items }: { items: ItemProps[] }): Array<string | number> {
|
|
118
|
+
return items
|
|
119
|
+
.filter(
|
|
120
|
+
item =>
|
|
121
|
+
isAccordionItemProps(item) ||
|
|
122
|
+
isNextListItemProps(item) ||
|
|
123
|
+
isGroupItemProps(item) ||
|
|
124
|
+
(isBaseItemProps(item) && !item.inactive),
|
|
125
|
+
)
|
|
126
|
+
.reduce(
|
|
127
|
+
(prev: Array<string | number>, item: ItemProps) => {
|
|
128
|
+
if (isAccordionItemProps(item) || isNextListItemProps(item)) {
|
|
129
|
+
return prev.concat([item.id ?? '']).concat(extractAllChildIds({ items: item.items }));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (isGroupItemProps(item)) {
|
|
133
|
+
return prev.concat(extractAllChildIds({ items: item.items }));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return item.id && !isGroupItemProps(item) ? prev.concat([item.id]) : prev;
|
|
137
|
+
},
|
|
138
|
+
[] as Array<string | number>,
|
|
139
|
+
);
|
|
140
|
+
}
|