@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/LICENSE +201 -0
  3. package/README.md +58 -0
  4. package/dist/components/Tree/Tree.d.ts +8 -0
  5. package/dist/components/Tree/Tree.js +35 -0
  6. package/dist/components/Tree/index.d.ts +1 -0
  7. package/dist/components/Tree/index.js +1 -0
  8. package/dist/components/Tree/styles.module.css +4 -0
  9. package/dist/components/index.d.ts +1 -0
  10. package/dist/components/index.js +1 -0
  11. package/dist/constants.d.ts +20 -0
  12. package/dist/constants.js +21 -0
  13. package/dist/contexts/TreeContext.d.ts +23 -0
  14. package/dist/contexts/TreeContext.js +99 -0
  15. package/dist/helperComponents/ExpandableTreeNode/ExpandableTreeNode.d.ts +11 -0
  16. package/dist/helperComponents/ExpandableTreeNode/ExpandableTreeNode.js +90 -0
  17. package/dist/helperComponents/ExpandableTreeNode/index.d.ts +1 -0
  18. package/dist/helperComponents/ExpandableTreeNode/index.js +1 -0
  19. package/dist/helperComponents/ExpandableTreeNode/styles.module.css +34 -0
  20. package/dist/helperComponents/TreeItem/TreeItem.d.ts +8 -0
  21. package/dist/helperComponents/TreeItem/TreeItem.js +12 -0
  22. package/dist/helperComponents/TreeItem/index.d.ts +1 -0
  23. package/dist/helperComponents/TreeItem/index.js +1 -0
  24. package/dist/helperComponents/TreeLine/TreeLine.d.ts +7 -0
  25. package/dist/helperComponents/TreeLine/TreeLine.js +6 -0
  26. package/dist/helperComponents/TreeLine/index.d.ts +1 -0
  27. package/dist/helperComponents/TreeLine/index.js +1 -0
  28. package/dist/helperComponents/TreeLine/styles.module.css +37 -0
  29. package/dist/helperComponents/TreeNode/TreeNode.d.ts +11 -0
  30. package/dist/helperComponents/TreeNode/TreeNode.js +146 -0
  31. package/dist/helperComponents/TreeNode/components/TreeNodeActions.d.ts +14 -0
  32. package/dist/helperComponents/TreeNode/components/TreeNodeActions.js +39 -0
  33. package/dist/helperComponents/TreeNode/components/index.d.ts +1 -0
  34. package/dist/helperComponents/TreeNode/components/index.js +1 -0
  35. package/dist/helperComponents/TreeNode/index.d.ts +1 -0
  36. package/dist/helperComponents/TreeNode/index.js +1 -0
  37. package/dist/helperComponents/TreeNode/styles.module.css +114 -0
  38. package/dist/helperComponents/TreeNode/utils.d.ts +2 -0
  39. package/dist/helperComponents/TreeNode/utils.js +3 -0
  40. package/dist/helperComponents/index.d.ts +4 -0
  41. package/dist/helperComponents/index.js +4 -0
  42. package/dist/helpers.d.ts +11 -0
  43. package/dist/helpers.js +78 -0
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.js +1 -0
  46. package/dist/types.d.ts +86 -0
  47. package/dist/types.js +1 -0
  48. package/package.json +47 -0
  49. package/src/components/Tree/Tree.tsx +55 -0
  50. package/src/components/Tree/index.ts +1 -0
  51. package/src/components/Tree/styles.module.scss +4 -0
  52. package/src/components/index.ts +1 -0
  53. package/src/constants.ts +22 -0
  54. package/src/contexts/TreeContext.tsx +165 -0
  55. package/src/helperComponents/ExpandableTreeNode/ExpandableTreeNode.tsx +126 -0
  56. package/src/helperComponents/ExpandableTreeNode/index.ts +1 -0
  57. package/src/helperComponents/ExpandableTreeNode/styles.module.scss +43 -0
  58. package/src/helperComponents/TreeItem/TreeItem.tsx +22 -0
  59. package/src/helperComponents/TreeItem/index.ts +1 -0
  60. package/src/helperComponents/TreeLine/TreeLine.tsx +22 -0
  61. package/src/helperComponents/TreeLine/index.ts +1 -0
  62. package/src/helperComponents/TreeLine/styles.module.scss +58 -0
  63. package/src/helperComponents/TreeNode/TreeNode.tsx +288 -0
  64. package/src/helperComponents/TreeNode/components/TreeNodeActions.tsx +109 -0
  65. package/src/helperComponents/TreeNode/components/index.ts +1 -0
  66. package/src/helperComponents/TreeNode/index.ts +1 -0
  67. package/src/helperComponents/TreeNode/styles.module.scss +144 -0
  68. package/src/helperComponents/TreeNode/utils.ts +5 -0
  69. package/src/helperComponents/index.ts +4 -0
  70. package/src/helpers.ts +107 -0
  71. package/src/index.ts +2 -0
  72. 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,7 @@
1
+ export type TreeLineProps = {
2
+ halfWidth?: boolean;
3
+ halfHeight?: boolean;
4
+ visible?: boolean;
5
+ horizontal?: boolean;
6
+ };
7
+ export declare function TreeLine({ halfWidth, halfHeight, horizontal, visible }: TreeLineProps): import("react/jsx-runtime").JSX.Element;
@@ -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,2 @@
1
+ import { MouseEvent } from 'react';
2
+ export declare function stopPropagationClick(e: MouseEvent<HTMLInputElement>): void;
@@ -0,0 +1,3 @@
1
+ export function stopPropagationClick(e) {
2
+ e.stopPropagation();
3
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ExpandableTreeNode';
2
+ export * from './TreeNode';
3
+ export * from './TreeLine';
4
+ export * from './TreeItem';
@@ -0,0 +1,4 @@
1
+ export * from './ExpandableTreeNode';
2
+ export * from './TreeNode';
3
+ export * from './TreeLine';
4
+ export * from './TreeItem';
@@ -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[];