@snack-uikit/list 0.3.5 → 0.4.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 +11 -2
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FocusEvent, KeyboardEvent, ReactNode, RefObject } from 'react';
|
|
2
2
|
import { DropdownProps } from '@snack-uikit/dropdown';
|
|
3
3
|
import { WithSupportProps } from '@snack-uikit/utils';
|
|
4
|
+
import { EmptyStateProps } from '../../helperComponents';
|
|
4
5
|
import { ScrollProps, SearchState } from '../../types';
|
|
5
6
|
import { ItemProps } from '../Items';
|
|
6
7
|
import { ListContextPrivateType, ListContextType, SelectionState } from './contexts';
|
|
@@ -9,6 +10,16 @@ type CollapseState = {
|
|
|
9
10
|
onChange?(value: (string | number)[]): void;
|
|
10
11
|
defaultValue?: (string | number)[];
|
|
11
12
|
};
|
|
13
|
+
export type EmptyState = {
|
|
14
|
+
dataFiltered?: boolean;
|
|
15
|
+
dataError?: boolean;
|
|
16
|
+
/** Экран при отстутствии данных */
|
|
17
|
+
noDataState?: EmptyStateProps;
|
|
18
|
+
/** Экран при отстутствии результатов поиска или фильтров */
|
|
19
|
+
noResultsState?: EmptyStateProps;
|
|
20
|
+
/** Экран при ошибке запроса */
|
|
21
|
+
errorDataState?: EmptyStateProps;
|
|
22
|
+
};
|
|
12
23
|
export type ListProps = WithSupportProps<{
|
|
13
24
|
/** Основные элементы списка */
|
|
14
25
|
items: ItemProps[];
|
|
@@ -18,19 +29,13 @@ export type ListProps = WithSupportProps<{
|
|
|
18
29
|
pinBottom?: ItemProps[];
|
|
19
30
|
/**
|
|
20
31
|
* Кастомизируемый элемент в конце списка
|
|
21
|
-
* @type
|
|
32
|
+
* @type ReactNode;
|
|
22
33
|
*/
|
|
23
34
|
footer?: ReactNode;
|
|
24
35
|
/** Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка */
|
|
25
36
|
footerActiveElementsRefs?: RefObject<HTMLElement>[];
|
|
26
37
|
/** Настройки поисковой строки */
|
|
27
38
|
search?: SearchState;
|
|
28
|
-
/** Флаг, отвещающий за состояние загрузки списка */
|
|
29
|
-
loading?: boolean;
|
|
30
|
-
/** Текст для состояния "Отсутсвие данных" */
|
|
31
|
-
noData?: string;
|
|
32
|
-
/** Текст для состояния "Отсутсвие результата" при поиске */
|
|
33
|
-
noResults?: string;
|
|
34
39
|
/** Tab Index */
|
|
35
40
|
tabIndex?: number;
|
|
36
41
|
/** Настройки раскрытия элементов */
|
|
@@ -38,12 +43,20 @@ export type ListProps = WithSupportProps<{
|
|
|
38
43
|
/** CSS-класс */
|
|
39
44
|
className?: string;
|
|
40
45
|
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
41
|
-
|
|
46
|
+
/** Флаг, отвещающий за состояние загрузки списка */
|
|
47
|
+
loading?: boolean;
|
|
48
|
+
} & SelectionState & ListContextType & ScrollProps & EmptyState>;
|
|
42
49
|
export type DroplistProps = {
|
|
43
50
|
/** Ссылка на элемент-триггер для дроплиста */
|
|
44
51
|
triggerElemRef?: RefObject<HTMLElement>;
|
|
45
|
-
/** Триггер для дроплиста
|
|
46
|
-
|
|
52
|
+
/** Триггер для дроплиста
|
|
53
|
+
* @type ReactNode | ({onKeyDown}) => ReactNode
|
|
54
|
+
*
|
|
55
|
+
* Рендер функция принимает аргументы `onKeyDown` - хендлер ввода, для поддержки управления с клавиатуры
|
|
56
|
+
*/
|
|
57
|
+
children: ReactNode | ((params: {
|
|
58
|
+
onKeyDown?: (e: KeyboardEvent<HTMLElement>) => void;
|
|
59
|
+
}) => ReactNode);
|
|
47
60
|
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> & Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
|
|
48
61
|
export type ListPrivateProps = ListProps & ListContextPrivateType & {
|
|
49
62
|
nested?: boolean;
|
package/dist/components/index.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { InfoBlockProps } from '@snack-uikit/info-block';
|
|
2
|
+
export type EmptyStateProps = Pick<InfoBlockProps, 'description' | 'icon' | 'data-test-id'>;
|
|
3
|
+
export type ListEmptyState = {
|
|
4
|
+
emptyStates: {
|
|
5
|
+
noDataState: EmptyStateProps;
|
|
6
|
+
noResultsState: EmptyStateProps;
|
|
7
|
+
errorDataState: EmptyStateProps;
|
|
8
|
+
};
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
dataError?: boolean;
|
|
11
|
+
dataFiltered?: boolean;
|
|
12
|
+
itemsLength: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function ListEmptyState({ dataError, dataFiltered, itemsLength, emptyStates, loading }: ListEmptyState): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { InfoBlock } from '@snack-uikit/info-block';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
export function ListEmptyState({ dataError, dataFiltered, itemsLength, emptyStates, loading }) {
|
|
5
|
+
if (itemsLength || loading) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return (_jsxs("div", { className: styles.listEmptyStateWrapper, children: [dataError && _jsx(InfoBlock, Object.assign({}, emptyStates.errorDataState, { size: 's', align: 'vertical' })), !dataError && dataFiltered && (_jsx(InfoBlock, Object.assign({}, emptyStates.noResultsState, { size: 's', align: 'vertical', "data-test-id": 'list__no-results' }))), !dataError && !dataFiltered && (_jsx(InfoBlock, Object.assign({}, emptyStates.noDataState, { size: 's', align: 'vertical', "data-test-id": 'list__no-data' })))] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EmptyStateProps } from './ListEmptyState';
|
|
2
|
+
export declare function useEmptyState({ noDataState: noDataStateProp, noResultsState: noResultsStateProp, errorDataState: errorDataStateProp, }: {
|
|
3
|
+
noDataState?: EmptyStateProps;
|
|
4
|
+
noResultsState?: EmptyStateProps;
|
|
5
|
+
errorDataState?: EmptyStateProps;
|
|
6
|
+
}): {
|
|
7
|
+
noDataState: EmptyStateProps;
|
|
8
|
+
noResultsState: EmptyStateProps;
|
|
9
|
+
errorDataState: EmptyStateProps;
|
|
10
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { CrossSVG } from '@snack-uikit/icons';
|
|
3
|
+
import { useLocale } from '@snack-uikit/locale';
|
|
4
|
+
export function useEmptyState({ noDataState: noDataStateProp, noResultsState: noResultsStateProp, errorDataState: errorDataStateProp, }) {
|
|
5
|
+
const { t } = useLocale('List');
|
|
6
|
+
return useMemo(() => {
|
|
7
|
+
const noDataState = Object.assign({ description: t('noData.description') }, noDataStateProp);
|
|
8
|
+
const noResultsState = Object.assign({ description: t('noResults.description') }, noResultsStateProp);
|
|
9
|
+
const errorDataState = Object.assign({ icon: { icon: CrossSVG, appearance: 'red', decor: true }, description: t('errorData.description') }, errorDataStateProp);
|
|
10
|
+
return {
|
|
11
|
+
noDataState,
|
|
12
|
+
noResultsState,
|
|
13
|
+
errorDataState,
|
|
14
|
+
};
|
|
15
|
+
}, [errorDataStateProp, noDataStateProp, noResultsStateProp, t]);
|
|
16
|
+
}
|
package/dist/hooks.d.ts
ADDED
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import FuzzySearch from 'fuzzy-search';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { isGroupItemProps } from './components/Items';
|
|
4
|
+
function flattenItems(items) {
|
|
5
|
+
const flattenedItems = [];
|
|
6
|
+
function flatten(item) {
|
|
7
|
+
if (!isGroupItemProps(item)) {
|
|
8
|
+
flattenedItems.push(item);
|
|
9
|
+
}
|
|
10
|
+
if ('items' in item) {
|
|
11
|
+
for (const nestedItem of item.items) {
|
|
12
|
+
flatten(nestedItem);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const item of items) {
|
|
17
|
+
flatten(item);
|
|
18
|
+
}
|
|
19
|
+
return flattenedItems;
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_MIN_SEARCH_INPUT_LENGTH = 2;
|
|
22
|
+
export function useFuzzySearch(items, minSearchInputLength) {
|
|
23
|
+
return useCallback((search) => {
|
|
24
|
+
const searcher = new FuzzySearch(flattenItems(items), ['content.option', 'content.caption', 'content.description', 'label'], {});
|
|
25
|
+
return search.length > (minSearchInputLength !== null && minSearchInputLength !== void 0 ? minSearchInputLength : DEFAULT_MIN_SEARCH_INPUT_LENGTH)
|
|
26
|
+
? searcher.search(search)
|
|
27
|
+
: items;
|
|
28
|
+
}, [items, minSearchInputLength]);
|
|
29
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -15,4 +15,7 @@ export declare function extractItemIds(items: ItemProps[]): Array<string | numbe
|
|
|
15
15
|
export declare function extractChildIds({ items }: {
|
|
16
16
|
items: ItemProps[];
|
|
17
17
|
}): Array<string | number>;
|
|
18
|
+
export declare function extractAllChildIds({ items }: {
|
|
19
|
+
items: ItemProps[];
|
|
20
|
+
}): Array<string | number>;
|
|
18
21
|
export {};
|
package/dist/utils.js
CHANGED
|
@@ -71,3 +71,20 @@ export function extractChildIds({ items }) {
|
|
|
71
71
|
return item.id && !isGroupItemProps(item) ? prev.concat([item.id]) : prev;
|
|
72
72
|
}, []);
|
|
73
73
|
}
|
|
74
|
+
export function extractAllChildIds({ items }) {
|
|
75
|
+
return items
|
|
76
|
+
.filter(item => isAccordionItemProps(item) ||
|
|
77
|
+
isNextListItemProps(item) ||
|
|
78
|
+
isGroupItemProps(item) ||
|
|
79
|
+
(isBaseItemProps(item) && !item.inactive))
|
|
80
|
+
.reduce((prev, item) => {
|
|
81
|
+
var _a;
|
|
82
|
+
if (isAccordionItemProps(item) || isNextListItemProps(item)) {
|
|
83
|
+
return prev.concat([(_a = item.id) !== null && _a !== void 0 ? _a : '']).concat(extractAllChildIds({ items: item.items }));
|
|
84
|
+
}
|
|
85
|
+
if (isGroupItemProps(item)) {
|
|
86
|
+
return prev.concat(extractAllChildIds({ items: item.items }));
|
|
87
|
+
}
|
|
88
|
+
return item.id && !isGroupItemProps(item) ? prev.concat([item.id]) : prev;
|
|
89
|
+
}, []);
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "List",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.4.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -35,15 +35,24 @@
|
|
|
35
35
|
"@snack-uikit/divider": "3.0.1",
|
|
36
36
|
"@snack-uikit/dropdown": "0.2.0",
|
|
37
37
|
"@snack-uikit/icons": "0.20.1",
|
|
38
|
+
"@snack-uikit/info-block": "0.2.0",
|
|
38
39
|
"@snack-uikit/loaders": "0.5.1",
|
|
40
|
+
"@snack-uikit/locale": "0.5.0",
|
|
39
41
|
"@snack-uikit/scroll": "0.5.2",
|
|
40
42
|
"@snack-uikit/search-private": "0.1.5",
|
|
41
43
|
"@snack-uikit/toggles": "0.9.6",
|
|
42
44
|
"@snack-uikit/truncate-string": "0.4.8",
|
|
43
45
|
"@snack-uikit/utils": "3.2.0",
|
|
44
46
|
"classnames": "2.5.1",
|
|
47
|
+
"fuzzy-search": "3.2.1",
|
|
45
48
|
"merge-refs": "1.2.2",
|
|
46
49
|
"uncontrollable": "8.0.4"
|
|
47
50
|
},
|
|
48
|
-
"
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/fuzzy-search": "2.1.5"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@snack-uikit/locale": "*"
|
|
56
|
+
},
|
|
57
|
+
"gitHead": "ee199e2cdca1d24db821533512263dfe06365159"
|
|
49
58
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { KeyboardEvent, MouseEvent } from 'react';
|
|
2
2
|
|
|
3
3
|
import { ChevronDownSVG, ChevronUpSVG } from '@snack-uikit/icons';
|
|
4
|
-
import { useToggleGroup } from '@snack-uikit/toggles';
|
|
5
4
|
|
|
6
5
|
import { CollapseBlockPrivate } from '../../../helperComponents';
|
|
7
6
|
import { CollapseContext, useCollapseContext, useParentListContext } from '../../Lists/contexts';
|
|
@@ -11,14 +10,13 @@ import { AccordionItemProps } from '../types';
|
|
|
11
10
|
|
|
12
11
|
export function AccordionItem({ items: itemsProp, id, disabled, ...option }: AccordionItemProps) {
|
|
13
12
|
const { level = 1 } = useCollapseContext();
|
|
14
|
-
const { toggleOpenCollapsedItems } = useParentListContext();
|
|
13
|
+
const { toggleOpenCollapsedItems, openCollapsedItems } = useParentListContext();
|
|
15
14
|
const { isIndeterminate, checked, handleOnSelect } = useGroupItemSelection({ items: itemsProp, id, disabled });
|
|
16
15
|
|
|
17
|
-
const
|
|
16
|
+
const isOpen = Boolean(openCollapsedItems?.includes(id ?? ''));
|
|
18
17
|
|
|
19
18
|
const handleKeyDown = (e: KeyboardEvent<HTMLElement>) => {
|
|
20
19
|
if (e.key === 'ArrowRight') {
|
|
21
|
-
handleChange();
|
|
22
20
|
toggleOpenCollapsedItems?.(id ?? '');
|
|
23
21
|
|
|
24
22
|
e.preventDefault();
|
|
@@ -29,7 +27,6 @@ export function AccordionItem({ items: itemsProp, id, disabled, ...option }: Acc
|
|
|
29
27
|
const itemsJSX = useRenderItems(itemsProp);
|
|
30
28
|
|
|
31
29
|
const handleItemClick = (e: MouseEvent<HTMLElement>) => {
|
|
32
|
-
handleChange();
|
|
33
30
|
toggleOpenCollapsedItems?.(id ?? '');
|
|
34
31
|
option.onClick?.(e);
|
|
35
32
|
};
|
|
@@ -41,7 +38,7 @@ export function AccordionItem({ items: itemsProp, id, disabled, ...option }: Acc
|
|
|
41
38
|
{...option}
|
|
42
39
|
id={id}
|
|
43
40
|
disabled={disabled}
|
|
44
|
-
expandIcon={
|
|
41
|
+
expandIcon={isOpen ? <ChevronUpSVG /> : <ChevronDownSVG />}
|
|
45
42
|
onClick={handleItemClick}
|
|
46
43
|
isParentNode
|
|
47
44
|
onKeyDown={handleKeyDown}
|
|
@@ -49,7 +46,7 @@ export function AccordionItem({ items: itemsProp, id, disabled, ...option }: Acc
|
|
|
49
46
|
onSelect={!disabled ? handleOnSelect : undefined}
|
|
50
47
|
/>
|
|
51
48
|
}
|
|
52
|
-
expanded={
|
|
49
|
+
expanded={isOpen}
|
|
53
50
|
data-test-id={`list__accordion-item-${id}`}
|
|
54
51
|
>
|
|
55
52
|
<CollapseContext.Provider value={{ level: level + 1 }}>{itemsJSX}</CollapseContext.Provider>
|
|
@@ -113,6 +113,7 @@ export function BaseItem({
|
|
|
113
113
|
data-checked={(isParentNode && (indeterminate || isChecked)) || (isChecked && !switchProp) || undefined}
|
|
114
114
|
data-variant={mode || undefined}
|
|
115
115
|
data-open={open || undefined}
|
|
116
|
+
data-disabled={disabled || undefined}
|
|
116
117
|
aria-disabled={disabled || undefined}
|
|
117
118
|
data-parent={parent || 'list'}
|
|
118
119
|
onKeyDown={handleItemKeyDown}
|
|
@@ -147,7 +147,7 @@ $typography: (
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
&[aria-disabled],
|
|
150
|
-
|
|
150
|
+
&[data-disabled] {
|
|
151
151
|
.option,
|
|
152
152
|
.description,
|
|
153
153
|
.caption,
|
|
@@ -155,7 +155,7 @@ $typography: (
|
|
|
155
155
|
color: $sys-neutral-text-disabled;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
.beforeContent {
|
|
159
159
|
opacity: $opacity-a064;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -181,15 +181,12 @@ $typography: (
|
|
|
181
181
|
|
|
182
182
|
&:focus-visible {
|
|
183
183
|
&[data-variant='single'] {
|
|
184
|
-
background-color: $sys-primary-background;
|
|
185
184
|
outline-color: $sys-primary-accent-default;
|
|
186
185
|
}
|
|
187
186
|
}
|
|
188
187
|
|
|
189
|
-
|
|
188
|
+
&[data-disabled] {
|
|
190
189
|
&[data-variant='single'] {
|
|
191
|
-
background-color: $sys-neutral-background1-level;
|
|
192
|
-
|
|
193
190
|
&::before {
|
|
194
191
|
display: none;
|
|
195
192
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRef, RefObject, useEffect, useMemo } from 'react';
|
|
2
2
|
|
|
3
|
-
import { extractChildIds } from '../../utils';
|
|
3
|
+
import { extractAllChildIds, extractChildIds } from '../../utils';
|
|
4
4
|
import { useSelectionContext } from '../Lists/contexts';
|
|
5
5
|
import { AccordionItem } from './AccordionItem';
|
|
6
6
|
import { BaseItem } from './BaseItem';
|
|
@@ -12,20 +12,20 @@ import { addItemsIds, isAccordionItemProps, isGroupItemProps, isNextListItemProp
|
|
|
12
12
|
export function useRenderItems(items: ItemProps[]) {
|
|
13
13
|
return useMemo(
|
|
14
14
|
() =>
|
|
15
|
-
items.map(
|
|
15
|
+
items.map(item => {
|
|
16
16
|
if (isGroupItemProps(item)) {
|
|
17
|
-
return <GroupItem {...item} key={item.
|
|
17
|
+
return <GroupItem {...item} key={item.label} />;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (isAccordionItemProps(item)) {
|
|
21
|
-
return <AccordionItem {...item} key={
|
|
21
|
+
return <AccordionItem {...item} key={item.key} />;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
if (isNextListItemProps(item)) {
|
|
25
|
-
return <NextListItem {...item} key={item.
|
|
25
|
+
return <NextListItem {...item} key={item.key} />;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
return <BaseItem {...item} key={item.
|
|
28
|
+
return <BaseItem {...item} key={item.key} />;
|
|
29
29
|
}),
|
|
30
30
|
[items],
|
|
31
31
|
);
|
|
@@ -63,12 +63,15 @@ type UseGroupItemSelectionProps = {
|
|
|
63
63
|
|
|
64
64
|
export function useGroupItemSelection({ id, items, disabled }: UseGroupItemSelectionProps) {
|
|
65
65
|
const { value, setValue, isSelectionMultiple } = useSelectionContext();
|
|
66
|
-
const childIds = useMemo(
|
|
66
|
+
const { childIds, allChildIds } = useMemo(
|
|
67
|
+
() => ({ childIds: extractChildIds({ items }), allChildIds: extractAllChildIds({ items }) }),
|
|
68
|
+
[items],
|
|
69
|
+
);
|
|
67
70
|
|
|
68
71
|
const isIndeterminate = isSelectionMultiple
|
|
69
|
-
?
|
|
70
|
-
:
|
|
71
|
-
const checked = isSelectionMultiple ?
|
|
72
|
+
? allChildIds.some(childId => value?.includes(childId))
|
|
73
|
+
: allChildIds.includes(value ?? '');
|
|
74
|
+
const checked = isSelectionMultiple ? allChildIds.every(childId => value?.includes(childId)) : undefined;
|
|
72
75
|
|
|
73
76
|
useEffect(() => {
|
|
74
77
|
if (isSelectionMultiple) {
|
|
@@ -45,7 +45,7 @@ $sizes: 's', 'm', 'l';
|
|
|
45
45
|
|
|
46
46
|
background-color: transparent;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
&[data-disabled],
|
|
49
49
|
&:disabled {
|
|
50
50
|
cursor: not-allowed;
|
|
51
51
|
background-color: transparent;
|
|
@@ -61,7 +61,7 @@ $sizes: 's', 'm', 'l';
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
&:not([data-inactive]) {
|
|
64
|
+
&:not([data-inactive], [data-disabled]) {
|
|
65
65
|
&[data-open],
|
|
66
66
|
&[data-focused],
|
|
67
67
|
&:hover,
|
|
@@ -49,14 +49,16 @@ export function getSlicedItems({
|
|
|
49
49
|
|
|
50
50
|
export function addItemsIds(itemsProp: ItemProps[], prefix?: string | number): ItemProps[] {
|
|
51
51
|
return itemsProp.map((item, idx) => {
|
|
52
|
-
const
|
|
52
|
+
const autoId = prefix !== undefined ? [prefix, idx].join('-') : String(idx);
|
|
53
|
+
const itemId = item.id ?? autoId;
|
|
53
54
|
|
|
54
55
|
if (isGroupItemProps(item)) {
|
|
55
|
-
return { ...item, id: itemId, items: addItemsIds(item.items, itemId) };
|
|
56
|
+
return { key: autoId, ...item, id: itemId, items: addItemsIds(item.items, itemId) };
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
if (isAccordionItemProps(item) || isNextListItemProps(item)) {
|
|
59
60
|
return {
|
|
61
|
+
key: autoId,
|
|
60
62
|
...item,
|
|
61
63
|
id: itemId,
|
|
62
64
|
items: addItemsIds(item.items, itemId),
|
|
@@ -64,6 +66,6 @@ export function addItemsIds(itemsProp: ItemProps[], prefix?: string | number): I
|
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
return { ...item, id: itemId, itemRef: item.itemRef || createRef<HTMLElement>() };
|
|
69
|
+
return { key: autoId, ...item, id: itemId, itemRef: item.itemRef || createRef<HTMLElement>() };
|
|
68
70
|
});
|
|
69
71
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cloneElement, isValidElement, KeyboardEvent, useCallback, useMemo, useRef } from 'react';
|
|
1
|
+
import { cloneElement, isValidElement, KeyboardEvent, ReactNode, useCallback, useMemo, useRef } from 'react';
|
|
2
2
|
import { useUncontrolledProp } from 'uncontrollable';
|
|
3
3
|
|
|
4
4
|
import { Dropdown } from '@snack-uikit/dropdown';
|
|
@@ -36,12 +36,9 @@ export function Droplist({
|
|
|
36
36
|
const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp<Array<number | string>>(
|
|
37
37
|
collapse.value,
|
|
38
38
|
collapse.defaultValue ?? [],
|
|
39
|
-
collapse.onChange
|
|
40
|
-
? cb => {
|
|
41
|
-
collapse.onChange?.(cb(collapse.value));
|
|
42
|
-
}
|
|
43
|
-
: undefined,
|
|
39
|
+
collapse.onChange,
|
|
44
40
|
);
|
|
41
|
+
|
|
45
42
|
const { search: searchItem, footerRefs } = useItemsWithIds({
|
|
46
43
|
search: hasSearch,
|
|
47
44
|
footerActiveElementsRefs,
|
|
@@ -83,11 +80,9 @@ export function Droplist({
|
|
|
83
80
|
const [open, setOpen] = useUncontrolledProp<boolean>(openProp, false, onOpenChange);
|
|
84
81
|
|
|
85
82
|
const handleOpenChange = (open: boolean) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
setOpenCollapsedItems([]);
|
|
90
|
-
}
|
|
83
|
+
resetNestedIndex();
|
|
84
|
+
resetActiveFocusIndex();
|
|
85
|
+
setOpenCollapsedItems([]);
|
|
91
86
|
|
|
92
87
|
setOpen(open);
|
|
93
88
|
};
|
|
@@ -109,7 +104,7 @@ export function Droplist({
|
|
|
109
104
|
[setActiveFocusIndex, setOpen],
|
|
110
105
|
);
|
|
111
106
|
|
|
112
|
-
const triggerElem = useMemo(() => {
|
|
107
|
+
const triggerElem: ReactNode = useMemo(() => {
|
|
113
108
|
if (isValidElement(children)) {
|
|
114
109
|
const props = typeof children.props === 'object' ? children.props : {};
|
|
115
110
|
|
|
@@ -120,6 +115,11 @@ export function Droplist({
|
|
|
120
115
|
},
|
|
121
116
|
});
|
|
122
117
|
}
|
|
118
|
+
|
|
119
|
+
if (typeof children === 'function') {
|
|
120
|
+
return children({ onKeyDown });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
123
|
return children;
|
|
124
124
|
}, [onKeyDown, children]);
|
|
125
125
|
|
|
@@ -138,6 +138,7 @@ export function Droplist({
|
|
|
138
138
|
parentRef: listRef,
|
|
139
139
|
parentResetNestedIndex: resetNestedIndex,
|
|
140
140
|
parentResetActiveFocusIndex: resetActiveFocusIndex,
|
|
141
|
+
openCollapsedItems,
|
|
141
142
|
toggleOpenCollapsedItems: id =>
|
|
142
143
|
setOpenCollapsedItems((items: Array<string | number> | undefined = []) =>
|
|
143
144
|
items.includes(id) ? items.filter(item => item !== id) : items?.concat([id]),
|
|
@@ -38,11 +38,7 @@ export const List = forwardRef<HTMLElement, ListProps>(
|
|
|
38
38
|
const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp<Array<number | string>>(
|
|
39
39
|
collapse.value,
|
|
40
40
|
collapse.defaultValue ?? [],
|
|
41
|
-
collapse.onChange
|
|
42
|
-
? cb => {
|
|
43
|
-
collapse.onChange?.(cb(collapse.value));
|
|
44
|
-
}
|
|
45
|
-
: undefined,
|
|
41
|
+
collapse.onChange,
|
|
46
42
|
);
|
|
47
43
|
|
|
48
44
|
const { search: searchItem, footerRefs } = useItemsWithIds({
|