@splunk/react-ui 5.7.1 → 5.9.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/Accordion.js +6 -6
- package/Anchor.js +2 -1
- package/Box.js +83 -34
- package/CHANGELOG.md +51 -0
- package/Calendar.js +134 -134
- package/Clickable.js +131 -94
- package/CollapsiblePanel.js +175 -137
- package/ComboBox.js +32 -27
- package/ControlGroup.js +92 -91
- package/DefinitionList.js +9 -9
- package/Drawer.d.ts +2 -0
- package/Drawer.js +679 -0
- package/Dropdown.js +27 -18
- package/DualListbox.js +1 -1
- package/File.js +35 -35
- package/JSONTree.js +73 -72
- package/Link.js +2 -2
- package/MIGRATION.md +10 -0
- package/Menu.js +403 -261
- package/Modal.js +263 -252
- package/Monogram.js +2 -2
- package/Multiselect.js +551 -385
- package/Number.js +2 -1
- package/Paginator.js +14 -12
- package/Popover.js +4 -1
- package/README.md +11 -0
- package/RadioBar.js +1 -1
- package/Search.js +111 -95
- package/Select.js +42 -40
- package/SelectBase.js +819 -715
- package/SidePanel.js +346 -167
- package/SlidingPanels.js +11 -11
- package/StepBar.js +7 -7
- package/Switch.js +5 -5
- package/Table.js +116 -119
- package/Text.js +48 -48
- package/TextArea.js +7 -7
- package/TransitionOpen.js +188 -169
- package/docs-llm/Accordion.md +267 -0
- package/docs-llm/Anchor Menu.md +115 -0
- package/docs-llm/Anchor.md +54 -0
- package/docs-llm/AnimationToggle.md +254 -0
- package/docs-llm/Avatar.md +292 -0
- package/docs-llm/Badge.md +212 -0
- package/docs-llm/Breadcrumbs.md +306 -0
- package/docs-llm/Button Group.md +53 -0
- package/docs-llm/Button.md +361 -0
- package/docs-llm/Card Layout.md +286 -0
- package/docs-llm/Card.md +619 -0
- package/docs-llm/Checkbox.md +218 -0
- package/docs-llm/Chip.md +291 -0
- package/docs-llm/Clickable.md +160 -0
- package/docs-llm/Code.md +292 -0
- package/docs-llm/Collapsible Panel.md +744 -0
- package/docs-llm/Color.md +253 -0
- package/docs-llm/Column Layout.md +391 -0
- package/docs-llm/Combo Box.md +540 -0
- package/docs-llm/Control Group.md +594 -0
- package/docs-llm/Date.md +270 -0
- package/docs-llm/Definition List.md +278 -0
- package/docs-llm/Divider.md +216 -0
- package/docs-llm/Drawer.md +414 -0
- package/docs-llm/Dropdown.md +472 -0
- package/docs-llm/Dual Listbox.md +325 -0
- package/docs-llm/File.md +653 -0
- package/docs-llm/Form Rows.md +374 -0
- package/docs-llm/Heading.md +179 -0
- package/docs-llm/Image.md +109 -0
- package/docs-llm/JSON Tree.md +260 -0
- package/docs-llm/Layer.md +74 -0
- package/docs-llm/Layout.md +50 -0
- package/docs-llm/Link.md +318 -0
- package/docs-llm/List.md +189 -0
- package/docs-llm/Markdown.md +179 -0
- package/docs-llm/Menu.md +735 -0
- package/docs-llm/Message Bar.md +236 -0
- package/docs-llm/Message.md +248 -0
- package/docs-llm/Modal.md +443 -0
- package/docs-llm/Monogram.md +159 -0
- package/docs-llm/Multiselect.md +939 -0
- package/docs-llm/Notifications.md +46 -0
- package/docs-llm/Number.md +298 -0
- package/docs-llm/Paginator.md +395 -0
- package/docs-llm/Paragraph.md +148 -0
- package/docs-llm/Phone Number.md +254 -0
- package/docs-llm/Popover.md +166 -0
- package/docs-llm/Progress.md +141 -0
- package/docs-llm/Radio Bar.md +303 -0
- package/docs-llm/Radio List.md +350 -0
- package/docs-llm/Resize.md +362 -0
- package/docs-llm/Screen Reader Content.md +73 -0
- package/docs-llm/Scroll Container Context.md +155 -0
- package/docs-llm/Scroll.md +152 -0
- package/docs-llm/Search.md +381 -0
- package/docs-llm/Select.md +985 -0
- package/docs-llm/Side Panel.md +777 -0
- package/docs-llm/Slider.md +339 -0
- package/docs-llm/Sliding Panels.md +340 -0
- package/docs-llm/Split Button.md +295 -0
- package/docs-llm/Static Content.md +90 -0
- package/docs-llm/Step Bar.md +292 -0
- package/docs-llm/Switch.md +268 -0
- package/docs-llm/Tab Bar.md +439 -0
- package/docs-llm/Tab Layout.md +398 -0
- package/docs-llm/Table.md +2642 -0
- package/docs-llm/Text Area.md +253 -0
- package/docs-llm/Text.md +339 -0
- package/docs-llm/Tooltip.md +325 -0
- package/docs-llm/Transition Open.md +406 -0
- package/docs-llm/Tree.md +591 -0
- package/docs-llm/Typography.md +125 -0
- package/docs-llm/Wait Spinner.md +121 -0
- package/docs-llm/llms.txt +101 -0
- package/package.json +6 -5
- package/types/src/Box/Box.d.ts +2 -10
- package/types/src/Drawer/Body.d.ts +17 -0
- package/types/src/Drawer/Drawer.d.ts +114 -0
- package/types/src/Drawer/DrawerContext.d.ts +11 -0
- package/types/src/Drawer/Footer.d.ts +25 -0
- package/types/src/Drawer/Header.d.ts +41 -0
- package/types/src/Drawer/docs/examples/Basic.d.ts +6 -0
- package/types/src/Drawer/docs/examples/ContainerPosition.d.ts +7 -0
- package/types/src/Drawer/docs/examples/InitialFocus.d.ts +9 -0
- package/types/src/Drawer/docs/examples/InlinePosition.d.ts +7 -0
- package/types/src/Drawer/docs/examples/PagePosition.d.ts +7 -0
- package/types/src/Drawer/index.d.ts +2 -0
- package/types/src/JSONTree/JSONTree.d.ts +12 -5
- package/types/src/JSONTree/renderTreeItems.d.ts +2 -1
- package/types/src/Menu/Item.d.ts +2 -1
- package/types/src/Menu/docs/examples/SelectableCheckbox.d.ts +7 -0
- package/types/src/Modal/Modal.d.ts +1 -2
- package/types/src/Multiselect/Compact.d.ts +8 -3
- package/types/src/Multiselect/Multiselect.d.ts +8 -3
- package/types/src/Multiselect/Normal.d.ts +8 -3
- package/types/src/Multiselect/Option.d.ts +6 -3
- package/types/src/Multiselect/docs/examples/Disabled.d.ts +1 -0
- package/types/src/Select/Option.d.ts +6 -3
- package/types/src/Select/Select.d.ts +8 -5
- package/types/src/Select/docs/examples/Dimmed.d.ts +7 -0
- package/types/src/SelectBase/OptionBase.d.ts +6 -3
- package/types/src/SelectBase/SelectBase.d.ts +8 -3
- package/types/src/SidePanel/SidePanel.d.ts +43 -2
- package/types/src/SidePanel/docs/examples/DockLayout.d.ts +17 -0
- package/types/src/SidePanel/docs/examples/InitialFocus.d.ts +9 -0
- package/types/src/TransitionOpen/TransitionOpen.d.ts +29 -4
- package/types/src/useKeyPress/index.d.ts +9 -2
- package/types/src/useOnClickOutside/index.d.ts +2 -0
- package/types/src/useOnClickOutside/useOnClickOutside.d.ts +4 -0
- package/useKeyPress.js +23 -18
- package/useOnClickOutside.d.ts +2 -0
- package/useOnClickOutside.js +79 -0
- package/types/src/RadioList/docs/examples/Row.d.ts +0 -6
package/docs-llm/Tree.md
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
# Tree
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
> Image: Illustration of Tree component
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## When to use this component
|
|
10
|
+
|
|
11
|
+
The `Tree` component displays hierarchical data, allowing users to expand, collapse, and select nested items efficiently.
|
|
12
|
+
|
|
13
|
+
- When you need to show nested or grouped data in a clear, expandable structure.
|
|
14
|
+
- When users must select or interact with items at multiple levels of a hierarchy.
|
|
15
|
+
- When space is limited but hierarchical context is important.
|
|
16
|
+
|
|
17
|
+
### Additional considerations
|
|
18
|
+
- For large datasets, consider lazy loading or virtualization to maintain performance.
|
|
19
|
+
- Ensure keyboard navigation and screen reader support for accessibility.
|
|
20
|
+
|
|
21
|
+
## When to use another component
|
|
22
|
+
- When you only need to show a flat list, use a `List`.
|
|
23
|
+
- For simple selection from a set of options, use a `ComboBox` or `Select`.
|
|
24
|
+
- For non-hierarchical multiple selections, use a `Multiselect`.
|
|
25
|
+
- If you need to display tabular data, use a `Table`.
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
graph TD
|
|
29
|
+
accDescr: Decision tree that guides on when to use Tree or something else
|
|
30
|
+
A(Is the data hierarchical or nested?) -- Yes --- B(Do you need to expand/collapse or select items at multiple levels?)
|
|
31
|
+
A -- No --- C(ComboBox, Multiselect, Select, or Table)
|
|
32
|
+
B -- Yes --- D(Tree)
|
|
33
|
+
B -- No --- E(List)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Check out
|
|
37
|
+
- [List][1]
|
|
38
|
+
- [ComboBox][2]
|
|
39
|
+
- [Select][3]
|
|
40
|
+
- [Multiselect][4]
|
|
41
|
+
- [Table][5]
|
|
42
|
+
|
|
43
|
+
## Behaviors
|
|
44
|
+
|
|
45
|
+
### Expand/Collapse
|
|
46
|
+
|
|
47
|
+
Allows users to expand or collapse branches to view nested items. Users can navigate and interact with the tree using keyboard controls.
|
|
48
|
+
|
|
49
|
+
> Image: Tree expand/collapse behavior demonstration
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Selection
|
|
53
|
+
|
|
54
|
+
Nodes within the Tree can be selectable, enabling users to highlight or activate specific items. Keyboard focus management is integral to ensure accessibility and smooth interaction.
|
|
55
|
+
|
|
56
|
+
> Image: Tree selection behavior demonstration
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### Grouping and organization
|
|
62
|
+
|
|
63
|
+
Use the `Tree` component to organize items with clear parent-child relationships, such as file directories, organizational charts, or nested categories. Group related items together to improve clarity and help users understand the hierarchy.
|
|
64
|
+
|
|
65
|
+
> Image: Grouping items in Tree. The first example with heart eyes emoji shows clear organization; the second example with grimacing emoji shows cluttered grouping.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Content
|
|
69
|
+
|
|
70
|
+
### Concise labels
|
|
71
|
+
|
|
72
|
+
Use concise, descriptive labels for each node, ideally 1-3 words, employing sentence-style capitalization. Labels should clearly convey the content or category represented by the node.
|
|
73
|
+
|
|
74
|
+
> Image: Tree node label examples. The first example with heart eyes emoji shows clear, brief labels; the second example with grimacing emoji shows overly long or vague labels.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
[1]: ./List
|
|
79
|
+
[2]: ./ComboBox
|
|
80
|
+
[3]: ./Select
|
|
81
|
+
[4]: ./Multiselect
|
|
82
|
+
[5]: ./Table
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## Examples
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### Basic
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import React, { useCallback, useState } from 'react';
|
|
92
|
+
|
|
93
|
+
import Tree, { TreeItemToggleExpansionHandler } from '@splunk/react-ui/Tree';
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export default function Basic() {
|
|
97
|
+
const [expandedIdsMap, setExpandedIdsMap] = useState(
|
|
98
|
+
new Map([
|
|
99
|
+
['two', true],
|
|
100
|
+
['three', true],
|
|
101
|
+
])
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const handleToggleExpansion: TreeItemToggleExpansionHandler = useCallback(
|
|
105
|
+
(event, { treeItemId } = {}) => {
|
|
106
|
+
if (!treeItemId) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
setExpandedIdsMap((prevMap) => {
|
|
111
|
+
const newMap = new Map(prevMap);
|
|
112
|
+
if (newMap.has(treeItemId)) {
|
|
113
|
+
newMap.delete(treeItemId);
|
|
114
|
+
} else {
|
|
115
|
+
newMap.set(treeItemId, true);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return newMap;
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
[]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Tree>
|
|
126
|
+
<Tree.Item content="node-0" id="one" />
|
|
127
|
+
<Tree.Item
|
|
128
|
+
content="node-1"
|
|
129
|
+
id="two"
|
|
130
|
+
expanded={expandedIdsMap.has('two')}
|
|
131
|
+
onToggleExpansion={handleToggleExpansion}
|
|
132
|
+
>
|
|
133
|
+
<Tree.Item
|
|
134
|
+
content="node-1-0"
|
|
135
|
+
id="three"
|
|
136
|
+
expanded={expandedIdsMap.has('three')}
|
|
137
|
+
onToggleExpansion={handleToggleExpansion}
|
|
138
|
+
>
|
|
139
|
+
<Tree.Item content="node-1-0-0" id="four" />
|
|
140
|
+
<Tree.Item content="node-1-0-1" id="five" />
|
|
141
|
+
</Tree.Item>
|
|
142
|
+
<Tree.Item content="node-1-1" id="six" />
|
|
143
|
+
</Tree.Item>
|
|
144
|
+
<Tree.Item content="node-2" id="seven" />
|
|
145
|
+
</Tree>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
### StyledExpansionToggleWrapper
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
156
|
+
|
|
157
|
+
import styled from 'styled-components';
|
|
158
|
+
|
|
159
|
+
import ChevronDown from '@splunk/react-icons/ChevronDown';
|
|
160
|
+
import ChevronRight from '@splunk/react-icons/ChevronRight';
|
|
161
|
+
import Tree, { TreeItemPropsBase, TreeItemToggleExpansionHandler } from '@splunk/react-ui/Tree';
|
|
162
|
+
import { variables } from '@splunk/themes';
|
|
163
|
+
|
|
164
|
+
const StyledExpansionToggleWrapper = styled.span`
|
|
165
|
+
display: inline-flex;
|
|
166
|
+
width: 16px;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
align-items: center;
|
|
169
|
+
padding-inline: ${variables.spacingMedium};
|
|
170
|
+
`;
|
|
171
|
+
const StyledSpan = styled.span`
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
padding: ${variables.spacingXSmall} 0;
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
const ExpansionToggle = ({
|
|
178
|
+
expanded,
|
|
179
|
+
treeItemId,
|
|
180
|
+
onToggleExpansion,
|
|
181
|
+
}: {
|
|
182
|
+
expanded: boolean;
|
|
183
|
+
treeItemId: string;
|
|
184
|
+
onToggleExpansion: TreeItemPropsBase['onToggleExpansion'];
|
|
185
|
+
}) => (
|
|
186
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
|
187
|
+
<span
|
|
188
|
+
onClick={(e) => {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
onToggleExpansion?.(e, { treeItemId });
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{expanded ? <ChevronDown /> : <ChevronRight />}
|
|
194
|
+
</span>
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const TreeItemWithExpansion = ({
|
|
198
|
+
children,
|
|
199
|
+
content,
|
|
200
|
+
expanded,
|
|
201
|
+
id,
|
|
202
|
+
onToggleExpansion,
|
|
203
|
+
...otherTreeItemProps
|
|
204
|
+
}: TreeItemPropsBase) => {
|
|
205
|
+
const contentWithExpansion = useMemo(() => {
|
|
206
|
+
const renderExpansionToggle = () => {
|
|
207
|
+
if (!children) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<ExpansionToggle
|
|
213
|
+
expanded={expanded || false}
|
|
214
|
+
onToggleExpansion={onToggleExpansion}
|
|
215
|
+
treeItemId={id}
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<StyledSpan>
|
|
222
|
+
<StyledExpansionToggleWrapper>
|
|
223
|
+
{renderExpansionToggle()}
|
|
224
|
+
</StyledExpansionToggleWrapper>
|
|
225
|
+
{content}
|
|
226
|
+
</StyledSpan>
|
|
227
|
+
);
|
|
228
|
+
}, [children, content, expanded, id, onToggleExpansion]);
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<Tree.Item
|
|
232
|
+
content={contentWithExpansion}
|
|
233
|
+
expanded={expanded}
|
|
234
|
+
id={id}
|
|
235
|
+
onToggleExpansion={onToggleExpansion}
|
|
236
|
+
{...otherTreeItemProps}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
</Tree.Item>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export default function ClickableExpansion() {
|
|
244
|
+
const [expandedIdsMap, setExpandedIdsMap] = useState(
|
|
245
|
+
new Map([
|
|
246
|
+
['two', true],
|
|
247
|
+
['three', true],
|
|
248
|
+
])
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const handleToggleExpansion: TreeItemToggleExpansionHandler = useCallback(
|
|
252
|
+
(event, { treeItemId } = {}) => {
|
|
253
|
+
if (!treeItemId) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setExpandedIdsMap((prevMap) => {
|
|
258
|
+
const newMap = new Map(prevMap);
|
|
259
|
+
if (newMap.has(treeItemId)) {
|
|
260
|
+
newMap.delete(treeItemId);
|
|
261
|
+
} else {
|
|
262
|
+
newMap.set(treeItemId, true);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return newMap;
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
[]
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<Tree data-test="tree-fixture">
|
|
273
|
+
<TreeItemWithExpansion content="node-0" id="one" />
|
|
274
|
+
<TreeItemWithExpansion
|
|
275
|
+
content="node-1"
|
|
276
|
+
id="two"
|
|
277
|
+
expanded={expandedIdsMap.has('two')}
|
|
278
|
+
onToggleExpansion={handleToggleExpansion}
|
|
279
|
+
>
|
|
280
|
+
<TreeItemWithExpansion
|
|
281
|
+
content="node-1-0"
|
|
282
|
+
id="three"
|
|
283
|
+
expanded={expandedIdsMap.has('three')}
|
|
284
|
+
onToggleExpansion={handleToggleExpansion}
|
|
285
|
+
>
|
|
286
|
+
<TreeItemWithExpansion content="node-1-0-0" id="four" />
|
|
287
|
+
<TreeItemWithExpansion content="node-1-0-1" id="five" />
|
|
288
|
+
</TreeItemWithExpansion>
|
|
289
|
+
<TreeItemWithExpansion content="node-1-1" id="six" />
|
|
290
|
+
</TreeItemWithExpansion>
|
|
291
|
+
<TreeItemWithExpansion content="node-2" id="seven" />
|
|
292
|
+
</Tree>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
### ClickableExpansioWithSelection
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
303
|
+
|
|
304
|
+
import styled from 'styled-components';
|
|
305
|
+
|
|
306
|
+
import ChevronDown from '@splunk/react-icons/ChevronDown';
|
|
307
|
+
import ChevronRight from '@splunk/react-icons/ChevronRight';
|
|
308
|
+
import Checkbox from '@splunk/react-ui/Checkbox';
|
|
309
|
+
import Tree, {
|
|
310
|
+
TreeItemPropsBase,
|
|
311
|
+
TreeItemToggleExpansionHandler,
|
|
312
|
+
TreeItemToggleSelectionHandler,
|
|
313
|
+
} from '@splunk/react-ui/Tree';
|
|
314
|
+
import { variables } from '@splunk/themes';
|
|
315
|
+
|
|
316
|
+
const StyledExpansionToggleWrapper = styled.span`
|
|
317
|
+
display: inline-flex;
|
|
318
|
+
width: 16px;
|
|
319
|
+
justify-content: center;
|
|
320
|
+
align-items: center;
|
|
321
|
+
padding-inline: ${variables.spacingMedium};
|
|
322
|
+
`;
|
|
323
|
+
const StyledCheckbox = styled(Checkbox)`
|
|
324
|
+
padding-inline-end: ${variables.spacingSmall};
|
|
325
|
+
`;
|
|
326
|
+
const StyledSpan = styled.span`
|
|
327
|
+
align-items: center;
|
|
328
|
+
display: inline-flex;
|
|
329
|
+
min-height: 100%;
|
|
330
|
+
padding: ${variables.spacingXSmall} 0;
|
|
331
|
+
`;
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
const ExpansionToggle = ({
|
|
335
|
+
expanded,
|
|
336
|
+
onToggleExpansion,
|
|
337
|
+
treeItemId,
|
|
338
|
+
}: {
|
|
339
|
+
expanded: boolean;
|
|
340
|
+
onToggleExpansion: TreeItemPropsBase['onToggleExpansion'];
|
|
341
|
+
treeItemId: string;
|
|
342
|
+
}) => (
|
|
343
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
|
344
|
+
<span
|
|
345
|
+
onClick={(e) => {
|
|
346
|
+
e.preventDefault();
|
|
347
|
+
onToggleExpansion?.(e, { treeItemId });
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
{expanded ? <ChevronDown /> : <ChevronRight />}
|
|
351
|
+
</span>
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const ItemSelectionCheckbox = ({
|
|
355
|
+
selected,
|
|
356
|
+
onToggleSelection,
|
|
357
|
+
treeItemId,
|
|
358
|
+
}: {
|
|
359
|
+
selected?: boolean;
|
|
360
|
+
onToggleSelection: TreeItemPropsBase['onToggleSelection'];
|
|
361
|
+
treeItemId: string;
|
|
362
|
+
}) => (
|
|
363
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
|
364
|
+
<span
|
|
365
|
+
onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
366
|
+
e.preventDefault();
|
|
367
|
+
onToggleSelection?.(e, { treeItemId });
|
|
368
|
+
}}
|
|
369
|
+
style={{ display: 'inline-flex', userSelect: 'none' }}
|
|
370
|
+
>
|
|
371
|
+
<StyledCheckbox checked={selected} inert />
|
|
372
|
+
</span>
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const TreeItemWithExpansionAndSelection = ({
|
|
376
|
+
children,
|
|
377
|
+
expanded,
|
|
378
|
+
id,
|
|
379
|
+
label,
|
|
380
|
+
onToggleSelection,
|
|
381
|
+
onToggleExpansion,
|
|
382
|
+
selected,
|
|
383
|
+
...otherTreeItemProps
|
|
384
|
+
}: Omit<TreeItemPropsBase, 'content'> & {
|
|
385
|
+
label: string;
|
|
386
|
+
selected?: boolean;
|
|
387
|
+
}) => {
|
|
388
|
+
const treeItemRef = useRef<HTMLLIElement>(null);
|
|
389
|
+
|
|
390
|
+
const content = useMemo(() => {
|
|
391
|
+
const renderExpansionToggle = () => {
|
|
392
|
+
if (!children) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<ExpansionToggle
|
|
398
|
+
expanded={expanded || false}
|
|
399
|
+
onToggleExpansion={onToggleExpansion}
|
|
400
|
+
treeItemId={id}
|
|
401
|
+
/>
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
return (
|
|
406
|
+
<StyledSpan>
|
|
407
|
+
<StyledExpansionToggleWrapper>
|
|
408
|
+
{renderExpansionToggle()}
|
|
409
|
+
</StyledExpansionToggleWrapper>
|
|
410
|
+
<ItemSelectionCheckbox
|
|
411
|
+
selected={selected}
|
|
412
|
+
onToggleSelection={onToggleSelection}
|
|
413
|
+
treeItemId={id}
|
|
414
|
+
/>
|
|
415
|
+
{label}
|
|
416
|
+
</StyledSpan>
|
|
417
|
+
);
|
|
418
|
+
}, [children, expanded, id, label, onToggleExpansion, onToggleSelection, selected]);
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<Tree.Item
|
|
422
|
+
aria-selected={selected ? 'true' : 'false'}
|
|
423
|
+
content={content}
|
|
424
|
+
elementRef={treeItemRef}
|
|
425
|
+
expanded={expanded}
|
|
426
|
+
id={id}
|
|
427
|
+
onToggleExpansion={onToggleExpansion}
|
|
428
|
+
onToggleSelection={onToggleSelection}
|
|
429
|
+
{...otherTreeItemProps}
|
|
430
|
+
>
|
|
431
|
+
{children}
|
|
432
|
+
</Tree.Item>
|
|
433
|
+
);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
export default function ClickableExpansionWithSelection() {
|
|
437
|
+
const [expandedIdsMap, setExpandedIdsMap] = useState(
|
|
438
|
+
new Map([
|
|
439
|
+
['two', true],
|
|
440
|
+
['three', true],
|
|
441
|
+
])
|
|
442
|
+
);
|
|
443
|
+
const [selectedIdsMap, setSelectedIdsMap] = useState(
|
|
444
|
+
new Map([
|
|
445
|
+
['two', true],
|
|
446
|
+
['three', true],
|
|
447
|
+
])
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const handleToggleExpansion: TreeItemToggleExpansionHandler = useCallback(
|
|
451
|
+
(event, { treeItemId } = {}) => {
|
|
452
|
+
if (!treeItemId) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
setExpandedIdsMap((prevMap) => {
|
|
457
|
+
const newMap = new Map(prevMap);
|
|
458
|
+
if (newMap.has(treeItemId)) {
|
|
459
|
+
newMap.delete(treeItemId);
|
|
460
|
+
} else {
|
|
461
|
+
newMap.set(treeItemId, true);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return newMap;
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
[]
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const handleToggleSelected: TreeItemToggleSelectionHandler = useCallback(
|
|
471
|
+
(event, { treeItemId } = {}) => {
|
|
472
|
+
if (!treeItemId) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
setSelectedIdsMap((prevMap) => {
|
|
477
|
+
const newMap = new Map(prevMap);
|
|
478
|
+
if (newMap.has(treeItemId)) {
|
|
479
|
+
newMap.delete(treeItemId);
|
|
480
|
+
} else {
|
|
481
|
+
newMap.set(treeItemId, true);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return newMap;
|
|
485
|
+
});
|
|
486
|
+
},
|
|
487
|
+
[]
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<Tree aria-multiselectable="true" data-test="tree-fixture">
|
|
492
|
+
<TreeItemWithExpansionAndSelection
|
|
493
|
+
id="one"
|
|
494
|
+
label="node-0"
|
|
495
|
+
onToggleSelection={handleToggleSelected}
|
|
496
|
+
selected={selectedIdsMap.has('one')}
|
|
497
|
+
/>
|
|
498
|
+
<TreeItemWithExpansionAndSelection
|
|
499
|
+
expanded={expandedIdsMap.has('two')}
|
|
500
|
+
id="two"
|
|
501
|
+
label="node-1"
|
|
502
|
+
onToggleExpansion={handleToggleExpansion}
|
|
503
|
+
onToggleSelection={handleToggleSelected}
|
|
504
|
+
selected={selectedIdsMap.has('two')}
|
|
505
|
+
>
|
|
506
|
+
<TreeItemWithExpansionAndSelection
|
|
507
|
+
id="three"
|
|
508
|
+
expanded={expandedIdsMap.has('three')}
|
|
509
|
+
label="node-1-0"
|
|
510
|
+
onToggleExpansion={handleToggleExpansion}
|
|
511
|
+
onToggleSelection={handleToggleSelected}
|
|
512
|
+
selected={selectedIdsMap.has('three')}
|
|
513
|
+
>
|
|
514
|
+
<TreeItemWithExpansionAndSelection
|
|
515
|
+
id="four"
|
|
516
|
+
label="node-1-0-0"
|
|
517
|
+
onToggleSelection={handleToggleSelected}
|
|
518
|
+
selected={selectedIdsMap.has('four')}
|
|
519
|
+
/>
|
|
520
|
+
<TreeItemWithExpansionAndSelection
|
|
521
|
+
id="five"
|
|
522
|
+
label="node-1-0-1"
|
|
523
|
+
onToggleSelection={handleToggleSelected}
|
|
524
|
+
selected={selectedIdsMap.has('five')}
|
|
525
|
+
/>
|
|
526
|
+
</TreeItemWithExpansionAndSelection>
|
|
527
|
+
<TreeItemWithExpansionAndSelection
|
|
528
|
+
id="six"
|
|
529
|
+
label="node-1-1"
|
|
530
|
+
onToggleSelection={handleToggleSelected}
|
|
531
|
+
selected={selectedIdsMap.has('six')}
|
|
532
|
+
/>
|
|
533
|
+
</TreeItemWithExpansionAndSelection>
|
|
534
|
+
<TreeItemWithExpansionAndSelection
|
|
535
|
+
id="seven"
|
|
536
|
+
label="node-2"
|
|
537
|
+
onToggleSelection={handleToggleSelected}
|
|
538
|
+
selected={selectedIdsMap.has('seven')}
|
|
539
|
+
/>
|
|
540
|
+
</Tree>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
## API
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
### Tree API
|
|
552
|
+
|
|
553
|
+
Used to present a hierarchical list.
|
|
554
|
+
|
|
555
|
+
#### Props
|
|
556
|
+
|
|
557
|
+
| Name | Type | Required | Default | Description |
|
|
558
|
+
|------|------|------|------|------|
|
|
559
|
+
| children | React.ReactNode | no | | Should contain `Tree.Item`s, can also include other elements to display in between tree items. |
|
|
560
|
+
| defaultIndent | boolean | no | true | Removes default indent from list styles if set to false. |
|
|
561
|
+
| elementRef | React.Ref<HTMLUListElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
### Tree.Item API
|
|
566
|
+
|
|
567
|
+
#### Props
|
|
568
|
+
|
|
569
|
+
| Name | Type | Required | Default | Description |
|
|
570
|
+
|------|------|------|------|------|
|
|
571
|
+
| children | React.ReactNode | no | | Should contain `Tree.Item`s, can also include other elements to display in between tree items. |
|
|
572
|
+
| content | React.ReactNode | no | | Content to show on the `Tree.Item`. |
|
|
573
|
+
| elementRef | React.Ref<HTMLLIElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
|
|
574
|
+
| expanded | boolean | no | | Expansion state of the `Tree.Item`. |
|
|
575
|
+
| id | string | yes | | A unique `id` for this item and used by `Tree` to keep track of the focused item. |
|
|
576
|
+
| onFocus | React.FocusEventHandler<HTMLLIElement> | no | | |
|
|
577
|
+
| onKeyDown | React.KeyboardEventHandler<HTMLLIElement> | no | | |
|
|
578
|
+
| onToggleExpansion | TreeItemToggleExpansionHandler | no | | Called on expansion state change of the `Tree.Item` and should be used to maintain `expanded`. For proper keyboard accessibility this is required when a `Tree.Item` has children. |
|
|
579
|
+
| onToggleSelection | TreeItemToggleSelectionHandler | no | | Called on selection state change of the `Tree.Item` and can be used to maintain optional external `Tree.Item` selection state. |
|
|
580
|
+
|
|
581
|
+
#### Types
|
|
582
|
+
|
|
583
|
+
| Name | Type | Description |
|
|
584
|
+
|------|------|------|
|
|
585
|
+
| TreeItemToggleExpansionHandler | ( event: React.KeyboardEvent<HTMLLIElement> \| React.MouseEvent<HTMLSpanElement>, data?: { // Used to support clickable items inside `Tree.Item` `content` that won't have the `Tree.Item`'s id in its click event treeItemId?: string; } ) => void | |
|
|
586
|
+
| TreeItemToggleSelectionHandler | ( event: React.KeyboardEvent<HTMLLIElement> \| React.MouseEvent<HTMLSpanElement>, data?: { // Used to support clickable items inside `Tree.Item` `content` that won't have the `Tree.Item`'s id in its click event treeItemId?: string; } ) => void | |
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
|