@snack-uikit/tree 0.3.4 → 0.5.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 +22 -0
- package/README.md +2 -1
- package/dist/components/Tree/Tree.d.ts +1 -1
- package/dist/components/Tree/Tree.js +4 -7
- package/dist/components/Tree/utils.d.ts +2 -0
- package/dist/components/Tree/utils.js +20 -0
- package/dist/contexts/TreeContext.d.ts +1 -0
- package/dist/contexts/TreeContext.js +5 -2
- package/dist/helperComponents/TreeNode/TreeNode.js +5 -3
- package/dist/helperComponents/TreeNode/styles.module.css +0 -1
- package/dist/types.d.ts +12 -1
- package/package.json +2 -2
- package/src/components/Tree/Tree.tsx +5 -12
- package/src/components/Tree/utils.ts +26 -0
- package/src/contexts/TreeContext.tsx +6 -2
- package/src/helperComponents/TreeNode/TreeNode.tsx +11 -5
- package/src/helperComponents/TreeNode/styles.module.scss +0 -2
- package/src/types.ts +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 0.5.0 (2024-01-18)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **FF-4126:** selectionMode is optional, and tree may be unselectable ([1540e26](https://github.com/cloud-ru-tech/snack-uikit/commit/1540e266b9351a92f1619c399a904fd6bd25518f))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# 0.4.0 (2024-01-11)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **FF-4018:** add showIcons prop to disable tree node icon ([eecda7f](https://github.com/cloud-ru-tech/snack-uikit/commit/eecda7f35fc98efeea2770098acfe7241b44b57a))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## 0.3.4 (2023-12-28)
|
|
7
29
|
|
|
8
30
|
### Only dependencies have been changed
|
package/README.md
CHANGED
|
@@ -43,15 +43,16 @@ const [selectedNodes, setSelected] = useState<TreeNodeId[]>([]);
|
|
|
43
43
|
| name | type | default value | description |
|
|
44
44
|
|------|------|---------------|-------------|
|
|
45
45
|
| data* | `TreeNodeProps[]` | - | Данные для отрисовки |
|
|
46
|
-
| selectionMode | "single" \| "multi" | - | Режим выбора элементов: <br> - `Single` - одиночный выбор <br> - `Multi` - множественный выбор |
|
|
47
46
|
| onNodeClick | `OnNodeClick` | - | Обработчик клика по элементу дерева |
|
|
48
47
|
| expandedNodes | `string[]` | - | Состояние для раскрытых элементов |
|
|
49
48
|
| onExpand | `(expandedKeys: string[], nodeId: string) => void` | - | Колбэк при раскрытии/закрытии элементов |
|
|
50
49
|
| onDataLoad | `(node: TreeNodeProps) => Promise<unknown>` | - | Колбэк для асинхронной загрузки данных при раскрытии дерева |
|
|
51
50
|
| parentActions | `(node: TreeNodeProps) => DroplistItemSingleProps[]` | - | Дополнительные действия для элемента-родителя |
|
|
52
51
|
| nodeActions | `(node: TreeNodeProps) => DroplistItemSingleProps[]` | - | Дополнительные действия для элемента-потомка |
|
|
52
|
+
| showIcons | `boolean` | true | Флаг отвечающий за отображение иконок у элементов дерева |
|
|
53
53
|
| showLines | `boolean` | true | Флаг отвечающий за отображение линий вложенности |
|
|
54
54
|
| className | `string` | - | CSS-класс |
|
|
55
|
+
| selectionMode | "single" \| "multi" | - | Режим выбора элементов: <br> - `Single` - одиночный выбор <br> - `Multi` - множественный выбор <br> - `undefined` - без выбора |
|
|
55
56
|
| selected | `string \| string[]` | - | Состояние для выбраных элементов: <br> - При <strong>selectionMode</strong>=`Multi` - принимает массив строк <br> - При <strong>selectionMode</strong>=`Single` - принимает строку |
|
|
56
57
|
| onSelect | `((selectedKeys: string[], node: TreeNodeProps) => void) \| ((selectedKey: string, node: TreeNodeProps) => void)` | - | Колбэк при изменении выбраных элементов: <br> - При <strong>selectionMode</strong>=`Multi` - возвращает массив строк <br> - При <strong>selectionMode</strong>=`Single` - возвращает строку |
|
|
57
58
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { WithSupportProps } from '@snack-uikit/utils';
|
|
2
2
|
import { TreeBaseProps } from '../../types';
|
|
3
3
|
export type TreeProps = WithSupportProps<TreeBaseProps>;
|
|
4
|
-
export declare function Tree({ data,
|
|
4
|
+
export declare function Tree({ data, onNodeClick, onExpand, expandedNodes, nodeActions, parentActions, onDataLoad, showLines, showIcons, className, ...rest }: TreeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -15,13 +15,10 @@ import { extractSupportProps } from '@snack-uikit/utils';
|
|
|
15
15
|
import { TreeContextProvider } from '../../contexts/TreeContext';
|
|
16
16
|
import { TreeItem } from '../../helperComponents';
|
|
17
17
|
import styles from './styles.module.css';
|
|
18
|
+
import { extractSelectableProps } from './utils';
|
|
18
19
|
export function Tree(_a) {
|
|
19
|
-
var { data,
|
|
20
|
-
return (_jsx("div", Object.assign({ className: cn(styles.tree, className), role: 'tree' }, extractSupportProps(rest), { children: _jsx(TreeContextProvider, { value: {
|
|
21
|
-
data,
|
|
22
|
-
selected: selected,
|
|
23
|
-
selectionMode: selectionMode,
|
|
24
|
-
onSelect: onSelect,
|
|
20
|
+
var { data, onNodeClick, onExpand, expandedNodes, nodeActions, parentActions, onDataLoad, showLines = true, showIcons = true, className } = _a, rest = __rest(_a, ["data", "onNodeClick", "onExpand", "expandedNodes", "nodeActions", "parentActions", "onDataLoad", "showLines", "showIcons", "className"]);
|
|
21
|
+
return (_jsx("div", Object.assign({ className: cn(styles.tree, className), role: 'tree' }, extractSupportProps(rest), { children: _jsx(TreeContextProvider, { value: Object.assign({ data,
|
|
25
22
|
expandedNodes,
|
|
26
23
|
onNodeClick,
|
|
27
24
|
onExpand,
|
|
@@ -29,5 +26,5 @@ export function Tree(_a) {
|
|
|
29
26
|
parentActions,
|
|
30
27
|
onDataLoad,
|
|
31
28
|
showLines,
|
|
32
|
-
|
|
29
|
+
showIcons }, extractSelectableProps(rest)), children: data.map((node, index) => (_jsx(TreeItem, { node: node, tabIndexAvailable: index === 0 || index === data.length - 1 }, node.id))) }) })));
|
|
33
30
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { TreeBaseProps, TreeMultiSelect, TreeSingleSelect, TreeView } from '../../types';
|
|
2
|
+
export declare function extractSelectableProps({ selectionMode, selected, onSelect, }: Pick<TreeBaseProps, 'selectionMode' | 'selected' | 'onSelect'>): Pick<TreeSingleSelect, "selected" | "selectionMode" | "onSelect"> | Pick<TreeMultiSelect, "selected" | "selectionMode" | "onSelect"> | Pick<TreeView, "selected" | "selectionMode" | "onSelect">;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SELECTION_MODE } from '../../constants';
|
|
2
|
+
export function extractSelectableProps({ selectionMode, selected, onSelect, }) {
|
|
3
|
+
switch (selectionMode) {
|
|
4
|
+
case SELECTION_MODE.Single:
|
|
5
|
+
return {
|
|
6
|
+
selectionMode,
|
|
7
|
+
selected,
|
|
8
|
+
onSelect,
|
|
9
|
+
};
|
|
10
|
+
case SELECTION_MODE.Multi:
|
|
11
|
+
return {
|
|
12
|
+
selectionMode,
|
|
13
|
+
selected,
|
|
14
|
+
onSelect,
|
|
15
|
+
};
|
|
16
|
+
case undefined:
|
|
17
|
+
default:
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -7,6 +7,7 @@ type TreeContextProps = Omit<TreeBaseProps, 'onSelect' | 'onExpand' | 'onNodeCli
|
|
|
7
7
|
onNodeClick: OnNodeClick;
|
|
8
8
|
isMultiSelect: boolean;
|
|
9
9
|
isSingleSelect: boolean;
|
|
10
|
+
isSelectable: boolean;
|
|
10
11
|
focusedNodeId?: TreeNodeId;
|
|
11
12
|
setFocusPosition(id: TreeNodeId): void;
|
|
12
13
|
resetFocusPosition(): void;
|
|
@@ -29,11 +29,13 @@ export const TreeContext = createContext({
|
|
|
29
29
|
focusableNodeIds: [],
|
|
30
30
|
isMultiSelect: false,
|
|
31
31
|
isSingleSelect: false,
|
|
32
|
+
isSelectable: false,
|
|
32
33
|
});
|
|
33
34
|
export function TreeContextProvider({ children, value }) {
|
|
34
35
|
const { onNodeClick: onNodeClickProp, onExpand: onExpandProp, onSelect: onSelectProp, selectionMode, data } = value, props = __rest(value, ["onNodeClick", "onExpand", "onSelect", "selectionMode", "data"]);
|
|
35
36
|
const isMultiSelect = selectionMode === SELECTION_MODE.Multi;
|
|
36
37
|
const isSingleSelect = selectionMode === SELECTION_MODE.Single;
|
|
38
|
+
const isSelectable = Boolean(selectionMode);
|
|
37
39
|
const [expandedNodes, onExpandHandler] = useUncontrolledProp(value.expandedNodes, value.expandedNodes || [], onExpandProp);
|
|
38
40
|
const onExpand = useCallback(node => {
|
|
39
41
|
const isExpanded = expandedNodes.includes(node.id);
|
|
@@ -42,7 +44,7 @@ export function TreeContextProvider({ children, value }) {
|
|
|
42
44
|
}, [expandedNodes, onExpandHandler]);
|
|
43
45
|
const [selectedNodes, onSelectHandler] = useUncontrolledProp(value.selected, [], onSelectProp);
|
|
44
46
|
const onSelect = useCallback((node, parentNode) => {
|
|
45
|
-
if (node.disabled)
|
|
47
|
+
if (!isSelectable || node.disabled)
|
|
46
48
|
return;
|
|
47
49
|
if (isSingleSelect) {
|
|
48
50
|
onSelectHandler(node.id, node);
|
|
@@ -52,7 +54,7 @@ export function TreeContextProvider({ children, value }) {
|
|
|
52
54
|
const updatedSelectedNodes = lookupTreeForSelectedNodes({ node, parentNode, selectedNodes });
|
|
53
55
|
onSelectHandler(updatedSelectedNodes, node);
|
|
54
56
|
}
|
|
55
|
-
}, [isSingleSelect, onSelectHandler, selectedNodes]);
|
|
57
|
+
}, [isSingleSelect, onSelectHandler, selectedNodes, isSelectable]);
|
|
56
58
|
const focusableNodeIds = useMemo(() => {
|
|
57
59
|
if (!(expandedNodes === null || expandedNodes === void 0 ? void 0 : expandedNodes.length)) {
|
|
58
60
|
return data.map(node => node.id);
|
|
@@ -86,6 +88,7 @@ export function TreeContextProvider({ children, value }) {
|
|
|
86
88
|
}, [isSingleSelect, onSelect, onNodeClickProp]);
|
|
87
89
|
return (_jsx(TreeContext.Provider, { value: Object.assign(Object.assign({}, props), { data, selected: selectedNodes, isSingleSelect,
|
|
88
90
|
isMultiSelect,
|
|
91
|
+
isSelectable,
|
|
89
92
|
expandedNodes,
|
|
90
93
|
onExpand,
|
|
91
94
|
onSelect,
|
|
@@ -27,7 +27,7 @@ import styles from './styles.module.css';
|
|
|
27
27
|
import { stopPropagationClick } from './utils';
|
|
28
28
|
export function TreeNode(_a) {
|
|
29
29
|
var { id, title, icon = _jsx(FileSVG, { size: 24 }), expandedIcon = _jsx(FolderOpenSVG, { size: 24 }), collapsedIcon = _jsx(FolderSVG, { size: 24 }), disabled, onClick, nested, className, onChevronClick, onKeyDown, isLoading, parentNode, tabIndexAvailable } = _a, rest = __rest(_a, ["id", "title", "icon", "expandedIcon", "collapsedIcon", "disabled", "onClick", "nested", "className", "onChevronClick", "onKeyDown", "isLoading", "parentNode", "tabIndexAvailable"]);
|
|
30
|
-
const { isMultiSelect, onNodeClick, selected, expandedNodes, onSelect, nodeActions, parentActions, setFocusPosition, resetFocusPosition, focusedNodeId, setFocusIndex, focusableNodeIds, showLines, } = useTreeContext();
|
|
30
|
+
const { isMultiSelect, isSelectable, onNodeClick, selected, expandedNodes, onSelect, nodeActions, parentActions, setFocusPosition, resetFocusPosition, focusedNodeId, setFocusIndex, focusableNodeIds, showLines, showIcons, } = useTreeContext();
|
|
31
31
|
const [isDroplistOpen, setDroplistOpen] = useState(false);
|
|
32
32
|
const [isDroplistTriggerFocused, setFocusDroplistTrigger] = useState(false);
|
|
33
33
|
const ref = useRef(null);
|
|
@@ -46,11 +46,13 @@ export function TreeNode(_a) {
|
|
|
46
46
|
}
|
|
47
47
|
}, [isFocused]);
|
|
48
48
|
const treeNodeIcon = useMemo(() => {
|
|
49
|
+
if (!showIcons)
|
|
50
|
+
return undefined;
|
|
49
51
|
if (isExpandable) {
|
|
50
52
|
return isExpanded ? expandedIcon : collapsedIcon;
|
|
51
53
|
}
|
|
52
54
|
return icon;
|
|
53
|
-
}, [isExpandable, icon, isExpanded, expandedIcon, collapsedIcon]);
|
|
55
|
+
}, [showIcons, isExpandable, icon, isExpanded, expandedIcon, collapsedIcon]);
|
|
54
56
|
const handleClick = e => {
|
|
55
57
|
onNodeClick({
|
|
56
58
|
id,
|
|
@@ -137,7 +139,7 @@ export function TreeNode(_a) {
|
|
|
137
139
|
}
|
|
138
140
|
};
|
|
139
141
|
const getNodeActions = Boolean(nested) ? parentActions : nodeActions;
|
|
140
|
-
return (_jsxs("div", Object.assign({ role: 'presentation', className: cn(styles.treeNode, className) }, extractSupportProps(rest), { "data-node-id": id, children: [parentNode && (_jsx(TreeLine, { halfWidth: Boolean(nested), horizontal: true, visible: showLines, "data-test-id": TEST_IDS.line })), !parentNode && !Boolean(nested) && _jsx(TreeLine, { visible: false }), isExpandable && (_jsx(ButtonFunction, { size: 'xs', icon: _jsx(ChevronRightSVG, {}), disabled: disabled, loading: isLoading, onClick: onChevronClick, "data-expanded": isExpanded || undefined, className: styles.treeNodeExpandButton, tabIndex: -1, "data-test-id": TEST_IDS.chevron })), _jsxs("div", { role: 'treeitem', "aria-expanded": isExpanded, "aria-selected": isSelected, "aria-disabled": disabled, "data-multiselect": isMultiSelect || undefined, "data-droplist-active": isDroplistOpen || isDroplistTriggerFocused || undefined, onClick: handleClick, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: resetFocusPosition, tabIndex: tabIndexAvailable ? 0 : -1, className: styles.treeNodeContent, "data-test-id": TEST_IDS.item, ref: ref, children: [isMultiSelect && (_jsx("div", { className: styles.treeNodeCheckboxWrap, children: _jsx(Checkbox, { size: 's', disabled: disabled, checked: !disabled && isSelected, indeterminate: !disabled && !isSelected && (nestedNodesSelection === null || nestedNodesSelection === void 0 ? void 0 : nestedNodesSelection.someSelected), onChange: handleSelect, onClick: stopPropagationClick, "data-test-id": TEST_IDS.checkbox, tabIndex: -1 }) })), _jsx("div", { className: styles.treeNodeIcon, "data-test-id": TEST_IDS.icon, children: treeNodeIcon }), _jsx(Typography.SansBodyM, { tag: 'div', className: styles.treeNodeTitle, children: _jsx(TruncateString, { text: title, "data-test-id": TEST_IDS.title }) }), getNodeActions && (_jsx(TreeNodeActions, { getNodeActions: getNodeActions, node: {
|
|
142
|
+
return (_jsxs("div", Object.assign({ role: 'presentation', className: cn(styles.treeNode, className) }, extractSupportProps(rest), { "data-node-id": id, children: [parentNode && (_jsx(TreeLine, { halfWidth: Boolean(nested), horizontal: true, visible: showLines, "data-test-id": TEST_IDS.line })), !parentNode && !Boolean(nested) && _jsx(TreeLine, { visible: false }), isExpandable && (_jsx(ButtonFunction, { size: 'xs', icon: _jsx(ChevronRightSVG, {}), disabled: disabled, loading: isLoading, onClick: onChevronClick, "data-expanded": isExpanded || undefined, className: styles.treeNodeExpandButton, tabIndex: -1, "data-test-id": TEST_IDS.chevron })), _jsxs("div", { role: 'treeitem', "aria-expanded": isExpanded, "aria-selected": isSelectable ? isSelected : undefined, "aria-disabled": disabled, "data-multiselect": isMultiSelect || undefined, "data-droplist-active": isDroplistOpen || isDroplistTriggerFocused || undefined, onClick: handleClick, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: resetFocusPosition, tabIndex: tabIndexAvailable ? 0 : -1, className: styles.treeNodeContent, "data-test-id": TEST_IDS.item, ref: ref, children: [isMultiSelect && (_jsx("div", { className: styles.treeNodeCheckboxWrap, children: _jsx(Checkbox, { size: 's', disabled: disabled, checked: !disabled && isSelected, indeterminate: !disabled && !isSelected && (nestedNodesSelection === null || nestedNodesSelection === void 0 ? void 0 : nestedNodesSelection.someSelected), onChange: handleSelect, onClick: stopPropagationClick, "data-test-id": TEST_IDS.checkbox, tabIndex: -1 }) })), treeNodeIcon && (_jsx("div", { className: styles.treeNodeIcon, "data-test-id": TEST_IDS.icon, children: treeNodeIcon })), _jsx(Typography.SansBodyM, { tag: 'div', className: styles.treeNodeTitle, children: _jsx(TruncateString, { text: title, "data-test-id": TEST_IDS.title }) }), getNodeActions && (_jsx(TreeNodeActions, { getNodeActions: getNodeActions, node: {
|
|
141
143
|
id,
|
|
142
144
|
title,
|
|
143
145
|
disabled,
|
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export type TreeCommonProps = {
|
|
|
43
43
|
* Режим выбора элементов:
|
|
44
44
|
* <br> - `Single` - одиночный выбор
|
|
45
45
|
* <br> - `Multi` - множественный выбор
|
|
46
|
+
* <br> - `undefined` - без выбора
|
|
46
47
|
*/
|
|
47
48
|
selectionMode?: SelectionMode;
|
|
48
49
|
/** Обработчик клика по элементу дерева */
|
|
@@ -59,10 +60,20 @@ export type TreeCommonProps = {
|
|
|
59
60
|
parentActions?(node: TreeNodeProps): ItemSingleProps[];
|
|
60
61
|
/** Дополнительные действия для элемента-потомка */
|
|
61
62
|
nodeActions?(node: TreeNodeProps): ItemSingleProps[];
|
|
63
|
+
/**
|
|
64
|
+
* Флаг отвечающий за отображение иконок у элементов дерева
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
showIcons?: boolean;
|
|
62
68
|
/** Флаг отвечающий за отображение линий вложенности */
|
|
63
69
|
showLines?: boolean;
|
|
64
70
|
className?: string;
|
|
65
71
|
};
|
|
72
|
+
export type TreeView = Omit<TreeCommonProps, 'selected' | 'selectionMode'> & {
|
|
73
|
+
selectionMode?: never;
|
|
74
|
+
selected?: never;
|
|
75
|
+
onSelect?: never;
|
|
76
|
+
};
|
|
66
77
|
export type TreeSingleSelect = Omit<TreeCommonProps, 'selected'> & {
|
|
67
78
|
selectionMode: 'single';
|
|
68
79
|
/** Состояние для выбраного элемента */
|
|
@@ -84,4 +95,4 @@ export type TreeMultiSelect = Omit<TreeCommonProps, 'selected'> & {
|
|
|
84
95
|
*/
|
|
85
96
|
onSelect?(selectedKeys: TreeNodeId[], node: TreeNodeProps): void;
|
|
86
97
|
};
|
|
87
|
-
export type TreeBaseProps = TreeMultiSelect | TreeSingleSelect;
|
|
98
|
+
export type TreeBaseProps = TreeView | TreeMultiSelect | TreeSingleSelect;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Tree",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.5.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"react-transition-state": "2.1.1",
|
|
44
44
|
"uncontrollable": "8.0.4"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "4b0eba6c0cbe808bf637fc77c2b25666e15668e3"
|
|
47
47
|
}
|
|
@@ -2,19 +2,16 @@ import cn from 'classnames';
|
|
|
2
2
|
|
|
3
3
|
import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
|
|
4
4
|
|
|
5
|
-
import { SELECTION_MODE } from '../../constants';
|
|
6
5
|
import { TreeContextProvider } from '../../contexts/TreeContext';
|
|
7
6
|
import { TreeItem } from '../../helperComponents';
|
|
8
|
-
import {
|
|
7
|
+
import { TreeBaseProps } from '../../types';
|
|
9
8
|
import styles from './styles.module.scss';
|
|
9
|
+
import { extractSelectableProps } from './utils';
|
|
10
10
|
|
|
11
11
|
export type TreeProps = WithSupportProps<TreeBaseProps>;
|
|
12
12
|
|
|
13
13
|
export function Tree({
|
|
14
14
|
data,
|
|
15
|
-
selected,
|
|
16
|
-
selectionMode,
|
|
17
|
-
onSelect,
|
|
18
15
|
onNodeClick,
|
|
19
16
|
onExpand,
|
|
20
17
|
expandedNodes,
|
|
@@ -22,6 +19,7 @@ export function Tree({
|
|
|
22
19
|
parentActions,
|
|
23
20
|
onDataLoad,
|
|
24
21
|
showLines = true,
|
|
22
|
+
showIcons = true,
|
|
25
23
|
className,
|
|
26
24
|
...rest
|
|
27
25
|
}: TreeProps) {
|
|
@@ -30,13 +28,6 @@ export function Tree({
|
|
|
30
28
|
<TreeContextProvider
|
|
31
29
|
value={{
|
|
32
30
|
data,
|
|
33
|
-
selected: selected as SelectionMode extends typeof SELECTION_MODE.Single ? TreeNodeId : TreeNodeId[],
|
|
34
|
-
selectionMode: selectionMode as typeof selected extends string
|
|
35
|
-
? typeof SELECTION_MODE.Single
|
|
36
|
-
: typeof SELECTION_MODE.Multi,
|
|
37
|
-
onSelect: onSelect as SelectionMode extends typeof SELECTION_MODE.Single
|
|
38
|
-
? TreeSingleSelect['onSelect']
|
|
39
|
-
: TreeMultiSelect['onSelect'],
|
|
40
31
|
expandedNodes,
|
|
41
32
|
onNodeClick,
|
|
42
33
|
onExpand,
|
|
@@ -44,6 +35,8 @@ export function Tree({
|
|
|
44
35
|
parentActions,
|
|
45
36
|
onDataLoad,
|
|
46
37
|
showLines,
|
|
38
|
+
showIcons,
|
|
39
|
+
...extractSelectableProps(rest),
|
|
47
40
|
}}
|
|
48
41
|
>
|
|
49
42
|
{data.map((node, index) => (
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SELECTION_MODE } from '../../constants';
|
|
2
|
+
import { TreeBaseProps, TreeMultiSelect, TreeSingleSelect, TreeView } from '../../types';
|
|
3
|
+
|
|
4
|
+
export function extractSelectableProps({
|
|
5
|
+
selectionMode,
|
|
6
|
+
selected,
|
|
7
|
+
onSelect,
|
|
8
|
+
}: Pick<TreeBaseProps, 'selectionMode' | 'selected' | 'onSelect'>) {
|
|
9
|
+
switch (selectionMode) {
|
|
10
|
+
case SELECTION_MODE.Single:
|
|
11
|
+
return {
|
|
12
|
+
selectionMode,
|
|
13
|
+
selected,
|
|
14
|
+
onSelect,
|
|
15
|
+
} as Pick<TreeSingleSelect, 'selectionMode' | 'selected' | 'onSelect'>;
|
|
16
|
+
case SELECTION_MODE.Multi:
|
|
17
|
+
return {
|
|
18
|
+
selectionMode,
|
|
19
|
+
selected,
|
|
20
|
+
onSelect,
|
|
21
|
+
} as Pick<TreeMultiSelect, 'selectionMode' | 'selected' | 'onSelect'>;
|
|
22
|
+
case undefined:
|
|
23
|
+
default:
|
|
24
|
+
return {} as Pick<TreeView, 'selectionMode' | 'selected' | 'onSelect'>;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -12,6 +12,7 @@ type TreeContextProps = Omit<TreeBaseProps, 'onSelect' | 'onExpand' | 'onNodeCli
|
|
|
12
12
|
onNodeClick: OnNodeClick;
|
|
13
13
|
isMultiSelect: boolean;
|
|
14
14
|
isSingleSelect: boolean;
|
|
15
|
+
isSelectable: boolean;
|
|
15
16
|
focusedNodeId?: TreeNodeId;
|
|
16
17
|
setFocusPosition(id: TreeNodeId): void;
|
|
17
18
|
resetFocusPosition(): void;
|
|
@@ -39,6 +40,7 @@ export const TreeContext = createContext<TreeContextProps>({
|
|
|
39
40
|
focusableNodeIds: [],
|
|
40
41
|
isMultiSelect: false,
|
|
41
42
|
isSingleSelect: false,
|
|
43
|
+
isSelectable: false,
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
export function TreeContextProvider({ children, value }: TreeContextProviderProps) {
|
|
@@ -53,6 +55,7 @@ export function TreeContextProvider({ children, value }: TreeContextProviderProp
|
|
|
53
55
|
|
|
54
56
|
const isMultiSelect = selectionMode === SELECTION_MODE.Multi;
|
|
55
57
|
const isSingleSelect = selectionMode === SELECTION_MODE.Single;
|
|
58
|
+
const isSelectable = Boolean(selectionMode);
|
|
56
59
|
|
|
57
60
|
const [expandedNodes, onExpandHandler] = useUncontrolledProp<TreeNodeId[]>(
|
|
58
61
|
value.expandedNodes,
|
|
@@ -75,7 +78,7 @@ export function TreeContextProvider({ children, value }: TreeContextProviderProp
|
|
|
75
78
|
|
|
76
79
|
const onSelect = useCallback<TreeContextProps['onSelect']>(
|
|
77
80
|
(node, parentNode) => {
|
|
78
|
-
if (node.disabled) return;
|
|
81
|
+
if (!isSelectable || node.disabled) return;
|
|
79
82
|
|
|
80
83
|
if (isSingleSelect) {
|
|
81
84
|
onSelectHandler(node.id, node);
|
|
@@ -88,7 +91,7 @@ export function TreeContextProvider({ children, value }: TreeContextProviderProp
|
|
|
88
91
|
onSelectHandler(updatedSelectedNodes, node);
|
|
89
92
|
}
|
|
90
93
|
},
|
|
91
|
-
[isSingleSelect, onSelectHandler, selectedNodes],
|
|
94
|
+
[isSingleSelect, onSelectHandler, selectedNodes, isSelectable],
|
|
92
95
|
);
|
|
93
96
|
|
|
94
97
|
const focusableNodeIds = useMemo(() => {
|
|
@@ -146,6 +149,7 @@ export function TreeContextProvider({ children, value }: TreeContextProviderProp
|
|
|
146
149
|
selected: selectedNodes,
|
|
147
150
|
isSingleSelect,
|
|
148
151
|
isMultiSelect,
|
|
152
|
+
isSelectable,
|
|
149
153
|
expandedNodes,
|
|
150
154
|
onExpand,
|
|
151
155
|
onSelect,
|
|
@@ -46,6 +46,7 @@ export function TreeNode({
|
|
|
46
46
|
}: TreeNodeComponentProps) {
|
|
47
47
|
const {
|
|
48
48
|
isMultiSelect,
|
|
49
|
+
isSelectable,
|
|
49
50
|
onNodeClick,
|
|
50
51
|
selected,
|
|
51
52
|
expandedNodes,
|
|
@@ -58,6 +59,7 @@ export function TreeNode({
|
|
|
58
59
|
setFocusIndex,
|
|
59
60
|
focusableNodeIds,
|
|
60
61
|
showLines,
|
|
62
|
+
showIcons,
|
|
61
63
|
} = useTreeContext();
|
|
62
64
|
|
|
63
65
|
const [isDroplistOpen, setDroplistOpen] = useState(false);
|
|
@@ -86,12 +88,14 @@ export function TreeNode({
|
|
|
86
88
|
}, [isFocused]);
|
|
87
89
|
|
|
88
90
|
const treeNodeIcon = useMemo(() => {
|
|
91
|
+
if (!showIcons) return undefined;
|
|
92
|
+
|
|
89
93
|
if (isExpandable) {
|
|
90
94
|
return isExpanded ? expandedIcon : collapsedIcon;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
return icon;
|
|
94
|
-
}, [isExpandable, icon, isExpanded, expandedIcon, collapsedIcon]);
|
|
98
|
+
}, [showIcons, isExpandable, icon, isExpanded, expandedIcon, collapsedIcon]);
|
|
95
99
|
|
|
96
100
|
const handleClick: TreeNodeProps['onClick'] = e => {
|
|
97
101
|
onNodeClick(
|
|
@@ -230,7 +234,7 @@ export function TreeNode({
|
|
|
230
234
|
<div
|
|
231
235
|
role='treeitem'
|
|
232
236
|
aria-expanded={isExpanded}
|
|
233
|
-
aria-selected={isSelected}
|
|
237
|
+
aria-selected={isSelectable ? isSelected : undefined}
|
|
234
238
|
aria-disabled={disabled}
|
|
235
239
|
data-multiselect={isMultiSelect || undefined}
|
|
236
240
|
data-droplist-active={isDroplistOpen || isDroplistTriggerFocused || undefined}
|
|
@@ -258,9 +262,11 @@ export function TreeNode({
|
|
|
258
262
|
</div>
|
|
259
263
|
)}
|
|
260
264
|
|
|
261
|
-
|
|
262
|
-
{treeNodeIcon}
|
|
263
|
-
|
|
265
|
+
{treeNodeIcon && (
|
|
266
|
+
<div className={styles.treeNodeIcon} data-test-id={TEST_IDS.icon}>
|
|
267
|
+
{treeNodeIcon}
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
264
270
|
|
|
265
271
|
<Typography.SansBodyM tag='div' className={styles.treeNodeTitle}>
|
|
266
272
|
<TruncateString text={title} data-test-id={TEST_IDS.title} />
|
package/src/types.ts
CHANGED
|
@@ -52,6 +52,7 @@ export type TreeCommonProps = {
|
|
|
52
52
|
* Режим выбора элементов:
|
|
53
53
|
* <br> - `Single` - одиночный выбор
|
|
54
54
|
* <br> - `Multi` - множественный выбор
|
|
55
|
+
* <br> - `undefined` - без выбора
|
|
55
56
|
*/
|
|
56
57
|
selectionMode?: SelectionMode;
|
|
57
58
|
/** Обработчик клика по элементу дерева */
|
|
@@ -68,11 +69,22 @@ export type TreeCommonProps = {
|
|
|
68
69
|
parentActions?(node: TreeNodeProps): ItemSingleProps[];
|
|
69
70
|
/** Дополнительные действия для элемента-потомка */
|
|
70
71
|
nodeActions?(node: TreeNodeProps): ItemSingleProps[];
|
|
72
|
+
/**
|
|
73
|
+
* Флаг отвечающий за отображение иконок у элементов дерева
|
|
74
|
+
* @default true
|
|
75
|
+
*/
|
|
76
|
+
showIcons?: boolean;
|
|
71
77
|
/** Флаг отвечающий за отображение линий вложенности */
|
|
72
78
|
showLines?: boolean;
|
|
73
79
|
className?: string;
|
|
74
80
|
};
|
|
75
81
|
|
|
82
|
+
export type TreeView = Omit<TreeCommonProps, 'selected' | 'selectionMode'> & {
|
|
83
|
+
selectionMode?: never;
|
|
84
|
+
selected?: never;
|
|
85
|
+
onSelect?: never;
|
|
86
|
+
};
|
|
87
|
+
|
|
76
88
|
export type TreeSingleSelect = Omit<TreeCommonProps, 'selected'> & {
|
|
77
89
|
selectionMode: 'single';
|
|
78
90
|
/** Состояние для выбраного элемента */
|
|
@@ -96,4 +108,4 @@ export type TreeMultiSelect = Omit<TreeCommonProps, 'selected'> & {
|
|
|
96
108
|
onSelect?(selectedKeys: TreeNodeId[], node: TreeNodeProps): void;
|
|
97
109
|
};
|
|
98
110
|
|
|
99
|
-
export type TreeBaseProps = TreeMultiSelect | TreeSingleSelect;
|
|
111
|
+
export type TreeBaseProps = TreeView | TreeMultiSelect | TreeSingleSelect;
|