@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,22 @@
1
+ import { TEST_IDS } from '../../constants';
2
+ import styles from './styles.module.scss';
3
+
4
+ export type TreeLineProps = {
5
+ halfWidth?: boolean;
6
+ halfHeight?: boolean;
7
+ visible?: boolean;
8
+ horizontal?: boolean;
9
+ };
10
+
11
+ export function TreeLine({ halfWidth, halfHeight, horizontal, visible }: TreeLineProps) {
12
+ return (
13
+ <div
14
+ className={styles.treeLine}
15
+ data-horizontal={horizontal || undefined}
16
+ data-half-width={halfWidth || undefined}
17
+ data-half-height={halfHeight || undefined}
18
+ data-visible={visible || undefined}
19
+ data-test-id={TEST_IDS.line}
20
+ />
21
+ );
22
+ }
@@ -0,0 +1 @@
1
+ export * from './TreeLine';
@@ -0,0 +1,58 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-tree';
2
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
+
4
+ .treeLine {
5
+ pointer-events: none;
6
+
7
+ position: relative;
8
+
9
+ flex-shrink: 0;
10
+
11
+ width: $size-tree-item-line-width;
12
+
13
+ visibility: hidden;
14
+
15
+ &[data-visible] {
16
+ visibility: visible;
17
+
18
+ &::after {
19
+ content: '';
20
+
21
+ position: absolute;
22
+ top: 0;
23
+ left: 50%;
24
+ transform: translateX(-50%);
25
+
26
+ width: 1px;
27
+ height: calc(100% - ($size-tree-item-height / 2));
28
+
29
+ background: $sys-neutral-decor-default;
30
+ }
31
+ }
32
+
33
+ &[data-half-height] {
34
+ &::after {
35
+ height: calc(100% - ($size-tree-item-height / 2));
36
+ }
37
+ }
38
+
39
+ &[data-horizontal] {
40
+ &::after {
41
+ top: 50%;
42
+ left: -50%;
43
+ transform: translateY(-50%);
44
+
45
+ width: calc(150% - $space-tree-item-child-line-padding);
46
+ height: 1px;
47
+ }
48
+
49
+ &[data-half-width] {
50
+ width: 0;
51
+
52
+ &::after {
53
+ left: calc(0px - $size-tree-item-line-width / 2);
54
+ width: calc($size-tree-item-line-width / 2);
55
+ }
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,288 @@
1
+ import cn from 'classnames';
2
+ import { FocusEvent, KeyboardEventHandler, MouseEventHandler, useEffect, useMemo, useRef, useState } from 'react';
3
+
4
+ import { ButtonFunction } from '@snack-uikit/button';
5
+ import { ChevronRightSVG, FileSVG, FolderOpenSVG, FolderSVG } from '@snack-uikit/icons';
6
+ import { Checkbox } from '@snack-uikit/toggles';
7
+ import { TruncateString } from '@snack-uikit/truncate-string';
8
+ import { Typography } from '@snack-uikit/typography';
9
+ import { extractSupportProps } from '@snack-uikit/utils';
10
+
11
+ import { TEST_IDS } from '../../constants';
12
+ import { useTreeContext } from '../../contexts/TreeContext';
13
+ import { checkNestedNodesSelection } from '../../helpers';
14
+ import { ParentNode, TreeNodeProps } from '../../types';
15
+ import { TreeLine } from '../TreeLine';
16
+ import { TreeNodeActions } from './components';
17
+ import styles from './styles.module.scss';
18
+ import { stopPropagationClick } from './utils';
19
+
20
+ export type { TreeNodeProps };
21
+
22
+ type TreeNodeComponentProps = TreeNodeProps & {
23
+ onChevronClick?: MouseEventHandler<HTMLElement>;
24
+ onKeyDown?: KeyboardEventHandler<HTMLElement>;
25
+ isLoading?: boolean;
26
+ parentNode?: ParentNode;
27
+ tabIndexAvailable?: boolean;
28
+ };
29
+
30
+ export function TreeNode({
31
+ id,
32
+ title,
33
+ icon = <FileSVG size={24} />,
34
+ expandedIcon = <FolderOpenSVG size={24} />,
35
+ collapsedIcon = <FolderSVG size={24} />,
36
+ disabled,
37
+ onClick,
38
+ nested,
39
+ className,
40
+ onChevronClick,
41
+ onKeyDown,
42
+ isLoading,
43
+ parentNode,
44
+ tabIndexAvailable,
45
+ ...rest
46
+ }: TreeNodeComponentProps) {
47
+ const {
48
+ isMultiSelect,
49
+ onNodeClick,
50
+ selected,
51
+ expandedNodes,
52
+ onSelect,
53
+ nodeActions,
54
+ parentActions,
55
+ setFocusPosition,
56
+ resetFocusPosition,
57
+ focusedNodeId,
58
+ setFocusIndex,
59
+ focusableNodeIds,
60
+ showLines,
61
+ } = useTreeContext();
62
+
63
+ const [isDroplistOpen, setDroplistOpen] = useState(false);
64
+ const [isDroplistTriggerFocused, setFocusDroplistTrigger] = useState(false);
65
+
66
+ const ref = useRef<HTMLDivElement | null>(null);
67
+
68
+ const isExpandable = Array.isArray(nested);
69
+ const isExpanded = isExpandable ? expandedNodes?.includes(id) : undefined;
70
+
71
+ const nestedNodesSelection = useMemo(() => {
72
+ if (!nested || !Array.isArray(selected) || !selected?.length) return undefined;
73
+
74
+ return checkNestedNodesSelection(nested, selected);
75
+ }, [nested, selected]);
76
+
77
+ const isSelected =
78
+ (Array.isArray(selected) ? selected.includes(id) || nestedNodesSelection?.allSelected : selected === id) || false;
79
+
80
+ const isFocused = focusedNodeId === id;
81
+
82
+ useEffect(() => {
83
+ if (ref.current && isFocused) {
84
+ ref.current.focus();
85
+ }
86
+ }, [isFocused]);
87
+
88
+ const treeNodeIcon = useMemo(() => {
89
+ if (isExpandable) {
90
+ return isExpanded ? expandedIcon : collapsedIcon;
91
+ }
92
+
93
+ return icon;
94
+ }, [isExpandable, icon, isExpanded, expandedIcon, collapsedIcon]);
95
+
96
+ const handleClick: TreeNodeProps['onClick'] = e => {
97
+ onNodeClick(
98
+ {
99
+ id,
100
+ title,
101
+ disabled,
102
+ nested,
103
+ onClick,
104
+ },
105
+ e,
106
+ );
107
+ };
108
+
109
+ const handleSelect = () => {
110
+ onSelect(
111
+ {
112
+ id,
113
+ disabled,
114
+ nested,
115
+ },
116
+ parentNode,
117
+ );
118
+ };
119
+
120
+ const handleFocus = (e?: FocusEvent) => {
121
+ setFocusPosition(id);
122
+
123
+ if (!e) {
124
+ setFocusDroplistTrigger(false);
125
+ ref.current?.focus();
126
+ }
127
+ };
128
+
129
+ const handleBlurActions = () => {
130
+ if (isDroplistTriggerFocused && !isDroplistOpen) {
131
+ setFocusDroplistTrigger(false);
132
+
133
+ ref.current?.blur();
134
+ }
135
+ };
136
+
137
+ const handleKeyDown: KeyboardEventHandler<HTMLElement> = e => {
138
+ onKeyDown?.(e);
139
+
140
+ const focusableCount = focusableNodeIds.length;
141
+
142
+ switch (e.key) {
143
+ case 'ArrowDown': {
144
+ e.preventDefault();
145
+
146
+ setFocusIndex((prev = 0) => {
147
+ if (prev >= focusableCount - 1) {
148
+ return focusableCount - 1;
149
+ }
150
+ return prev + 1;
151
+ });
152
+
153
+ return;
154
+ }
155
+ case 'ArrowUp': {
156
+ e.preventDefault();
157
+
158
+ setFocusIndex((prev = 0) => {
159
+ if (!prev) {
160
+ return 0;
161
+ }
162
+ return prev - 1;
163
+ });
164
+
165
+ return;
166
+ }
167
+ case 'ArrowRight': {
168
+ e.preventDefault();
169
+
170
+ if (isExpanded || !isExpandable || disabled) {
171
+ setFocusDroplistTrigger(true);
172
+ }
173
+
174
+ return;
175
+ }
176
+ case 'ArrowLeft': {
177
+ e.preventDefault();
178
+
179
+ if (isDroplistTriggerFocused) {
180
+ setFocusDroplistTrigger(false);
181
+ ref.current?.focus();
182
+ }
183
+
184
+ return;
185
+ }
186
+ case 'Escape': {
187
+ ref.current?.blur();
188
+ return;
189
+ }
190
+ case ' ':
191
+ case 'Enter': {
192
+ e.preventDefault();
193
+ handleSelect();
194
+ return;
195
+ }
196
+ default:
197
+ return;
198
+ }
199
+ };
200
+
201
+ const getNodeActions = Boolean(nested) ? parentActions : nodeActions;
202
+
203
+ return (
204
+ <div
205
+ role='presentation'
206
+ className={cn(styles.treeNode, className)}
207
+ {...extractSupportProps(rest)}
208
+ data-node-id={id}
209
+ >
210
+ {parentNode && (
211
+ <TreeLine halfWidth={Boolean(nested)} horizontal visible={showLines} data-test-id={TEST_IDS.line} />
212
+ )}
213
+
214
+ {!parentNode && !Boolean(nested) && <TreeLine visible={false} />}
215
+
216
+ {isExpandable && (
217
+ <ButtonFunction
218
+ size={ButtonFunction.sizes.Xs}
219
+ icon={<ChevronRightSVG />}
220
+ disabled={disabled}
221
+ loading={isLoading}
222
+ onClick={onChevronClick}
223
+ data-expanded={isExpanded || undefined}
224
+ className={styles.treeNodeExpandButton}
225
+ tabIndex={-1}
226
+ data-test-id={TEST_IDS.chevron}
227
+ />
228
+ )}
229
+
230
+ <div
231
+ role='treeitem'
232
+ aria-expanded={isExpanded}
233
+ aria-selected={isSelected}
234
+ aria-disabled={disabled}
235
+ data-multiselect={isMultiSelect || undefined}
236
+ data-droplist-active={isDroplistOpen || isDroplistTriggerFocused || undefined}
237
+ onClick={handleClick}
238
+ onKeyDown={handleKeyDown}
239
+ onFocus={handleFocus}
240
+ onBlur={resetFocusPosition}
241
+ tabIndex={tabIndexAvailable ? 0 : -1}
242
+ className={styles.treeNodeContent}
243
+ data-test-id={TEST_IDS.item}
244
+ ref={ref}
245
+ >
246
+ {isMultiSelect && (
247
+ <div className={styles.treeNodeCheckboxWrap}>
248
+ <Checkbox
249
+ size={Checkbox.sizes.S}
250
+ disabled={disabled}
251
+ checked={!disabled && isSelected}
252
+ indeterminate={!disabled && !isSelected && nestedNodesSelection?.someSelected}
253
+ onChange={handleSelect}
254
+ onClick={stopPropagationClick}
255
+ data-test-id={TEST_IDS.checkbox}
256
+ tabIndex={-1}
257
+ />
258
+ </div>
259
+ )}
260
+
261
+ <div className={styles.treeNodeIcon} data-test-id={TEST_IDS.icon}>
262
+ {treeNodeIcon}
263
+ </div>
264
+
265
+ <Typography.SansBodyM tag={Typography.tags.div} className={styles.treeNodeTitle}>
266
+ <TruncateString text={title} data-test-id={TEST_IDS.title} />
267
+ </Typography.SansBodyM>
268
+
269
+ {getNodeActions && (
270
+ <TreeNodeActions
271
+ getNodeActions={getNodeActions}
272
+ node={{
273
+ id,
274
+ title,
275
+ disabled,
276
+ nested,
277
+ }}
278
+ focusNode={handleFocus}
279
+ onBlurActions={handleBlurActions}
280
+ isDroplistTriggerFocused={isDroplistTriggerFocused}
281
+ isDroplistOpen={isDroplistOpen}
282
+ setDroplistOpen={setDroplistOpen}
283
+ />
284
+ )}
285
+ </div>
286
+ </div>
287
+ );
288
+ }
@@ -0,0 +1,109 @@
1
+ import { Dispatch, KeyboardEventHandler, SetStateAction, useEffect } from 'react';
2
+
3
+ import { ButtonFunction } from '@snack-uikit/button';
4
+ import { Droplist, ItemSingleProps } from '@snack-uikit/droplist';
5
+ import { KebabSVG } from '@snack-uikit/icons';
6
+
7
+ import { TEST_IDS } from '../../../constants';
8
+ import { TreeNodeProps } from '../../../types';
9
+ import styles from '../styles.module.scss';
10
+ import { stopPropagationClick } from '../utils';
11
+
12
+ type TreeNodeActionsProps = {
13
+ isDroplistOpen: boolean;
14
+ setDroplistOpen: Dispatch<SetStateAction<boolean>>;
15
+ getNodeActions(node: TreeNodeProps): ItemSingleProps[];
16
+ node: TreeNodeProps;
17
+ isDroplistTriggerFocused: boolean;
18
+ focusNode(): void;
19
+ onBlurActions(): void;
20
+ };
21
+
22
+ export function TreeNodeActions({
23
+ getNodeActions,
24
+ isDroplistTriggerFocused,
25
+ focusNode,
26
+ isDroplistOpen,
27
+ setDroplistOpen,
28
+ onBlurActions,
29
+ node,
30
+ }: TreeNodeActionsProps) {
31
+ const droplistActions = getNodeActions(node);
32
+
33
+ const {
34
+ triggerElementRef,
35
+ handleDroplistFocusLeave,
36
+ handleDroplistItemKeyDown,
37
+ handleTriggerKeyDown,
38
+ handleDroplistItemClick,
39
+ firstElementRefCallback,
40
+ } = Droplist.useKeyboardNavigation<HTMLButtonElement>({ setDroplistOpen });
41
+
42
+ useEffect(() => {
43
+ if (triggerElementRef.current && isDroplistTriggerFocused) {
44
+ triggerElementRef.current.focus();
45
+ }
46
+ }, [isDroplistTriggerFocused, triggerElementRef]);
47
+
48
+ if (!droplistActions.length) {
49
+ return null;
50
+ }
51
+
52
+ const handleKeyDown: KeyboardEventHandler<HTMLButtonElement> = e => {
53
+ handleTriggerKeyDown(e);
54
+
55
+ switch (e.key) {
56
+ case 'Tab': {
57
+ e.preventDefault();
58
+ return;
59
+ }
60
+ case 'ArrowLeft': {
61
+ if (isDroplistTriggerFocused) {
62
+ focusNode();
63
+ setDroplistOpen(false);
64
+ }
65
+ return;
66
+ }
67
+ default:
68
+ return;
69
+ }
70
+ };
71
+
72
+ return (
73
+ <div
74
+ role='presentation'
75
+ className={styles.treeNodeActions}
76
+ data-focused={isDroplistTriggerFocused || undefined}
77
+ onClick={stopPropagationClick}
78
+ >
79
+ <Droplist
80
+ open={isDroplistOpen}
81
+ onOpenChange={setDroplistOpen}
82
+ onFocusLeave={handleDroplistFocusLeave}
83
+ firstElementRefCallback={firstElementRefCallback}
84
+ triggerRef={triggerElementRef}
85
+ placement={Droplist.placements.BottomEnd}
86
+ triggerElement={
87
+ <ButtonFunction
88
+ size={ButtonFunction.sizes.Xs}
89
+ icon={<KebabSVG size={24} />}
90
+ onKeyDown={handleKeyDown}
91
+ onBlur={onBlurActions}
92
+ tabIndex={-1}
93
+ data-test-id={TEST_IDS.droplistTrigger}
94
+ />
95
+ }
96
+ >
97
+ {droplistActions.map(action => (
98
+ <Droplist.ItemSingle
99
+ key={action.option}
100
+ {...action}
101
+ onKeyDown={handleDroplistItemKeyDown}
102
+ onClick={e => handleDroplistItemClick(e, action.onClick)}
103
+ data-test-id={TEST_IDS.droplistAction}
104
+ />
105
+ ))}
106
+ </Droplist>
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1 @@
1
+ export * from './TreeNodeActions';
@@ -0,0 +1 @@
1
+ export * from './TreeNode';
@@ -0,0 +1,144 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-tree';
2
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
+
4
+ .treeNode {
5
+ display: flex;
6
+ align-items: center;
7
+ box-sizing: border-box;
8
+ width: 100%;
9
+ }
10
+
11
+ .treeNodeIcon {
12
+ display: flex;
13
+ flex-shrink: 0;
14
+ align-items: center;
15
+ justify-content: center;
16
+
17
+ box-sizing: border-box;
18
+
19
+ color: $sys-neutral-text-support;
20
+
21
+ svg {
22
+ width: $icon-s !important; /* stylelint-disable-line declaration-no-important */
23
+ height: $icon-s !important; /* stylelint-disable-line declaration-no-important */
24
+ }
25
+ }
26
+
27
+ .treeNodeTitle {
28
+ flex-grow: 1;
29
+ box-sizing: border-box;
30
+ min-width: $size-tree-item-checkbox-wrap;
31
+ color: $sys-neutral-text-main;
32
+ }
33
+
34
+ .treeNodeActions {
35
+ display: flex;
36
+ visibility: hidden;
37
+
38
+ &:focus-visible, &[data-focused] {
39
+ visibility: visible;
40
+ }
41
+ }
42
+
43
+ .treeNodeContent {
44
+ @include composite-var($tree-item-content-layout);
45
+
46
+ cursor: pointer;
47
+
48
+ position: relative;
49
+
50
+ display: flex;
51
+ align-items: center;
52
+
53
+ box-sizing: border-box;
54
+ width: 100%;
55
+
56
+ border-radius: $radius-tree-item-content-layout;
57
+
58
+ &::before {
59
+ pointer-events: none;
60
+ content: '';
61
+
62
+ position: absolute;
63
+ top: 0;
64
+ left: 0;
65
+
66
+ box-sizing: border-box;
67
+ width: 100%;
68
+ height: 100%;
69
+
70
+ opacity: 0;
71
+ background-color: $sys-neutral-accent-default;
72
+ border-radius: inherit;
73
+ }
74
+
75
+ &:hover, &:focus-visible, &[data-droplist-active] {
76
+ &::before {
77
+ opacity: $opacity-a008;
78
+ }
79
+
80
+ .treeNodeActions {
81
+ visibility: visible;
82
+ }
83
+ }
84
+
85
+ &:focus-visible {
86
+ @include outline-inside-var($container-focused-s);
87
+
88
+ outline-color: $sys-available-complementary;
89
+ }
90
+
91
+ &[data-multiselect] {
92
+ cursor: default;
93
+ }
94
+
95
+ &[aria-disabled='true'] {
96
+ cursor: not-allowed;
97
+
98
+ &::before {
99
+ display: none;
100
+ }
101
+
102
+ .treeNodeTitle, .treeNodeIcon {
103
+ color: $sys-neutral-text-disabled;
104
+ }
105
+ }
106
+
107
+ &[aria-selected='true']:not([data-multiselect]) {
108
+ &::before {
109
+ opacity: $opacity-a008;
110
+ background-color: $sys-primary-accent-default;
111
+ }
112
+
113
+ &:hover {
114
+ &::before {
115
+ opacity: $opacity-a016;
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ .treeNodeCheckboxWrap {
122
+ @include composite-var($tree-item-checkbox-wrap);
123
+
124
+ display: flex;
125
+ flex-shrink: 0;
126
+ align-items: center;
127
+ justify-content: center;
128
+
129
+ box-sizing: border-box;
130
+ }
131
+
132
+ .treeNodeExpandButton {
133
+ box-sizing: border-box;
134
+
135
+ svg {
136
+ transition: transform 0.2s ease;
137
+ }
138
+
139
+ &[data-expanded] {
140
+ svg {
141
+ transform: rotate(90deg);
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,5 @@
1
+ import { MouseEvent } from 'react';
2
+
3
+ export function stopPropagationClick(e: MouseEvent<HTMLInputElement>) {
4
+ e.stopPropagation();
5
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ExpandableTreeNode';
2
+ export * from './TreeNode';
3
+ export * from './TreeLine';
4
+ export * from './TreeItem';