@snack-uikit/list 0.1.1-preview-57c837e3.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 +21 -0
- package/LICENSE +201 -0
- package/README.md +73 -0
- package/dist/components/Items/AccordionItem/AccordionItem.d.ts +2 -0
- package/dist/components/Items/AccordionItem/AccordionItem.js +41 -0
- package/dist/components/Items/AccordionItem/index.d.ts +1 -0
- package/dist/components/Items/AccordionItem/index.js +1 -0
- package/dist/components/Items/BaseItem/BaseItem.d.ts +8 -0
- package/dist/components/Items/BaseItem/BaseItem.js +67 -0
- package/dist/components/Items/BaseItem/constants.d.ts +2 -0
- package/dist/components/Items/BaseItem/constants.js +5 -0
- package/dist/components/Items/BaseItem/index.d.ts +1 -0
- package/dist/components/Items/BaseItem/index.js +1 -0
- package/dist/components/Items/BaseItem/styles.module.css +205 -0
- package/dist/components/Items/GroupItem/GroupItem.d.ts +2 -0
- package/dist/components/Items/GroupItem/GroupItem.js +9 -0
- package/dist/components/Items/GroupItem/index.d.ts +1 -0
- package/dist/components/Items/GroupItem/index.js +1 -0
- package/dist/components/Items/NextListItem/NextListItem.d.ts +2 -0
- package/dist/components/Items/NextListItem/NextListItem.js +66 -0
- package/dist/components/Items/NextListItem/hooks.d.ts +19 -0
- package/dist/components/Items/NextListItem/hooks.js +80 -0
- package/dist/components/Items/NextListItem/index.d.ts +1 -0
- package/dist/components/Items/NextListItem/index.js +1 -0
- package/dist/components/Items/PinGroupItem/PinGroupItem.d.ts +7 -0
- package/dist/components/Items/PinGroupItem/PinGroupItem.js +15 -0
- package/dist/components/Items/PinGroupItem/index.d.ts +1 -0
- package/dist/components/Items/PinGroupItem/index.js +1 -0
- package/dist/components/Items/PinGroupItem/styles.module.css +31 -0
- package/dist/components/Items/SearchItem/SearchItem.d.ts +5 -0
- package/dist/components/Items/SearchItem/SearchItem.js +28 -0
- package/dist/components/Items/SearchItem/index.d.ts +1 -0
- package/dist/components/Items/SearchItem/index.js +1 -0
- package/dist/components/Items/SearchItem/styles.module.css +11 -0
- package/dist/components/Items/hooks.d.ts +22 -0
- package/dist/components/Items/hooks.js +61 -0
- package/dist/components/Items/index.d.ts +9 -0
- package/dist/components/Items/index.js +9 -0
- package/dist/components/Items/styles.module.css +74 -0
- package/dist/components/Items/types.d.ts +43 -0
- package/dist/components/Items/types.js +1 -0
- package/dist/components/Items/utils.d.ts +17 -0
- package/dist/components/Items/utils.js +42 -0
- package/dist/components/Lists/Droplist/DropList.d.ts +2 -0
- package/dist/components/Lists/Droplist/DropList.js +97 -0
- package/dist/components/Lists/Droplist/index.d.ts +1 -0
- package/dist/components/Lists/Droplist/index.js +1 -0
- package/dist/components/Lists/List/List.d.ts +14 -0
- package/dist/components/Lists/List/List.js +77 -0
- package/dist/components/Lists/List/index.d.ts +1 -0
- package/dist/components/Lists/List/index.js +1 -0
- package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +22 -0
- package/dist/components/Lists/ListPrivate/ListPrivate.js +41 -0
- package/dist/components/Lists/ListPrivate/index.d.ts +1 -0
- package/dist/components/Lists/ListPrivate/index.js +1 -0
- package/dist/components/Lists/ListPrivate/styles.module.css +46 -0
- package/dist/components/Lists/contexts/CollapseProvider.d.ts +6 -0
- package/dist/components/Lists/contexts/CollapseProvider.js +3 -0
- package/dist/components/Lists/contexts/ListProvider.d.ts +12 -0
- package/dist/components/Lists/contexts/ListProvider.js +22 -0
- package/dist/components/Lists/contexts/ParentListProvider.d.ts +18 -0
- package/dist/components/Lists/contexts/ParentListProvider.js +12 -0
- package/dist/components/Lists/contexts/SelectionProvider.d.ts +53 -0
- package/dist/components/Lists/contexts/SelectionProvider.js +61 -0
- package/dist/components/Lists/contexts/index.d.ts +4 -0
- package/dist/components/Lists/contexts/index.js +4 -0
- package/dist/components/Lists/hooks.d.ts +16 -0
- package/dist/components/Lists/hooks.js +73 -0
- package/dist/components/Lists/index.d.ts +4 -0
- package/dist/components/Lists/index.js +2 -0
- package/dist/components/Lists/styles.module.css +70 -0
- package/dist/components/Lists/types.d.ts +35 -0
- package/dist/components/Lists/types.js +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -0
- package/dist/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.d.ts +9 -0
- package/dist/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.js +20 -0
- package/dist/helperComponents/CollapseBlockPrivate/index.d.ts +1 -0
- package/dist/helperComponents/CollapseBlockPrivate/index.js +1 -0
- package/dist/helperComponents/CollapseBlockPrivate/styles.module.css +38 -0
- package/dist/helperComponents/HiddenTabButton/HiddenTabButton.d.ts +6 -0
- package/dist/helperComponents/HiddenTabButton/HiddenTabButton.js +17 -0
- package/dist/helperComponents/HiddenTabButton/index.d.ts +1 -0
- package/dist/helperComponents/HiddenTabButton/index.js +1 -0
- package/dist/helperComponents/HiddenTabButton/styles.module.css +9 -0
- package/dist/helperComponents/Separator/Separator.d.ts +7 -0
- package/dist/helperComponents/Separator/Separator.js +12 -0
- package/dist/helperComponents/Separator/index.d.ts +1 -0
- package/dist/helperComponents/Separator/index.js +1 -0
- package/dist/helperComponents/Separator/styles.module.css +112 -0
- package/dist/helperComponents/index.d.ts +3 -0
- package/dist/helperComponents/index.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.js +69 -0
- package/package.json +48 -0
- package/src/components/Items/AccordionItem/AccordionItem.tsx +60 -0
- package/src/components/Items/AccordionItem/index.ts +1 -0
- package/src/components/Items/BaseItem/BaseItem.tsx +150 -0
- package/src/components/Items/BaseItem/constants.ts +7 -0
- package/src/components/Items/BaseItem/index.ts +1 -0
- package/src/components/Items/BaseItem/styles.module.scss +190 -0
- package/src/components/Items/GroupItem/GroupItem.tsx +17 -0
- package/src/components/Items/GroupItem/index.ts +1 -0
- package/src/components/Items/NextListItem/NextListItem.tsx +131 -0
- package/src/components/Items/NextListItem/hooks.ts +105 -0
- package/src/components/Items/NextListItem/index.ts +1 -0
- package/src/components/Items/PinGroupItem/PinGroupItem.tsx +28 -0
- package/src/components/Items/PinGroupItem/index.ts +1 -0
- package/src/components/Items/PinGroupItem/styles.module.scss +38 -0
- package/src/components/Items/SearchItem/SearchItem.tsx +52 -0
- package/src/components/Items/SearchItem/index.ts +1 -0
- package/src/components/Items/SearchItem/styles.module.scss +13 -0
- package/src/components/Items/hooks.tsx +101 -0
- package/src/components/Items/index.ts +9 -0
- package/src/components/Items/styles.module.scss +78 -0
- package/src/components/Items/types.ts +61 -0
- package/src/components/Items/utils.ts +69 -0
- package/src/components/Lists/Droplist/DropList.tsx +166 -0
- package/src/components/Lists/Droplist/index.ts +1 -0
- package/src/components/Lists/List/List.tsx +120 -0
- package/src/components/Lists/List/index.ts +1 -0
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +149 -0
- package/src/components/Lists/ListPrivate/index.ts +1 -0
- package/src/components/Lists/ListPrivate/styles.module.scss +48 -0
- package/src/components/Lists/contexts/CollapseProvider.ts +9 -0
- package/src/components/Lists/contexts/ListProvider.tsx +24 -0
- package/src/components/Lists/contexts/ParentListProvider.tsx +35 -0
- package/src/components/Lists/contexts/SelectionProvider.tsx +171 -0
- package/src/components/Lists/contexts/index.ts +4 -0
- package/src/components/Lists/hooks.ts +99 -0
- package/src/components/Lists/index.ts +10 -0
- package/src/components/Lists/styles.module.scss +73 -0
- package/src/components/Lists/types.ts +47 -0
- package/src/components/index.ts +3 -0
- package/src/helperComponents/CollapseBlockPrivate/CollapseBlockPrivate.tsx +34 -0
- package/src/helperComponents/CollapseBlockPrivate/index.ts +1 -0
- package/src/helperComponents/CollapseBlockPrivate/styles.module.scss +51 -0
- package/src/helperComponents/HiddenTabButton/HiddenTabButton.tsx +27 -0
- package/src/helperComponents/HiddenTabButton/index.ts +1 -0
- package/src/helperComponents/HiddenTabButton/styles.module.scss +11 -0
- package/src/helperComponents/Separator/Separator.tsx +33 -0
- package/src/helperComponents/Separator/index.ts +1 -0
- package/src/helperComponents/Separator/styles.module.scss +63 -0
- package/src/helperComponents/index.ts +3 -0
- package/src/index.ts +1 -0
- package/src/types.ts +14 -0
- package/src/utils.ts +106 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { cloneElement, isValidElement, useCallback, useMemo, useRef, useState } from 'react';
|
|
14
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
15
|
+
import { Dropdown } from '@snack-uikit/dropdown';
|
|
16
|
+
import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
|
|
17
|
+
import { addItemsIds, getSlicedItems, useItemsWithIds } from '../../Items';
|
|
18
|
+
import { extractSelectionProps, ParentListContext, SelectionProvider } from '../contexts';
|
|
19
|
+
import { useKeyboardNavigation } from '../hooks';
|
|
20
|
+
import { ListPrivate } from '../ListPrivate';
|
|
21
|
+
export function Droplist(_a) {
|
|
22
|
+
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange"]);
|
|
23
|
+
const hasSearch = useMemo(() => Boolean(search), [search]);
|
|
24
|
+
const memorizedItems = useMemo(() => addItemsIds((pinBottom !== null && pinBottom !== void 0 ? pinBottom : []).concat(itemsProp).concat(pinTop !== null && pinTop !== void 0 ? pinTop : [])), [itemsProp, pinBottom, pinTop]);
|
|
25
|
+
const [openCollapsedItems, setOpenCollapsedItems] = useState([]);
|
|
26
|
+
const { search: searchItem, footerRefs } = useItemsWithIds({
|
|
27
|
+
search: hasSearch,
|
|
28
|
+
footerActiveElementsRefs,
|
|
29
|
+
});
|
|
30
|
+
const { items, itemRefs, ids, expandedIds } = useMemo(() => {
|
|
31
|
+
const res = withCollapsedItems({
|
|
32
|
+
items: memorizedItems,
|
|
33
|
+
openCollapsedItems,
|
|
34
|
+
});
|
|
35
|
+
const items = searchItem.concat(res.items).concat(footerRefs);
|
|
36
|
+
return {
|
|
37
|
+
items,
|
|
38
|
+
ids: extractItemIds(searchItem).concat(res.ids).concat(extractItemIds(footerRefs)),
|
|
39
|
+
itemRefs: extractItemRefs(searchItem).concat(res.itemRefs).concat(extractItemRefs(footerRefs)),
|
|
40
|
+
expandedIds: res.expandedIds,
|
|
41
|
+
};
|
|
42
|
+
}, [footerRefs, memorizedItems, openCollapsedItems, searchItem]);
|
|
43
|
+
const slicedItems = useMemo(() => getSlicedItems({ items, hasSearch, pinTop, pinBottom, footerRefs }), [items, hasSearch, pinTop, pinBottom, footerRefs]);
|
|
44
|
+
const triggerElemRef = useRef(null);
|
|
45
|
+
const listRef = useRef(null);
|
|
46
|
+
const { activeFocusIndex, setActiveFocusIndex, openNestedIndex, handleListKeyDown, resetNestedIndex, resetActiveFocusIndex, } = useKeyboardNavigation({ ids, expandedIds, parentRef: triggerElemRefProp !== null && triggerElemRefProp !== void 0 ? triggerElemRefProp : triggerElemRef, itemRefs });
|
|
47
|
+
const [open, setOpen] = useUncontrolledProp(openProp, false, onOpenChange);
|
|
48
|
+
const handleOpenChange = (open) => {
|
|
49
|
+
if (!open) {
|
|
50
|
+
resetNestedIndex();
|
|
51
|
+
resetActiveFocusIndex();
|
|
52
|
+
setOpenCollapsedItems([]);
|
|
53
|
+
}
|
|
54
|
+
setOpen(open);
|
|
55
|
+
};
|
|
56
|
+
const onKeyDown = useCallback((e, cb) => {
|
|
57
|
+
if (e.key === 'ArrowDown') {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
setOpen(true);
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
var _a;
|
|
62
|
+
(_a = listRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
63
|
+
setActiveFocusIndex(0);
|
|
64
|
+
}, 0);
|
|
65
|
+
}
|
|
66
|
+
cb === null || cb === void 0 ? void 0 : cb(e);
|
|
67
|
+
}, [setActiveFocusIndex, setOpen]);
|
|
68
|
+
const triggerElem = useMemo(() => {
|
|
69
|
+
if (isValidElement(children)) {
|
|
70
|
+
const props = typeof children.props === 'object' ? children.props : {};
|
|
71
|
+
return cloneElement(children, Object.assign(Object.assign({}, props), { onKeyDown: (e) => {
|
|
72
|
+
var _a;
|
|
73
|
+
onKeyDown(e, (_a = children.props) === null || _a === void 0 ? void 0 : _a.onKeyDown);
|
|
74
|
+
} }));
|
|
75
|
+
}
|
|
76
|
+
return children;
|
|
77
|
+
}, [onKeyDown, children]);
|
|
78
|
+
return (
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
_jsx(SelectionProvider, Object.assign({}, extractSelectionProps(props), { children: _jsx(ParentListContext.Provider, { value: {
|
|
82
|
+
parentIds: ids,
|
|
83
|
+
parentActiveFocusIndex: activeFocusIndex,
|
|
84
|
+
parentExpandedIds: expandedIds,
|
|
85
|
+
parentItemRefs: itemRefs,
|
|
86
|
+
parentOpenNestedIndex: openNestedIndex,
|
|
87
|
+
triggerRef: triggerElemRefProp !== null && triggerElemRefProp !== void 0 ? triggerElemRefProp : triggerElemRef,
|
|
88
|
+
parentRef: listRef,
|
|
89
|
+
parentResetNestedIndex: resetNestedIndex,
|
|
90
|
+
parentResetActiveFocusIndex: resetActiveFocusIndex,
|
|
91
|
+
toggleOpenCollapsedItems: id => setOpenCollapsedItems(items => items.includes(id) ? items.filter(item => item !== id) : items.concat([id])),
|
|
92
|
+
}, children: _jsx(Dropdown, Object.assign({ content: _jsx(ListPrivate, Object.assign({ onKeyDown: handleListKeyDown, tabIndex: 0, ref: listRef, search: search }, slicedItems, props)), trigger: trigger, placement: placement, widthStrategy: widthStrategy }, (triggerElemRefProp
|
|
93
|
+
? {}
|
|
94
|
+
: {
|
|
95
|
+
triggerRef: triggerElemRef,
|
|
96
|
+
}), { open: open, onOpenChange: handleOpenChange, children: triggerElem })) }) })));
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DropList';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DropList';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export declare const List: import("react").ForwardRefExoticComponent<{
|
|
3
|
+
'data-test-id'?: string | undefined;
|
|
4
|
+
} & import("react").AriaAttributes & {
|
|
5
|
+
items: import("../../Items").ItemProps[];
|
|
6
|
+
pinTop?: import("../../Items").ItemProps[] | undefined;
|
|
7
|
+
pinBottom?: import("../../Items").ItemProps[] | undefined;
|
|
8
|
+
footer?: import("react").ReactNode;
|
|
9
|
+
search?: import("../../../types").SearchState | undefined;
|
|
10
|
+
loading?: boolean | undefined;
|
|
11
|
+
noData?: string | undefined;
|
|
12
|
+
noResults?: string | undefined;
|
|
13
|
+
footerActiveElementsRefs?: import("react").RefObject<HTMLElement>[] | undefined;
|
|
14
|
+
} & Pick<import("../contexts").SelectionProviderProps, "value" | "defaultValue" | "selection" | "onChange"> & import("../contexts").ListContextType & import("../../../types").ScrollProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,77 @@
|
|
|
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 } from "react/jsx-runtime";
|
|
13
|
+
import { forwardRef, useMemo, useRef, useState } from 'react';
|
|
14
|
+
import { HiddenTabButton } from '../../../helperComponents';
|
|
15
|
+
import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
|
|
16
|
+
import { addItemsIds, getSlicedItems, useItemsWithIds } from '../../Items';
|
|
17
|
+
import { extractSelectionProps, ParentListContext, SelectionProvider, useParentListContext } from '../contexts';
|
|
18
|
+
import { useKeyboardNavigation } from '../hooks';
|
|
19
|
+
import { ListPrivate } from '../ListPrivate';
|
|
20
|
+
import styles from '../styles.module.css';
|
|
21
|
+
export const List = forwardRef((_a) => {
|
|
22
|
+
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs"]);
|
|
23
|
+
const hasSearch = useMemo(() => Boolean(search), [search]);
|
|
24
|
+
const memorizedItems = useMemo(() => addItemsIds((pinTop !== null && pinTop !== void 0 ? pinTop : []).concat(itemsProp).concat(pinBottom !== null && pinBottom !== void 0 ? pinBottom : [])), [itemsProp, pinBottom, pinTop]);
|
|
25
|
+
const [openCollapsedItems, setOpenCollapsedItems] = useState([]);
|
|
26
|
+
const { search: searchItem, footerRefs } = useItemsWithIds({
|
|
27
|
+
search: hasSearch,
|
|
28
|
+
footerActiveElementsRefs,
|
|
29
|
+
});
|
|
30
|
+
const { items, itemRefs, ids, expandedIds } = useMemo(() => {
|
|
31
|
+
const res = withCollapsedItems({
|
|
32
|
+
items: memorizedItems,
|
|
33
|
+
openCollapsedItems,
|
|
34
|
+
});
|
|
35
|
+
const items = searchItem.concat(res.items).concat(footerRefs);
|
|
36
|
+
return {
|
|
37
|
+
items,
|
|
38
|
+
ids: extractItemIds(searchItem).concat(res.ids).concat(extractItemIds(footerRefs)),
|
|
39
|
+
itemRefs: extractItemRefs(searchItem).concat(res.itemRefs).concat(extractItemRefs(footerRefs)),
|
|
40
|
+
expandedIds: res.expandedIds,
|
|
41
|
+
};
|
|
42
|
+
}, [footerRefs, memorizedItems, openCollapsedItems, searchItem]);
|
|
43
|
+
const slicedItems = useMemo(() => getSlicedItems({ items, hasSearch, pinTop, pinBottom, footerRefs }), [items, hasSearch, pinTop, pinBottom, footerRefs]);
|
|
44
|
+
const listRef = useRef(null);
|
|
45
|
+
const btnRef = useRef(null);
|
|
46
|
+
const { triggerRef } = useParentListContext();
|
|
47
|
+
const { activeFocusIndex, openNestedIndex, handleListKeyDown, resetNestedIndex, resetActiveFocusIndex, setActiveFocusIndex, } = useKeyboardNavigation({
|
|
48
|
+
ids,
|
|
49
|
+
expandedIds,
|
|
50
|
+
parentRef: listRef,
|
|
51
|
+
btnRef,
|
|
52
|
+
itemRefs,
|
|
53
|
+
});
|
|
54
|
+
const isActive = listRef.current === document.activeElement && activeFocusIndex === -1 && openNestedIndex === -1;
|
|
55
|
+
return (
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
_jsx(SelectionProvider, Object.assign({}, extractSelectionProps(props), { children: _jsx(ParentListContext.Provider, { value: {
|
|
59
|
+
parentIds: ids,
|
|
60
|
+
parentActiveFocusIndex: activeFocusIndex,
|
|
61
|
+
parentExpandedIds: expandedIds,
|
|
62
|
+
parentItemRefs: itemRefs,
|
|
63
|
+
parentOpenNestedIndex: openNestedIndex,
|
|
64
|
+
triggerRef: triggerRef !== null && triggerRef !== void 0 ? triggerRef : listRef,
|
|
65
|
+
parentRef: listRef,
|
|
66
|
+
parentSetActiveFocusIndex: setActiveFocusIndex,
|
|
67
|
+
parentResetNestedIndex: resetNestedIndex,
|
|
68
|
+
parentResetActiveFocusIndex: resetActiveFocusIndex,
|
|
69
|
+
openCollapsedItems,
|
|
70
|
+
toggleOpenCollapsedItems: id => setOpenCollapsedItems(items => items.includes(id) ? items.filter(item => item !== id) : items.concat([id])),
|
|
71
|
+
}, children: _jsxs("div", { className: styles.wrapper, "data-active": isActive || undefined, children: [_jsx(ListPrivate, Object.assign({}, props, slicedItems, { ref: listRef, onFocus: e => {
|
|
72
|
+
if (e.relatedTarget === null ||
|
|
73
|
+
(e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))) {
|
|
74
|
+
resetActiveFocusIndex();
|
|
75
|
+
}
|
|
76
|
+
}, onKeyDown: handleListKeyDown, tabIndex: 0, search: search, nested: false })), _jsx(HiddenTabButton, { ref: btnRef, listRef: listRef })] }) }) })));
|
|
77
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './List';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './List';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
export declare const ListPrivate: import("react").ForwardRefExoticComponent<{
|
|
3
|
+
'data-test-id'?: string | undefined;
|
|
4
|
+
} & import("react").AriaAttributes & {
|
|
5
|
+
items: import("../../Items").ItemProps[];
|
|
6
|
+
pinTop?: import("../../Items").ItemProps[] | undefined;
|
|
7
|
+
pinBottom?: import("../../Items").ItemProps[] | undefined;
|
|
8
|
+
footer?: import("react").ReactNode;
|
|
9
|
+
search?: import("../../../types").SearchState | undefined;
|
|
10
|
+
loading?: boolean | undefined;
|
|
11
|
+
noData?: string | undefined;
|
|
12
|
+
noResults?: string | undefined;
|
|
13
|
+
footerActiveElementsRefs?: RefObject<HTMLElement>[] | undefined;
|
|
14
|
+
} & Pick<import("../contexts").SelectionProviderProps, "value" | "defaultValue" | "selection" | "onChange"> & import("../contexts").ListContextType & import("../../../types").ScrollProps & {
|
|
15
|
+
nested?: boolean | undefined;
|
|
16
|
+
active?: boolean | undefined;
|
|
17
|
+
tabIndex?: number | undefined;
|
|
18
|
+
onFocus?(e: import("react").FocusEvent<HTMLElement, Element>): void;
|
|
19
|
+
onBlur?(e: import("react").FocusEvent<HTMLElement, Element>): void;
|
|
20
|
+
onKeyDown?(e: import("react").KeyboardEvent<HTMLElement>): void;
|
|
21
|
+
collapse?: "single" | "multiple" | undefined;
|
|
22
|
+
} & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,41 @@
|
|
|
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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import cn from 'classnames';
|
|
14
|
+
import { forwardRef, useMemo } from 'react';
|
|
15
|
+
import { Spinner } from '@snack-uikit/loaders';
|
|
16
|
+
import { Scroll } from '@snack-uikit/scroll';
|
|
17
|
+
import { ToggleGroup } from '@snack-uikit/toggles';
|
|
18
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
19
|
+
import { PinBottomGroupItem, PinTopGroupItem, SearchItem, useRenderItems } from '../../Items';
|
|
20
|
+
import { ListContextProvider } from '../contexts';
|
|
21
|
+
import commonStyles from '../styles.module.css';
|
|
22
|
+
import styles from './styles.module.css';
|
|
23
|
+
export const ListPrivate = forwardRef((_a, ref) => {
|
|
24
|
+
var _b, _c;
|
|
25
|
+
var { items, pinTop, pinBottom, collapse = 'multiple', onKeyDown, onBlur, onFocus, tabIndex, active, scroll, nested, search, scrollRef, scrollContainerRef, footer, loading, noData = 'No data', noResults = 'No results', size = 's', marker } = _a, props = __rest(_a, ["items", "pinTop", "pinBottom", "collapse", "onKeyDown", "onBlur", "onFocus", "tabIndex", "active", "scroll", "nested", "search", "scrollRef", "scrollContainerRef", "footer", "loading", "noData", "noResults", "size", "marker"]);
|
|
26
|
+
const itemsJSX = useRenderItems(items);
|
|
27
|
+
const itemsPinTopJSX = useRenderItems(pinTop !== null && pinTop !== void 0 ? pinTop : []);
|
|
28
|
+
const itemsPinBottomJSX = useRenderItems(pinBottom !== null && pinBottom !== void 0 ? pinBottom : []);
|
|
29
|
+
const hasNoItems = items.length + ((_b = pinTop === null || pinTop === void 0 ? void 0 : pinTop.length) !== null && _b !== void 0 ? _b : 0) + ((_c = pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) !== null && _c !== void 0 ? _c : 0) === 0;
|
|
30
|
+
const loadingJSX = useMemo(() => loading && (_jsx("div", { role: 'spinbutton', tabIndex: -1, className: styles.loader, "data-size": size, "data-no-items": hasNoItems || undefined, "data-test-id": 'list__loader', children: _jsx(Spinner, { size: size === 'l' ? 's' : 'xs' }) })), [hasNoItems, loading, size]);
|
|
31
|
+
const content = useMemo(() => (_jsxs(_Fragment, { children: [itemsJSX, loadingJSX, hasNoItems && !(search === null || search === void 0 ? void 0 : search.value) && !loading && (_jsx("div", { className: commonStyles.infoBlock, "data-test-id": 'list__no-data', children: noData })), hasNoItems && (search === null || search === void 0 ? void 0 : search.value) && !loading && (_jsx("div", { className: commonStyles.infoBlock, "data-test-id": 'list__no-results', children: noResults }))] })), [hasNoItems, itemsJSX, loading, loadingJSX, noData, noResults, search === null || search === void 0 ? void 0 : search.value]);
|
|
32
|
+
const listJSX = (_jsx("ul", Object.assign({ className: commonStyles.listContainer, ref: ref, onKeyDown: onKeyDown, tabIndex: tabIndex, onFocus: !nested ? onFocus : undefined, onBlur: onBlur, "data-active": active || undefined, role: 'menu' }, extractSupportProps(props), { children: _jsxs(ToggleGroup, { selectionMode: collapse, children: [(Number(pinTop === null || pinTop === void 0 ? void 0 : pinTop.length) > 0 || search) && (_jsxs(PinTopGroupItem, { children: [search && _jsx(SearchItem, { search: search }), Number(pinTop === null || pinTop === void 0 ? void 0 : pinTop.length) > 0 && itemsPinTopJSX] })), scroll ? (_jsxs(Scroll, { className: cn({
|
|
33
|
+
[commonStyles.scrollContainerS]: scroll && size === 's',
|
|
34
|
+
[commonStyles.scrollContainerM]: scroll && size === 'm',
|
|
35
|
+
[commonStyles.scrollContainerL]: scroll && size === 'l',
|
|
36
|
+
}), barHideStrategy: 'never', size: 's', ref: scrollContainerRef, children: [content, _jsx("div", { className: styles.scrollStub, ref: scrollRef })] })) : (_jsx(_Fragment, { children: content })), (Number(pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) > 0 || footer) && (_jsxs(PinBottomGroupItem, { children: [Number(pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) > 0 && itemsPinBottomJSX, footer && _jsx("div", { className: styles.footer, children: footer })] }))] }) })));
|
|
37
|
+
if (!nested) {
|
|
38
|
+
return (_jsx(ListContextProvider, { size: size, marker: marker, children: listJSX }));
|
|
39
|
+
}
|
|
40
|
+
return (_jsx("li", { style: { listStyleType: 'none' }, role: 'menuitem', children: listJSX }));
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ListPrivate';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ListPrivate';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.loader{
|
|
2
|
+
display:flex;
|
|
3
|
+
align-items:center;
|
|
4
|
+
justify-content:center;
|
|
5
|
+
}
|
|
6
|
+
.loader[data-size=s]{
|
|
7
|
+
height:var(--dimension-3m, 24px);
|
|
8
|
+
}
|
|
9
|
+
.loader[data-size=s][data-no-items]{
|
|
10
|
+
height:calc(3 * var(--dimension-3m, 24px));
|
|
11
|
+
}
|
|
12
|
+
.loader[data-size=m]{
|
|
13
|
+
height:var(--dimension-4m, 32px);
|
|
14
|
+
}
|
|
15
|
+
.loader[data-size=m][data-no-items]{
|
|
16
|
+
height:calc(3 * var(--dimension-4m, 32px));
|
|
17
|
+
}
|
|
18
|
+
.loader[data-size=l]{
|
|
19
|
+
height:var(--dimension-5m, 40px);
|
|
20
|
+
}
|
|
21
|
+
.loader[data-size=l][data-no-items]{
|
|
22
|
+
height:calc(3 * var(--dimension-5m, 40px));
|
|
23
|
+
}
|
|
24
|
+
.loader:focus-visible{
|
|
25
|
+
outline-width:var(--border-state-focus-s-border-width, 2px);
|
|
26
|
+
outline-style:var(--border-state-focus-s-border-style, solid);
|
|
27
|
+
outline-color:var(--border-state-focus-s-border-color, );
|
|
28
|
+
outline-offset:calc(var(--border-state-focus-s-border-width, 2px) * -1);
|
|
29
|
+
outline-color:var(--sys-available-complementary, #131313);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.scrollStub{
|
|
33
|
+
height:calc(var(--dimension-025m, 2px) / 2);
|
|
34
|
+
margin-top:calc(var(--dimension-025m, 2px) / -2);
|
|
35
|
+
background:transparent;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.footer{
|
|
39
|
+
display:flex;
|
|
40
|
+
flex-direction:column;
|
|
41
|
+
gap:var(--dimension-050m, 4px);
|
|
42
|
+
align-items:center;
|
|
43
|
+
justify-content:center;
|
|
44
|
+
box-sizing:border-box;
|
|
45
|
+
padding:var(--dimension-050m, 4px);
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export type ListContextType = {
|
|
3
|
+
size?: 's' | 'm' | 'l';
|
|
4
|
+
marker?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type Child = {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export declare const ListContext: import("react").Context<ListContextType>;
|
|
10
|
+
export declare const useListContext: () => ListContextType;
|
|
11
|
+
export declare function ListContextProvider({ children, ...props }: ListContextType & Child): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { createContext, useContext } from 'react';
|
|
14
|
+
export const ListContext = createContext({});
|
|
15
|
+
export const useListContext = () => useContext(ListContext);
|
|
16
|
+
function extractListProps({ size, marker }) {
|
|
17
|
+
return { size, marker };
|
|
18
|
+
}
|
|
19
|
+
export function ListContextProvider(_a) {
|
|
20
|
+
var { children } = _a, props = __rest(_a, ["children"]);
|
|
21
|
+
return _jsx(ListContext.Provider, { value: extractListProps(props), children: children });
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
export type ParentListContextType = {
|
|
3
|
+
parentActiveFocusIndex: number;
|
|
4
|
+
parentSetActiveFocusIndex?(idx: number): void;
|
|
5
|
+
parentResetActiveFocusIndex?(): void;
|
|
6
|
+
parentIds: Array<string | number>;
|
|
7
|
+
parentExpandedIds: Array<string | number>;
|
|
8
|
+
parentItemRefs: Array<RefObject<HTMLButtonElement>>;
|
|
9
|
+
parentOpenNestedIndex: number;
|
|
10
|
+
parentResetNestedIndex?(): void;
|
|
11
|
+
openCollapsedItems?: Array<string | number>;
|
|
12
|
+
toggleOpenCollapsedItems?(id: string | number): void;
|
|
13
|
+
triggerRef?: RefObject<HTMLElement>;
|
|
14
|
+
parentRef?: RefObject<HTMLElement>;
|
|
15
|
+
};
|
|
16
|
+
export declare const ParentListContext: import("react").Context<ParentListContextType>;
|
|
17
|
+
export declare const useParentListContext: () => ParentListContextType;
|
|
18
|
+
export declare function ParentListProvider(): null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
export const ParentListContext = createContext({
|
|
3
|
+
parentActiveFocusIndex: -1,
|
|
4
|
+
parentOpenNestedIndex: -1,
|
|
5
|
+
parentIds: [],
|
|
6
|
+
parentExpandedIds: [],
|
|
7
|
+
parentItemRefs: [],
|
|
8
|
+
});
|
|
9
|
+
export const useParentListContext = () => useContext(ParentListContext);
|
|
10
|
+
export function ParentListProvider() {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
type SelectionSingleValueType = string | number | undefined;
|
|
3
|
+
export type SelectionSingleProps = {
|
|
4
|
+
/** Начальное состояние */
|
|
5
|
+
defaultValue?: SelectionSingleValueType;
|
|
6
|
+
/** Controlled состояние */
|
|
7
|
+
value?: SelectionSingleValueType;
|
|
8
|
+
/** Controlled обработчик измения состояния */
|
|
9
|
+
onChange?(value: any): void;
|
|
10
|
+
setValue?(value: any): void;
|
|
11
|
+
/** Режим выбора */
|
|
12
|
+
selection: 'single';
|
|
13
|
+
/** Режим выбора single */
|
|
14
|
+
isSelectionSingle: true;
|
|
15
|
+
/** Режим выбора multi */
|
|
16
|
+
isSelectionMultiple: false;
|
|
17
|
+
};
|
|
18
|
+
export type SelectionMultipleProps = {
|
|
19
|
+
/** Начальное состояние */
|
|
20
|
+
defaultValue?: SelectionSingleValueType[];
|
|
21
|
+
/** Controlled состояние */
|
|
22
|
+
value?: SelectionSingleValueType[];
|
|
23
|
+
/** Controlled обработчик измения состояния */
|
|
24
|
+
onChange?(value: any): void;
|
|
25
|
+
setValue?(value: any): void;
|
|
26
|
+
/** Режим выбора */
|
|
27
|
+
selection: 'multiple';
|
|
28
|
+
/** Режим выбора single */
|
|
29
|
+
isSelectionSingle: false;
|
|
30
|
+
/** Режим выбора multi */
|
|
31
|
+
isSelectionMultiple: true;
|
|
32
|
+
};
|
|
33
|
+
type SelectionNoneProps = {
|
|
34
|
+
selection?: undefined;
|
|
35
|
+
value?: undefined;
|
|
36
|
+
onChange?: undefined;
|
|
37
|
+
setValue?: undefined;
|
|
38
|
+
defaultValue?: undefined;
|
|
39
|
+
isSelectionSingle?: undefined;
|
|
40
|
+
isSelectionMultiple?: undefined;
|
|
41
|
+
};
|
|
42
|
+
export type SelectionProviderProps = SelectionSingleProps | SelectionMultipleProps | SelectionNoneProps;
|
|
43
|
+
type SelectionContextType = Omit<SelectionNoneProps, 'defaultValue'> | Omit<SelectionSingleProps, 'defaultValue'> | Omit<SelectionMultipleProps, 'defaultValue'>;
|
|
44
|
+
export declare const SelectionContext: import("react").Context<SelectionContextType>;
|
|
45
|
+
export declare function isSelectionMultipleProps(props: any): props is SelectionMultipleProps;
|
|
46
|
+
export declare function isSelectionSingleProps(props: any): props is SelectionSingleProps;
|
|
47
|
+
type Child = {
|
|
48
|
+
children: ReactNode;
|
|
49
|
+
};
|
|
50
|
+
export declare function SelectionProvider(props: SelectionProviderProps & Child): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
export declare const useSelectionContext: () => SelectionContextType;
|
|
52
|
+
export declare function extractSelectionProps<T extends SelectionProviderProps>({ selection, value, defaultValue, onChange, }: T): SelectionProviderProps;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext } from 'react';
|
|
3
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
4
|
+
export const SelectionContext = createContext({
|
|
5
|
+
value: undefined,
|
|
6
|
+
onChange: undefined,
|
|
7
|
+
selection: undefined,
|
|
8
|
+
});
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export function isSelectionMultipleProps(props) {
|
|
11
|
+
return 'selection' in props && props['selection'] === 'multiple';
|
|
12
|
+
}
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export function isSelectionSingleProps(props) {
|
|
15
|
+
return 'selection' in props && props['selection'] === 'single';
|
|
16
|
+
}
|
|
17
|
+
function SelectionSingleProvider({ value: valueProp, defaultValue, onChange: onChangeProp, children, }) {
|
|
18
|
+
const [value, setValue] = useUncontrolledProp(valueProp, defaultValue, cb => {
|
|
19
|
+
onChangeProp === null || onChangeProp === void 0 ? void 0 : onChangeProp(cb(value));
|
|
20
|
+
});
|
|
21
|
+
const onChange = useCallback((newValue) => setValue((oldValue) => {
|
|
22
|
+
if (newValue !== oldValue) {
|
|
23
|
+
return newValue;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}), [setValue]);
|
|
27
|
+
return (_jsx(SelectionContext.Provider, { value: { value, onChange, selection: 'single', isSelectionSingle: true, isSelectionMultiple: false, setValue }, children: children }));
|
|
28
|
+
}
|
|
29
|
+
function SelectionMultipleProvider({ value: valueProp, defaultValue, onChange: onChangeProp, children, }) {
|
|
30
|
+
const [value, setValue] = useUncontrolledProp(valueProp, defaultValue, cb => {
|
|
31
|
+
onChangeProp === null || onChangeProp === void 0 ? void 0 : onChangeProp(cb(value));
|
|
32
|
+
});
|
|
33
|
+
const onChange = useCallback((newValue) => setValue((oldValues) => {
|
|
34
|
+
if (Array.isArray(oldValues)) {
|
|
35
|
+
if (oldValues.includes(newValue)) {
|
|
36
|
+
return oldValues.filter(oldValue => oldValue !== newValue);
|
|
37
|
+
}
|
|
38
|
+
return oldValues.concat(newValue);
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}), [setValue]);
|
|
42
|
+
return (_jsx(SelectionContext.Provider, { value: { value, onChange, selection: 'multiple', isSelectionSingle: false, isSelectionMultiple: true, setValue }, children: children }));
|
|
43
|
+
}
|
|
44
|
+
export function SelectionProvider(props) {
|
|
45
|
+
if (isSelectionSingleProps(props)) {
|
|
46
|
+
return _jsx(SelectionSingleProvider, Object.assign({}, props));
|
|
47
|
+
}
|
|
48
|
+
if (isSelectionMultipleProps(props)) {
|
|
49
|
+
return _jsx(SelectionMultipleProvider, Object.assign({}, props));
|
|
50
|
+
}
|
|
51
|
+
return _jsx(SelectionContext.Provider, { value: {}, children: props.children });
|
|
52
|
+
}
|
|
53
|
+
export const useSelectionContext = () => useContext(SelectionContext);
|
|
54
|
+
export function extractSelectionProps({ selection, value, defaultValue, onChange, }) {
|
|
55
|
+
return {
|
|
56
|
+
selection,
|
|
57
|
+
value,
|
|
58
|
+
defaultValue,
|
|
59
|
+
onChange,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { KeyboardEvent, RefObject } from 'react';
|
|
2
|
+
export type UseKeyboardNavigationProps<T extends HTMLElement> = {
|
|
3
|
+
ids: Array<number | string>;
|
|
4
|
+
expandedIds: Array<number | string>;
|
|
5
|
+
parentRef?: RefObject<T>;
|
|
6
|
+
btnRef?: RefObject<HTMLButtonElement>;
|
|
7
|
+
itemRefs?: RefObject<HTMLButtonElement>[];
|
|
8
|
+
};
|
|
9
|
+
export declare function useKeyboardNavigation<T extends HTMLElement>({ ids, itemRefs, expandedIds, parentRef, btnRef, }: UseKeyboardNavigationProps<T>): {
|
|
10
|
+
activeFocusIndex: number;
|
|
11
|
+
setActiveFocusIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
12
|
+
openNestedIndex: number;
|
|
13
|
+
resetNestedIndex: () => void;
|
|
14
|
+
resetActiveFocusIndex: () => void;
|
|
15
|
+
handleListKeyDown: (e: KeyboardEvent<T>) => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function useKeyboardNavigation({ ids, itemRefs, expandedIds, parentRef, btnRef, }) {
|
|
3
|
+
const [activeFocusIndex, setActiveFocusIndex] = useState(-1);
|
|
4
|
+
const [openNestedIndex, setOpenNestedIndex] = useState(-1);
|
|
5
|
+
const handleListKeyDown = (e) => {
|
|
6
|
+
var _a, _b, _c, _d;
|
|
7
|
+
switch (e.key) {
|
|
8
|
+
case 'ArrowDown': {
|
|
9
|
+
setActiveFocusIndex(activeIndex => Math.min(activeIndex + 1, ids.length - 1));
|
|
10
|
+
// TODO: go to loader from last item
|
|
11
|
+
// if (activeFocusIndex === ids.length - 2) {
|
|
12
|
+
// return;
|
|
13
|
+
// }
|
|
14
|
+
e.stopPropagation();
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
case 'ArrowUp': {
|
|
19
|
+
if (activeFocusIndex === 0) {
|
|
20
|
+
(_a = parentRef === null || parentRef === void 0 ? void 0 : parentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
21
|
+
setActiveFocusIndex(-1);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
setActiveFocusIndex(activeIndex => Math.max(activeIndex - 1, 0));
|
|
25
|
+
e.stopPropagation();
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
case 'ArrowRight': {
|
|
30
|
+
if (expandedIds.includes(ids[activeFocusIndex])) {
|
|
31
|
+
setOpenNestedIndex(activeFocusIndex);
|
|
32
|
+
}
|
|
33
|
+
e.stopPropagation();
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
case 'Tab': {
|
|
38
|
+
if (parentRef && activeFocusIndex !== -1) {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
(_b = parentRef === null || parentRef === void 0 ? void 0 : parentRef.current) === null || _b === void 0 ? void 0 : _b.focus();
|
|
41
|
+
}
|
|
42
|
+
else if (activeFocusIndex === -1) {
|
|
43
|
+
btnRef && !e.shiftKey ? (_c = btnRef === null || btnRef === void 0 ? void 0 : btnRef.current) === null || _c === void 0 ? void 0 : _c.focus() : (_d = parentRef === null || parentRef === void 0 ? void 0 : parentRef.current) === null || _d === void 0 ? void 0 : _d.focus();
|
|
44
|
+
}
|
|
45
|
+
setOpenNestedIndex(-1);
|
|
46
|
+
setActiveFocusIndex(-1);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
default: {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const resetNestedIndex = () => {
|
|
55
|
+
setActiveFocusIndex(openNestedIndex);
|
|
56
|
+
setOpenNestedIndex(-1);
|
|
57
|
+
};
|
|
58
|
+
const resetActiveFocusIndex = () => {
|
|
59
|
+
setActiveFocusIndex(-1);
|
|
60
|
+
};
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
activeFocusIndex !== -1 && openNestedIndex === -1 && ((_b = (_a = itemRefs === null || itemRefs === void 0 ? void 0 : itemRefs[activeFocusIndex]) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.focus());
|
|
64
|
+
}, [activeFocusIndex, itemRefs, openNestedIndex]);
|
|
65
|
+
return {
|
|
66
|
+
activeFocusIndex,
|
|
67
|
+
setActiveFocusIndex,
|
|
68
|
+
openNestedIndex,
|
|
69
|
+
resetNestedIndex,
|
|
70
|
+
resetActiveFocusIndex,
|
|
71
|
+
handleListKeyDown,
|
|
72
|
+
};
|
|
73
|
+
}
|