@snack-uikit/tree 0.1.8-preview-85c5f47b.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 +89 -0
- package/LICENSE +201 -0
- package/README.md +58 -0
- package/dist/components/Tree/Tree.d.ts +8 -0
- package/dist/components/Tree/Tree.js +35 -0
- package/dist/components/Tree/index.d.ts +1 -0
- package/dist/components/Tree/index.js +1 -0
- package/dist/components/Tree/styles.module.css +4 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.js +21 -0
- package/dist/contexts/TreeContext.d.ts +23 -0
- package/dist/contexts/TreeContext.js +99 -0
- package/dist/helperComponents/ExpandableTreeNode/ExpandableTreeNode.d.ts +11 -0
- package/dist/helperComponents/ExpandableTreeNode/ExpandableTreeNode.js +90 -0
- package/dist/helperComponents/ExpandableTreeNode/index.d.ts +1 -0
- package/dist/helperComponents/ExpandableTreeNode/index.js +1 -0
- package/dist/helperComponents/ExpandableTreeNode/styles.module.css +34 -0
- package/dist/helperComponents/TreeItem/TreeItem.d.ts +8 -0
- package/dist/helperComponents/TreeItem/TreeItem.js +12 -0
- package/dist/helperComponents/TreeItem/index.d.ts +1 -0
- package/dist/helperComponents/TreeItem/index.js +1 -0
- package/dist/helperComponents/TreeLine/TreeLine.d.ts +7 -0
- package/dist/helperComponents/TreeLine/TreeLine.js +6 -0
- package/dist/helperComponents/TreeLine/index.d.ts +1 -0
- package/dist/helperComponents/TreeLine/index.js +1 -0
- package/dist/helperComponents/TreeLine/styles.module.css +37 -0
- package/dist/helperComponents/TreeNode/TreeNode.d.ts +11 -0
- package/dist/helperComponents/TreeNode/TreeNode.js +146 -0
- package/dist/helperComponents/TreeNode/components/TreeNodeActions.d.ts +14 -0
- package/dist/helperComponents/TreeNode/components/TreeNodeActions.js +39 -0
- package/dist/helperComponents/TreeNode/components/index.d.ts +1 -0
- package/dist/helperComponents/TreeNode/components/index.js +1 -0
- package/dist/helperComponents/TreeNode/index.d.ts +1 -0
- package/dist/helperComponents/TreeNode/index.js +1 -0
- package/dist/helperComponents/TreeNode/styles.module.css +114 -0
- package/dist/helperComponents/TreeNode/utils.d.ts +2 -0
- package/dist/helperComponents/TreeNode/utils.js +3 -0
- package/dist/helperComponents/index.d.ts +4 -0
- package/dist/helperComponents/index.js +4 -0
- package/dist/helpers.d.ts +11 -0
- package/dist/helpers.js +78 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +1 -0
- package/package.json +47 -0
- package/src/components/Tree/Tree.tsx +55 -0
- package/src/components/Tree/index.ts +1 -0
- package/src/components/Tree/styles.module.scss +4 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +22 -0
- package/src/contexts/TreeContext.tsx +165 -0
- package/src/helperComponents/ExpandableTreeNode/ExpandableTreeNode.tsx +126 -0
- package/src/helperComponents/ExpandableTreeNode/index.ts +1 -0
- package/src/helperComponents/ExpandableTreeNode/styles.module.scss +43 -0
- package/src/helperComponents/TreeItem/TreeItem.tsx +22 -0
- package/src/helperComponents/TreeItem/index.ts +1 -0
- package/src/helperComponents/TreeLine/TreeLine.tsx +22 -0
- package/src/helperComponents/TreeLine/index.ts +1 -0
- package/src/helperComponents/TreeLine/styles.module.scss +58 -0
- package/src/helperComponents/TreeNode/TreeNode.tsx +288 -0
- package/src/helperComponents/TreeNode/components/TreeNodeActions.tsx +109 -0
- package/src/helperComponents/TreeNode/components/index.ts +1 -0
- package/src/helperComponents/TreeNode/index.ts +1 -0
- package/src/helperComponents/TreeNode/styles.module.scss +144 -0
- package/src/helperComponents/TreeNode/utils.ts +5 -0
- package/src/helperComponents/index.ts +4 -0
- package/src/helpers.ts +107 -0
- package/src/index.ts +2 -0
- package/src/types.ts +97 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
21
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
22
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
23
|
+
import useTransition from 'react-transition-state';
|
|
24
|
+
import { TEST_IDS, TRANSITION_TIMING } from '../../constants';
|
|
25
|
+
import { useTreeContext } from '../../contexts/TreeContext';
|
|
26
|
+
import { TreeItem } from '../TreeItem';
|
|
27
|
+
import { TreeLine } from '../TreeLine';
|
|
28
|
+
import { TreeNode } from '../TreeNode';
|
|
29
|
+
import styles from './styles.module.css';
|
|
30
|
+
export function ExpandableTreeNode(_a) {
|
|
31
|
+
var _b = _a.node, { 'data-test-id': dataTestId } = _b, node = __rest(_b, ['data-test-id']), { parentNode, tabIndexAvailable } = _a;
|
|
32
|
+
const { expandedNodes, onExpand, onDataLoad, showLines } = useTreeContext();
|
|
33
|
+
const isExpandable = Boolean(node.nested);
|
|
34
|
+
const isExpanded = (expandedNodes === null || expandedNodes === void 0 ? void 0 : expandedNodes.includes(node.id)) || false;
|
|
35
|
+
const [isLoading, setLoading] = useState(false);
|
|
36
|
+
const [state, toggle] = useTransition({
|
|
37
|
+
timeout: TRANSITION_TIMING.accordionFolding,
|
|
38
|
+
initialEntered: isExpanded || undefined,
|
|
39
|
+
enter: isExpanded,
|
|
40
|
+
});
|
|
41
|
+
const showContent = node.nested && node.nested.length > 0 && state.status !== 'exited';
|
|
42
|
+
const isLineHalfHeight = useMemo(() => {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
if (!((_a = node.nested) === null || _a === void 0 ? void 0 : _a.length))
|
|
45
|
+
return false;
|
|
46
|
+
return !((_b = node.nested.at(-1)) === null || _b === void 0 ? void 0 : _b.nested);
|
|
47
|
+
}, [node.nested]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
var _a;
|
|
50
|
+
if (((_a = node.nested) === null || _a === void 0 ? void 0 : _a.length) && isExpanded && state.status === 'exited') {
|
|
51
|
+
toggle(true);
|
|
52
|
+
}
|
|
53
|
+
}, [isExpanded, node.nested, state.status]);
|
|
54
|
+
const toggleExpand = () => __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
if (node.disabled)
|
|
56
|
+
return;
|
|
57
|
+
if (node.nested && !node.nested.length && !isExpanded && onDataLoad) {
|
|
58
|
+
setLoading(true);
|
|
59
|
+
yield onDataLoad(node).finally(() => {
|
|
60
|
+
setLoading(false);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
onExpand(node);
|
|
64
|
+
toggle();
|
|
65
|
+
});
|
|
66
|
+
const handleKeyDown = e => {
|
|
67
|
+
if (!isExpandable)
|
|
68
|
+
return;
|
|
69
|
+
switch (e.key) {
|
|
70
|
+
case 'ArrowRight': {
|
|
71
|
+
if (!isExpanded) {
|
|
72
|
+
toggleExpand();
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
case 'ArrowLeft': {
|
|
77
|
+
if (isExpanded) {
|
|
78
|
+
toggleExpand();
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return (_jsxs("div", Object.assign({ className: styles.expandableTreeNode, "data-test-id": dataTestId, "data-expandable": Boolean(node.nested) || undefined, "data-disabled": node.disabled || undefined }, { children: [_jsx(TreeNode, Object.assign({}, node, { isLoading: isLoading, parentNode: parentNode, onChevronClick: toggleExpand, onKeyDown: handleKeyDown, tabIndexAvailable: tabIndexAvailable })), node.nested && (_jsx("div", Object.assign({ className: styles.expandableWrap, "data-expanded": isExpanded || undefined, "data-test-id": TEST_IDS.expandable }, { children: showContent && (_jsxs("div", Object.assign({ className: styles.expandableContent, "data-test-id": TEST_IDS.expandableContent }, { children: [_jsx(TreeLine, { visible: showLines, halfHeight: isLineHalfHeight, "data-test-id": TEST_IDS.line }), _jsx("div", Object.assign({ className: styles.expandableNested }, { children: node.nested.map(nestedNode => {
|
|
87
|
+
const parent = { id: node.id, nested: node.nested, parentNode };
|
|
88
|
+
return _jsx(TreeItem, { node: nestedNode, parentNode: parent }, nestedNode.id);
|
|
89
|
+
}) }))] }))) })))] })));
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ExpandableTreeNode';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ExpandableTreeNode';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.expandableTreeNode{
|
|
2
|
+
position:relative;
|
|
3
|
+
width:100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.expandableContent{
|
|
7
|
+
overflow:hidden;
|
|
8
|
+
display:flex;
|
|
9
|
+
box-sizing:border-box;
|
|
10
|
+
min-height:0;
|
|
11
|
+
visibility:hidden;
|
|
12
|
+
transition:visibility 200ms;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.expandableWrap{
|
|
16
|
+
overflow:hidden;
|
|
17
|
+
display:grid;
|
|
18
|
+
grid-template-rows:0fr;
|
|
19
|
+
box-sizing:border-box;
|
|
20
|
+
width:100%;
|
|
21
|
+
transition:grid-template-rows 200ms;
|
|
22
|
+
}
|
|
23
|
+
.expandableWrap[data-expanded=true]{
|
|
24
|
+
overflow:unset;
|
|
25
|
+
grid-template-rows:1fr;
|
|
26
|
+
}
|
|
27
|
+
.expandableWrap[data-expanded=true] .expandableContent{
|
|
28
|
+
visibility:visible;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.expandableNested{
|
|
32
|
+
width:100%;
|
|
33
|
+
min-width:0;
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ParentNode, TreeNodeProps } from '../../types';
|
|
2
|
+
type TreeItemProps = {
|
|
3
|
+
node: TreeNodeProps;
|
|
4
|
+
parentNode?: ParentNode;
|
|
5
|
+
tabIndexAvailable?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function TreeItem({ node, tabIndexAvailable, parentNode }: TreeItemProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { TEST_IDS } from '../../constants';
|
|
3
|
+
import { ExpandableTreeNode } from '../ExpandableTreeNode';
|
|
4
|
+
import { TreeNode } from '../TreeNode';
|
|
5
|
+
export function TreeItem({ node, tabIndexAvailable, parentNode }) {
|
|
6
|
+
const dataTestId = node['data-test-id'] || TEST_IDS.node;
|
|
7
|
+
const nodeProps = Object.assign(Object.assign({}, node), { 'data-test-id': dataTestId });
|
|
8
|
+
if (node.nested) {
|
|
9
|
+
return _jsx(ExpandableTreeNode, { node: nodeProps, tabIndexAvailable: tabIndexAvailable, parentNode: parentNode });
|
|
10
|
+
}
|
|
11
|
+
return _jsx(TreeNode, Object.assign({}, nodeProps, { tabIndexAvailable: tabIndexAvailable, parentNode: parentNode }));
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeItem';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeItem';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { TEST_IDS } from '../../constants';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
export function TreeLine({ halfWidth, halfHeight, horizontal, visible }) {
|
|
5
|
+
return (_jsx("div", { className: styles.treeLine, "data-horizontal": horizontal || undefined, "data-half-width": halfWidth || undefined, "data-half-height": halfHeight || undefined, "data-visible": visible || undefined, "data-test-id": TEST_IDS.line }));
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeLine';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeLine';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.treeLine{
|
|
2
|
+
pointer-events:none;
|
|
3
|
+
position:relative;
|
|
4
|
+
flex-shrink:0;
|
|
5
|
+
width:var(--size-tree-item-line-width, 24px);
|
|
6
|
+
visibility:hidden;
|
|
7
|
+
}
|
|
8
|
+
.treeLine[data-visible]{
|
|
9
|
+
visibility:visible;
|
|
10
|
+
}
|
|
11
|
+
.treeLine[data-visible]::after{
|
|
12
|
+
content:"";
|
|
13
|
+
position:absolute;
|
|
14
|
+
top:0;
|
|
15
|
+
left:50%;
|
|
16
|
+
transform:translateX(-50%);
|
|
17
|
+
width:1px;
|
|
18
|
+
height:calc(100% - var(--size-tree-item-height, 32px) / 2);
|
|
19
|
+
background:var(--sys-neutral-decor-default, #dedede);
|
|
20
|
+
}
|
|
21
|
+
.treeLine[data-half-height]::after{
|
|
22
|
+
height:calc(100% - var(--size-tree-item-height, 32px) / 2);
|
|
23
|
+
}
|
|
24
|
+
.treeLine[data-horizontal]::after{
|
|
25
|
+
top:50%;
|
|
26
|
+
left:-50%;
|
|
27
|
+
transform:translateY(-50%);
|
|
28
|
+
width:calc(150% - var(--space-tree-item-child-line-padding, 8px));
|
|
29
|
+
height:1px;
|
|
30
|
+
}
|
|
31
|
+
.treeLine[data-horizontal][data-half-width]{
|
|
32
|
+
width:0;
|
|
33
|
+
}
|
|
34
|
+
.treeLine[data-horizontal][data-half-width]::after{
|
|
35
|
+
left:calc(0px - var(--size-tree-item-line-width, 24px) / 2);
|
|
36
|
+
width:calc(var(--size-tree-item-line-width, 24px) / 2);
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { KeyboardEventHandler, MouseEventHandler } from 'react';
|
|
2
|
+
import { ParentNode, TreeNodeProps } from '../../types';
|
|
3
|
+
export type { TreeNodeProps };
|
|
4
|
+
type TreeNodeComponentProps = TreeNodeProps & {
|
|
5
|
+
onChevronClick?: MouseEventHandler<HTMLElement>;
|
|
6
|
+
onKeyDown?: KeyboardEventHandler<HTMLElement>;
|
|
7
|
+
isLoading?: boolean;
|
|
8
|
+
parentNode?: ParentNode;
|
|
9
|
+
tabIndexAvailable?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function TreeNode({ id, title, icon, expandedIcon, collapsedIcon, disabled, onClick, nested, className, onChevronClick, onKeyDown, isLoading, parentNode, tabIndexAvailable, ...rest }: TreeNodeComponentProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,146 @@
|
|
|
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 cn from 'classnames';
|
|
14
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
15
|
+
import { ButtonFunction } from '@snack-uikit/button';
|
|
16
|
+
import { ChevronRightSVG, FileSVG, FolderOpenSVG, FolderSVG } from '@snack-uikit/icons';
|
|
17
|
+
import { Checkbox } from '@snack-uikit/toggles';
|
|
18
|
+
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
19
|
+
import { Typography } from '@snack-uikit/typography';
|
|
20
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
21
|
+
import { TEST_IDS } from '../../constants';
|
|
22
|
+
import { useTreeContext } from '../../contexts/TreeContext';
|
|
23
|
+
import { checkNestedNodesSelection } from '../../helpers';
|
|
24
|
+
import { TreeLine } from '../TreeLine';
|
|
25
|
+
import { TreeNodeActions } from './components';
|
|
26
|
+
import styles from './styles.module.css';
|
|
27
|
+
import { stopPropagationClick } from './utils';
|
|
28
|
+
export function TreeNode(_a) {
|
|
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();
|
|
31
|
+
const [isDroplistOpen, setDroplistOpen] = useState(false);
|
|
32
|
+
const [isDroplistTriggerFocused, setFocusDroplistTrigger] = useState(false);
|
|
33
|
+
const ref = useRef(null);
|
|
34
|
+
const isExpandable = Array.isArray(nested);
|
|
35
|
+
const isExpanded = isExpandable ? expandedNodes === null || expandedNodes === void 0 ? void 0 : expandedNodes.includes(id) : undefined;
|
|
36
|
+
const nestedNodesSelection = useMemo(() => {
|
|
37
|
+
if (!nested || !Array.isArray(selected) || !(selected === null || selected === void 0 ? void 0 : selected.length))
|
|
38
|
+
return undefined;
|
|
39
|
+
return checkNestedNodesSelection(nested, selected);
|
|
40
|
+
}, [nested, selected]);
|
|
41
|
+
const isSelected = (Array.isArray(selected) ? selected.includes(id) || (nestedNodesSelection === null || nestedNodesSelection === void 0 ? void 0 : nestedNodesSelection.allSelected) : selected === id) || false;
|
|
42
|
+
const isFocused = focusedNodeId === id;
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (ref.current && isFocused) {
|
|
45
|
+
ref.current.focus();
|
|
46
|
+
}
|
|
47
|
+
}, [isFocused]);
|
|
48
|
+
const treeNodeIcon = useMemo(() => {
|
|
49
|
+
if (isExpandable) {
|
|
50
|
+
return isExpanded ? expandedIcon : collapsedIcon;
|
|
51
|
+
}
|
|
52
|
+
return icon;
|
|
53
|
+
}, [isExpandable, icon, isExpanded, expandedIcon, collapsedIcon]);
|
|
54
|
+
const handleClick = e => {
|
|
55
|
+
onNodeClick({
|
|
56
|
+
id,
|
|
57
|
+
title,
|
|
58
|
+
disabled,
|
|
59
|
+
nested,
|
|
60
|
+
onClick,
|
|
61
|
+
}, e);
|
|
62
|
+
};
|
|
63
|
+
const handleSelect = () => {
|
|
64
|
+
onSelect({
|
|
65
|
+
id,
|
|
66
|
+
disabled,
|
|
67
|
+
nested,
|
|
68
|
+
}, parentNode);
|
|
69
|
+
};
|
|
70
|
+
const handleFocus = (e) => {
|
|
71
|
+
var _a;
|
|
72
|
+
setFocusPosition(id);
|
|
73
|
+
if (!e) {
|
|
74
|
+
setFocusDroplistTrigger(false);
|
|
75
|
+
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const handleBlurActions = () => {
|
|
79
|
+
var _a;
|
|
80
|
+
if (isDroplistTriggerFocused && !isDroplistOpen) {
|
|
81
|
+
setFocusDroplistTrigger(false);
|
|
82
|
+
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const handleKeyDown = e => {
|
|
86
|
+
var _a, _b;
|
|
87
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
|
|
88
|
+
const focusableCount = focusableNodeIds.length;
|
|
89
|
+
switch (e.key) {
|
|
90
|
+
case 'ArrowDown': {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
setFocusIndex((prev = 0) => {
|
|
93
|
+
if (prev >= focusableCount - 1) {
|
|
94
|
+
return focusableCount - 1;
|
|
95
|
+
}
|
|
96
|
+
return prev + 1;
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
case 'ArrowUp': {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
setFocusIndex((prev = 0) => {
|
|
103
|
+
if (!prev) {
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
return prev - 1;
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
case 'ArrowRight': {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
if (isExpanded || !isExpandable || disabled) {
|
|
113
|
+
setFocusDroplistTrigger(true);
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
case 'ArrowLeft': {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
if (isDroplistTriggerFocused) {
|
|
120
|
+
setFocusDroplistTrigger(false);
|
|
121
|
+
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
case 'Escape': {
|
|
126
|
+
(_b = ref.current) === null || _b === void 0 ? void 0 : _b.blur();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
case ' ':
|
|
130
|
+
case 'Enter': {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
handleSelect();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
default:
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
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: ButtonFunction.sizes.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", Object.assign({ 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", Object.assign({ className: styles.treeNodeCheckboxWrap }, { children: _jsx(Checkbox, { size: Checkbox.sizes.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", Object.assign({ className: styles.treeNodeIcon, "data-test-id": TEST_IDS.icon }, { children: treeNodeIcon })), _jsx(Typography.SansBodyM, Object.assign({ tag: Typography.tags.div, className: styles.treeNodeTitle }, { children: _jsx(TruncateString, { text: title, "data-test-id": TEST_IDS.title }) })), getNodeActions && (_jsx(TreeNodeActions, { getNodeActions: getNodeActions, node: {
|
|
141
|
+
id,
|
|
142
|
+
title,
|
|
143
|
+
disabled,
|
|
144
|
+
nested,
|
|
145
|
+
}, focusNode: handleFocus, onBlurActions: handleBlurActions, isDroplistTriggerFocused: isDroplistTriggerFocused, isDroplistOpen: isDroplistOpen, setDroplistOpen: setDroplistOpen }))] }))] })));
|
|
146
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
import { ItemSingleProps } from '@snack-uikit/droplist';
|
|
3
|
+
import { TreeNodeProps } from '../../../types';
|
|
4
|
+
type TreeNodeActionsProps = {
|
|
5
|
+
isDroplistOpen: boolean;
|
|
6
|
+
setDroplistOpen: Dispatch<SetStateAction<boolean>>;
|
|
7
|
+
getNodeActions(node: TreeNodeProps): ItemSingleProps[];
|
|
8
|
+
node: TreeNodeProps;
|
|
9
|
+
isDroplistTriggerFocused: boolean;
|
|
10
|
+
focusNode(): void;
|
|
11
|
+
onBlurActions(): void;
|
|
12
|
+
};
|
|
13
|
+
export declare function TreeNodeActions({ getNodeActions, isDroplistTriggerFocused, focusNode, isDroplistOpen, setDroplistOpen, onBlurActions, node, }: TreeNodeActionsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { ButtonFunction } from '@snack-uikit/button';
|
|
4
|
+
import { Droplist } from '@snack-uikit/droplist';
|
|
5
|
+
import { KebabSVG } from '@snack-uikit/icons';
|
|
6
|
+
import { TEST_IDS } from '../../../constants';
|
|
7
|
+
import styles from '../styles.module.css';
|
|
8
|
+
import { stopPropagationClick } from '../utils';
|
|
9
|
+
export function TreeNodeActions({ getNodeActions, isDroplistTriggerFocused, focusNode, isDroplistOpen, setDroplistOpen, onBlurActions, node, }) {
|
|
10
|
+
const droplistActions = getNodeActions(node);
|
|
11
|
+
const { triggerElementRef, handleDroplistFocusLeave, handleDroplistItemKeyDown, handleTriggerKeyDown, handleDroplistItemClick, firstElementRefCallback, } = Droplist.useKeyboardNavigation({ setDroplistOpen });
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (triggerElementRef.current && isDroplistTriggerFocused) {
|
|
14
|
+
triggerElementRef.current.focus();
|
|
15
|
+
}
|
|
16
|
+
}, [isDroplistTriggerFocused, triggerElementRef]);
|
|
17
|
+
if (!droplistActions.length) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const handleKeyDown = e => {
|
|
21
|
+
handleTriggerKeyDown(e);
|
|
22
|
+
switch (e.key) {
|
|
23
|
+
case 'Tab': {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
case 'ArrowLeft': {
|
|
28
|
+
if (isDroplistTriggerFocused) {
|
|
29
|
+
focusNode();
|
|
30
|
+
setDroplistOpen(false);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
default:
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return (_jsx("div", Object.assign({ role: 'presentation', className: styles.treeNodeActions, "data-focused": isDroplistTriggerFocused || undefined, onClick: stopPropagationClick }, { children: _jsx(Droplist, Object.assign({ open: isDroplistOpen, onOpenChange: setDroplistOpen, onFocusLeave: handleDroplistFocusLeave, firstElementRefCallback: firstElementRefCallback, triggerRef: triggerElementRef, placement: Droplist.placements.BottomEnd, triggerElement: _jsx(ButtonFunction, { size: ButtonFunction.sizes.Xs, icon: _jsx(KebabSVG, { size: 24 }), onKeyDown: handleKeyDown, onBlur: onBlurActions, tabIndex: -1, "data-test-id": TEST_IDS.droplistTrigger }) }, { children: droplistActions.map(action => (_jsx(Droplist.ItemSingle, Object.assign({}, action, { onKeyDown: handleDroplistItemKeyDown, onClick: e => handleDroplistItemClick(e, action.onClick), "data-test-id": TEST_IDS.droplistAction }), action.option))) })) })));
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeNodeActions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeNodeActions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeNode';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TreeNode';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
.treeNode{
|
|
2
|
+
display:flex;
|
|
3
|
+
align-items:center;
|
|
4
|
+
box-sizing:border-box;
|
|
5
|
+
width:100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.treeNodeIcon{
|
|
9
|
+
display:flex;
|
|
10
|
+
flex-shrink:0;
|
|
11
|
+
align-items:center;
|
|
12
|
+
justify-content:center;
|
|
13
|
+
box-sizing:border-box;
|
|
14
|
+
color:var(--sys-neutral-text-support, #565656);
|
|
15
|
+
}
|
|
16
|
+
.treeNodeIcon svg{
|
|
17
|
+
width:var(--dimension-3m, 24px) !important;
|
|
18
|
+
height:var(--dimension-3m, 24px) !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.treeNodeTitle{
|
|
22
|
+
flex-grow:1;
|
|
23
|
+
box-sizing:border-box;
|
|
24
|
+
min-width:var(--size-tree-item-checkbox-wrap, 24px);
|
|
25
|
+
color:var(--sys-neutral-text-main, #333333);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.treeNodeActions{
|
|
29
|
+
display:flex;
|
|
30
|
+
visibility:hidden;
|
|
31
|
+
}
|
|
32
|
+
.treeNodeActions:focus-visible, .treeNodeActions[data-focused]{
|
|
33
|
+
visibility:visible;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.treeNodeContent{
|
|
37
|
+
height:var(--size-tree-item-height, 32px);
|
|
38
|
+
gap:var(--space-tree-item-content-layout-gap, 4px);
|
|
39
|
+
padding-right:var(--space-tree-item-content-layout-padding, 4px);
|
|
40
|
+
padding-left:var(--space-tree-item-content-layout-padding, 4px);
|
|
41
|
+
border-radius:var(--radius-tree-item-content-layout, 8px);
|
|
42
|
+
cursor:pointer;
|
|
43
|
+
position:relative;
|
|
44
|
+
display:flex;
|
|
45
|
+
align-items:center;
|
|
46
|
+
box-sizing:border-box;
|
|
47
|
+
width:100%;
|
|
48
|
+
border-radius:var(--radius-tree-item-content-layout, 8px);
|
|
49
|
+
}
|
|
50
|
+
.treeNodeContent::before{
|
|
51
|
+
pointer-events:none;
|
|
52
|
+
content:"";
|
|
53
|
+
position:absolute;
|
|
54
|
+
top:0;
|
|
55
|
+
left:0;
|
|
56
|
+
box-sizing:border-box;
|
|
57
|
+
width:100%;
|
|
58
|
+
height:100%;
|
|
59
|
+
opacity:0;
|
|
60
|
+
background-color:var(--sys-neutral-accent-default, #757575);
|
|
61
|
+
border-radius:inherit;
|
|
62
|
+
}
|
|
63
|
+
.treeNodeContent:hover::before, .treeNodeContent:focus-visible::before, .treeNodeContent[data-droplist-active]::before{
|
|
64
|
+
opacity:var(--opacity-a008, 0.08);
|
|
65
|
+
}
|
|
66
|
+
.treeNodeContent:hover .treeNodeActions, .treeNodeContent:focus-visible .treeNodeActions, .treeNodeContent[data-droplist-active] .treeNodeActions{
|
|
67
|
+
visibility:visible;
|
|
68
|
+
}
|
|
69
|
+
.treeNodeContent:focus-visible{
|
|
70
|
+
outline-width:var(--border-state-focus-s-border-width, 2px);
|
|
71
|
+
outline-style:var(--border-state-focus-s-border-style, solid);
|
|
72
|
+
outline-color:var(--border-state-focus-s-border-color, );
|
|
73
|
+
outline-offset:calc(var(--border-state-focus-s-border-width, 2px) * -1);
|
|
74
|
+
outline-color:var(--sys-available-complementary, #131313);
|
|
75
|
+
}
|
|
76
|
+
.treeNodeContent[data-multiselect]{
|
|
77
|
+
cursor:default;
|
|
78
|
+
}
|
|
79
|
+
.treeNodeContent[aria-disabled=true]{
|
|
80
|
+
cursor:not-allowed;
|
|
81
|
+
}
|
|
82
|
+
.treeNodeContent[aria-disabled=true]::before{
|
|
83
|
+
display:none;
|
|
84
|
+
}
|
|
85
|
+
.treeNodeContent[aria-disabled=true] .treeNodeTitle, .treeNodeContent[aria-disabled=true] .treeNodeIcon{
|
|
86
|
+
color:var(--sys-neutral-text-disabled, #9e9e9e);
|
|
87
|
+
}
|
|
88
|
+
.treeNodeContent[aria-selected=true]:not([data-multiselect])::before{
|
|
89
|
+
opacity:var(--opacity-a008, 0.08);
|
|
90
|
+
background-color:var(--sys-primary-accent-default, #794ed3);
|
|
91
|
+
}
|
|
92
|
+
.treeNodeContent[aria-selected=true]:not([data-multiselect]):hover::before{
|
|
93
|
+
opacity:var(--opacity-a016, 0.16);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.treeNodeCheckboxWrap{
|
|
97
|
+
width:var(--size-tree-item-checkbox-wrap, 24px);
|
|
98
|
+
height:var(--size-tree-item-checkbox-wrap, 24px);
|
|
99
|
+
display:flex;
|
|
100
|
+
flex-shrink:0;
|
|
101
|
+
align-items:center;
|
|
102
|
+
justify-content:center;
|
|
103
|
+
box-sizing:border-box;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.treeNodeExpandButton{
|
|
107
|
+
box-sizing:border-box;
|
|
108
|
+
}
|
|
109
|
+
.treeNodeExpandButton svg{
|
|
110
|
+
transition:transform 0.2s ease;
|
|
111
|
+
}
|
|
112
|
+
.treeNodeExpandButton[data-expanded] svg{
|
|
113
|
+
transform:rotate(90deg);
|
|
114
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ParentNode, TreeNodeId, TreeNodeProps } from './types';
|
|
2
|
+
export declare function checkNestedNodesSelection(nodes: TreeNodeProps[], selectedKeys: TreeNodeId[]): {
|
|
3
|
+
someSelected: boolean;
|
|
4
|
+
allSelected: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function findAllExpandedChildNodeIds(nodes: TreeNodeProps[], expandedNodes: TreeNodeId[]): string[];
|
|
7
|
+
export declare function lookupTreeForSelectedNodes({ node, selectedNodes, parentNode, }: {
|
|
8
|
+
node: Pick<TreeNodeProps, 'id' | 'nested' | 'disabled'>;
|
|
9
|
+
selectedNodes: TreeNodeId[];
|
|
10
|
+
parentNode?: ParentNode;
|
|
11
|
+
}): string[];
|