@patternfly/react-data-view 6.3.0-prerelease.3 → 6.4.0-prerelease.10
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/dist/cjs/DataViewTable/DataViewTable.d.ts +6 -1
- package/dist/cjs/DataViewTable/DataViewTable.js +21 -1
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +13 -0
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.js +46 -6
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.test.js +47 -9
- package/dist/cjs/DataViewTableHead/DataViewTableHead.d.ts +2 -0
- package/dist/cjs/DataViewTableHead/DataViewTableHead.js +5 -4
- package/dist/cjs/DataViewTableTree/DataViewTableTree.d.ts +2 -0
- package/dist/cjs/DataViewTableTree/DataViewTableTree.js +28 -1
- package/dist/cjs/DataViewTableTree/DataViewTableTree.test.js +4 -0
- package/dist/cjs/DataViewTh/DataViewTh.d.ts +32 -0
- package/dist/cjs/DataViewTh/DataViewTh.js +222 -0
- package/dist/cjs/DataViewTh/index.d.ts +2 -0
- package/dist/cjs/DataViewTh/index.js +23 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.d.ts +26 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.js +229 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.js +171 -0
- package/dist/cjs/DataViewTreeFilter/index.d.ts +2 -0
- package/dist/cjs/DataViewTreeFilter/index.js +23 -0
- package/dist/cjs/Hooks/selection.d.ts +1 -0
- package/dist/cjs/Hooks/selection.js +5 -1
- package/dist/cjs/Hooks/selection.test.js +48 -0
- package/dist/cjs/InternalContext/InternalContext.d.ts +2 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.js +10 -1
- package/dist/dynamic/DataViewTh/package.json +1 -0
- package/dist/dynamic/DataViewTreeFilter/package.json +1 -0
- package/dist/dynamic-modules.json +62 -0
- package/dist/esm/DataViewTable/DataViewTable.d.ts +6 -1
- package/dist/esm/DataViewTable/DataViewTable.js +21 -1
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +13 -0
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.js +48 -8
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.test.js +45 -10
- package/dist/esm/DataViewTableHead/DataViewTableHead.d.ts +2 -0
- package/dist/esm/DataViewTableHead/DataViewTableHead.js +5 -4
- package/dist/esm/DataViewTableTree/DataViewTableTree.d.ts +2 -0
- package/dist/esm/DataViewTableTree/DataViewTableTree.js +29 -2
- package/dist/esm/DataViewTableTree/DataViewTableTree.test.js +4 -0
- package/dist/esm/DataViewTh/DataViewTh.d.ts +32 -0
- package/dist/esm/DataViewTh/DataViewTh.js +215 -0
- package/dist/esm/DataViewTh/index.d.ts +2 -0
- package/dist/esm/DataViewTh/index.js +2 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.d.ts +26 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.js +225 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.js +166 -0
- package/dist/esm/DataViewTreeFilter/index.d.ts +2 -0
- package/dist/esm/DataViewTreeFilter/index.js +2 -0
- package/dist/esm/Hooks/selection.d.ts +1 -0
- package/dist/esm/Hooks/selection.js +5 -1
- package/dist/esm/Hooks/selection.test.js +48 -0
- package/dist/esm/InternalContext/InternalContext.d.ts +2 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +6 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/generate-fed-package-json.js +18 -0
- package/generate-index.js +2 -2
- package/package.json +12 -12
- package/patternfly-docs/content/extensions/data-view/examples/DataView/DataView.md +10 -4
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx +108 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx +148 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +155 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickyExample.tsx +90 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableTreeExample.tsx +1 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +113 -14
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/SelectionExample.tsx +14 -3
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +10 -2
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/TreeFilterExample.tsx +248 -0
- package/patternfly-docs/patternfly-docs.config.js +4 -1
- package/release.config.js +1 -1
- package/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap +0 -2
- package/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap +0 -2
- package/src/DataViewTable/DataViewTable.tsx +51 -28
- package/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap +17 -25
- package/src/DataViewTableBasic/DataViewTableBasic.test.tsx +54 -12
- package/src/DataViewTableBasic/DataViewTableBasic.tsx +104 -10
- package/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap +30 -30
- package/src/DataViewTableHead/DataViewTableHead.tsx +24 -23
- package/src/DataViewTableHead/__snapshots__/DataViewTableHead.test.tsx.snap +15 -15
- package/src/DataViewTableTree/DataViewTableTree.test.tsx +9 -0
- package/src/DataViewTableTree/DataViewTableTree.tsx +35 -1
- package/src/DataViewTableTree/__snapshots__/DataViewTableTree.test.tsx.snap +977 -28
- package/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap +0 -3
- package/src/DataViewTh/DataViewTh.tsx +342 -0
- package/src/DataViewTh/index.ts +2 -0
- package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +0 -10
- package/src/DataViewTreeFilter/DataViewTreeFilter.test.tsx +222 -0
- package/src/DataViewTreeFilter/DataViewTreeFilter.tsx +361 -0
- package/src/DataViewTreeFilter/__snapshots__/DataViewTreeFilter.test.tsx.snap +199 -0
- package/src/DataViewTreeFilter/index.ts +2 -0
- package/src/Hooks/selection.test.tsx +65 -1
- package/src/Hooks/selection.ts +6 -1
- package/src/InternalContext/InternalContext.tsx +2 -0
- package/src/index.ts +9 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { Dropdown, MenuToggle, MenuToggleElement, ToolbarFilter, ToolbarFilterProps, TreeView, TreeViewDataItem } from '@patternfly/react-core'
|
|
2
|
+
import React, { FC, useState, useRef, useEffect } from 'react'
|
|
3
|
+
import { createUseStyles } from 'react-jss'
|
|
4
|
+
|
|
5
|
+
/** This style is needed so the tree filter dropdown looks like the basic filter dropdow */
|
|
6
|
+
const useStyles = createUseStyles({
|
|
7
|
+
dataViewTreeFilterTreeView: {
|
|
8
|
+
'& .pf-v6-c-tree-view__node::after': {
|
|
9
|
+
borderRadius: 0,
|
|
10
|
+
borderRightStyle: 'none',
|
|
11
|
+
borderLeftStyle: 'none'
|
|
12
|
+
},
|
|
13
|
+
'& .pf-v6-c-tree-view__content': {
|
|
14
|
+
borderRadius: 0
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// Generic helper to collect items from tree based on predicate
|
|
20
|
+
const collectTreeItems = (
|
|
21
|
+
items: TreeViewDataItem[],
|
|
22
|
+
predicate: (item: TreeViewDataItem) => boolean,
|
|
23
|
+
leafOnly = false
|
|
24
|
+
): TreeViewDataItem[] => {
|
|
25
|
+
const collected: TreeViewDataItem[] = [];
|
|
26
|
+
|
|
27
|
+
const collect = (item: TreeViewDataItem) => {
|
|
28
|
+
const isLeaf = !item.children || item.children.length === 0;
|
|
29
|
+
|
|
30
|
+
if (predicate(item) && (!leafOnly || isLeaf)) {
|
|
31
|
+
collected.push(item);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
item.children?.forEach(child => collect(child));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
items.forEach(item => collect(item));
|
|
38
|
+
return collected;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Helper function to get all checked items (not just leaf nodes)
|
|
42
|
+
const getAllCheckedItems = (items: TreeViewDataItem[]): TreeViewDataItem[] =>
|
|
43
|
+
collectTreeItems(items, item => item.checkProps?.checked === true, false);
|
|
44
|
+
|
|
45
|
+
// Get all checked leaf items (returns array of names)
|
|
46
|
+
const getAllCheckedLeafItems = (items: TreeViewDataItem[]): string[] =>
|
|
47
|
+
collectTreeItems(
|
|
48
|
+
items,
|
|
49
|
+
item => item.checkProps?.checked === true,
|
|
50
|
+
true
|
|
51
|
+
).map(item => String(item.name));
|
|
52
|
+
|
|
53
|
+
// Helper function to expand all nodes in the tree
|
|
54
|
+
const expandAllNodes = (items: TreeViewDataItem[]): TreeViewDataItem[] =>
|
|
55
|
+
items.map(item => ({
|
|
56
|
+
...item,
|
|
57
|
+
defaultExpanded: true,
|
|
58
|
+
children: item.children ? expandAllNodes(item.children) : undefined
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Helper function to set pre-selected items
|
|
62
|
+
const setPreSelectedItems = (items: TreeViewDataItem[], selectedIds: string[]): TreeViewDataItem[] =>
|
|
63
|
+
items.map(item => {
|
|
64
|
+
const isSelected = selectedIds.includes(String(item.id));
|
|
65
|
+
const hasSelectedChildren = item.children?.some(child => selectedIds.includes(String(child.id))) ?? false;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...item,
|
|
69
|
+
checkProps: item.checkProps ? {
|
|
70
|
+
...item.checkProps,
|
|
71
|
+
checked: isSelected || hasSelectedChildren
|
|
72
|
+
} : undefined,
|
|
73
|
+
children: item.children ? setPreSelectedItems(item.children, selectedIds) : undefined
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Helper function to uncheck all items recursively
|
|
78
|
+
const uncheckRecursive = (items: TreeViewDataItem[]): TreeViewDataItem[] =>
|
|
79
|
+
items.map(item => ({
|
|
80
|
+
...item,
|
|
81
|
+
checkProps: item.checkProps ? { ...item.checkProps, checked: false } : undefined,
|
|
82
|
+
children: item.children ? uncheckRecursive(item.children) : undefined
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
export interface DataViewTreeFilterProps {
|
|
86
|
+
/** Unique key for the filter attribute */
|
|
87
|
+
filterId: string;
|
|
88
|
+
/** Array of current filter values */
|
|
89
|
+
value?: string[];
|
|
90
|
+
/** Filter title displayed in the toolbar */
|
|
91
|
+
title: string;
|
|
92
|
+
/** Callback for when the selection changes */
|
|
93
|
+
onChange?: (event?: React.MouseEvent, values?: string[]) => void;
|
|
94
|
+
/** Controls visibility of the filter in the toolbar */
|
|
95
|
+
showToolbarItem?: ToolbarFilterProps['showToolbarItem'];
|
|
96
|
+
/** Custom OUIA ID */
|
|
97
|
+
ouiaId?: string;
|
|
98
|
+
/** Hierarchical data items for the tree structure */
|
|
99
|
+
items?: TreeViewDataItem[];
|
|
100
|
+
/** When true, expands all tree nodes by default */
|
|
101
|
+
defaultExpanded?: boolean;
|
|
102
|
+
/** Callback for when tree items are selected/deselected, provides all currently selected nodes */
|
|
103
|
+
onSelect?: (selectedItems: TreeViewDataItem[]) => void;
|
|
104
|
+
/** Array of pre-selected item id's to be checked on initial render */
|
|
105
|
+
defaultSelected?: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const DataViewTreeFilter: FC<DataViewTreeFilterProps> = ({
|
|
109
|
+
filterId,
|
|
110
|
+
title,
|
|
111
|
+
value = [],
|
|
112
|
+
onChange,
|
|
113
|
+
showToolbarItem,
|
|
114
|
+
ouiaId = 'DataViewTreeFilter',
|
|
115
|
+
items,
|
|
116
|
+
defaultExpanded = false,
|
|
117
|
+
onSelect,
|
|
118
|
+
defaultSelected = []
|
|
119
|
+
}: DataViewTreeFilterProps) => {
|
|
120
|
+
const classes = useStyles();
|
|
121
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
122
|
+
const [treeData, setTreeData] = useState<TreeViewDataItem[]>(items || []);
|
|
123
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
124
|
+
const isInitialMount = useRef(true);
|
|
125
|
+
const hasCalledInitialOnChange = useRef(false);
|
|
126
|
+
|
|
127
|
+
// Initialize tree data with defaultExpanded and defaultSelected (only on first mount)
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!items) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let initializedData = [...items];
|
|
134
|
+
|
|
135
|
+
// Apply default expansion
|
|
136
|
+
if (defaultExpanded) {
|
|
137
|
+
initializedData = expandAllNodes(initializedData);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Apply pre-selected items only on initial mount
|
|
141
|
+
if (isInitialMount.current && defaultSelected.length > 0) {
|
|
142
|
+
initializedData = setPreSelectedItems(initializedData, defaultSelected);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setTreeData(initializedData);
|
|
146
|
+
|
|
147
|
+
if (isInitialMount.current) {
|
|
148
|
+
isInitialMount.current = false;
|
|
149
|
+
}
|
|
150
|
+
}, [items, defaultExpanded]);
|
|
151
|
+
|
|
152
|
+
// Call onChange and onSelect after tree data is initialized with default selections
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (!hasCalledInitialOnChange.current && defaultSelected.length > 0 && treeData.length > 0) {
|
|
155
|
+
const selectedValues = getAllCheckedLeafItems(treeData);
|
|
156
|
+
|
|
157
|
+
// Only call if there are actually selected values
|
|
158
|
+
if (selectedValues.length > 0) {
|
|
159
|
+
// Calculate both values synchronously before calling callbacks
|
|
160
|
+
const selectedItems = getAllCheckedItems(treeData);
|
|
161
|
+
|
|
162
|
+
// useEffect already runs after render, so this is safe
|
|
163
|
+
if (onChange) {
|
|
164
|
+
onChange(undefined, selectedValues);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (onSelect) {
|
|
168
|
+
onSelect(selectedItems);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
hasCalledInitialOnChange.current = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}, [treeData, onChange, onSelect, defaultSelected.length]);
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
// Sync tree checkboxes when value prop changes (when clearAllFilters is called)
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (value.length === 0) {
|
|
180
|
+
setTreeData(currentTreeData => {
|
|
181
|
+
if (currentTreeData.length === 0) {
|
|
182
|
+
return currentTreeData;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const currentCheckedItems = getAllCheckedLeafItems(currentTreeData);
|
|
186
|
+
|
|
187
|
+
// Only update if there are checked items that need to be unchecked
|
|
188
|
+
if (currentCheckedItems.length > 0) {
|
|
189
|
+
return uncheckRecursive(currentTreeData);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return currentTreeData;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}, [value]);
|
|
196
|
+
|
|
197
|
+
// Check if all children are checked (recursive)
|
|
198
|
+
const areAllChildrenChecked = (item: TreeViewDataItem): boolean => {
|
|
199
|
+
if (!item.children?.length) {
|
|
200
|
+
return item.checkProps?.checked === true;
|
|
201
|
+
}
|
|
202
|
+
return item.children.every(child => areAllChildrenChecked(child));
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Check if some children are checked (recursive)
|
|
206
|
+
const areSomeChildrenChecked = (item: TreeViewDataItem): boolean => {
|
|
207
|
+
if (!item.children?.length) {
|
|
208
|
+
return item.checkProps?.checked === true;
|
|
209
|
+
}
|
|
210
|
+
return item.children.some(child => areSomeChildrenChecked(child));
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Find tree item by name
|
|
214
|
+
const findItemByName = (items: TreeViewDataItem[], name: string): TreeViewDataItem | null => {
|
|
215
|
+
for (const item of items) {
|
|
216
|
+
if (item.name === name) {
|
|
217
|
+
return item;
|
|
218
|
+
}
|
|
219
|
+
if (item.children) {
|
|
220
|
+
const found = findItemByName(item.children, name);
|
|
221
|
+
if (found) {
|
|
222
|
+
return found;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Find parent item by child ID
|
|
230
|
+
const findParentById = (items: TreeViewDataItem[], childId: string): TreeViewDataItem | null => {
|
|
231
|
+
for (const item of items) {
|
|
232
|
+
if (item.children?.some(child => child.id === childId)) {
|
|
233
|
+
return item;
|
|
234
|
+
}
|
|
235
|
+
if (item.children) {
|
|
236
|
+
const found = findParentById(item.children, childId);
|
|
237
|
+
if (found) {
|
|
238
|
+
return found;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Update parent checkbox states based on children (recursive)
|
|
246
|
+
const onCheckParentHandle = (childId: string): void => {
|
|
247
|
+
const parent = findParentById(treeData, childId);
|
|
248
|
+
if (!parent) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (parent.checkProps) {
|
|
253
|
+
const allChildrenChecked = areAllChildrenChecked(parent);
|
|
254
|
+
const someChildrenChecked = areSomeChildrenChecked(parent);
|
|
255
|
+
|
|
256
|
+
if (allChildrenChecked) {
|
|
257
|
+
parent.checkProps.checked = true;
|
|
258
|
+
} else if (someChildrenChecked) {
|
|
259
|
+
parent.checkProps.checked = null;
|
|
260
|
+
} else {
|
|
261
|
+
parent.checkProps.checked = false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (parent.id) {
|
|
266
|
+
onCheckParentHandle(parent.id);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// Check/uncheck item and all its children (recursive)
|
|
271
|
+
const onCheckHandle = (treeViewItem: TreeViewDataItem, checked: boolean): void => {
|
|
272
|
+
if (treeViewItem.checkProps) {
|
|
273
|
+
treeViewItem.checkProps.checked = checked;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
treeViewItem.children?.forEach(child => onCheckHandle(child, checked));
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Handle checkbox change event
|
|
280
|
+
const onCheck = (event: React.ChangeEvent, treeViewItem: TreeViewDataItem) => {
|
|
281
|
+
const checked = (event.target as HTMLInputElement).checked;
|
|
282
|
+
|
|
283
|
+
onCheckHandle(treeViewItem, checked);
|
|
284
|
+
|
|
285
|
+
if (treeViewItem.id) {
|
|
286
|
+
onCheckParentHandle(treeViewItem.id);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
setTreeData(prev => [...prev]);
|
|
290
|
+
|
|
291
|
+
const selectedValues = getAllCheckedLeafItems(treeData);
|
|
292
|
+
onChange?.(event as any, selectedValues);
|
|
293
|
+
|
|
294
|
+
if (onSelect) {
|
|
295
|
+
const selectedItems = getAllCheckedItems(treeData);
|
|
296
|
+
onSelect(selectedItems);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// Clear a specific filter by name (when label chip is removed)
|
|
301
|
+
const onFilterSelectorClear = (itemName: string) => {
|
|
302
|
+
const treeViewItem = findItemByName(treeData, itemName);
|
|
303
|
+
if (!treeViewItem) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
onCheckHandle(treeViewItem, false);
|
|
308
|
+
if (treeViewItem.id) {
|
|
309
|
+
onCheckParentHandle(treeViewItem.id);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Uncheck all items in the tree
|
|
314
|
+
const uncheckAllItems = () => {
|
|
315
|
+
const updatedTreeData = uncheckRecursive(treeData);
|
|
316
|
+
setTreeData(updatedTreeData);
|
|
317
|
+
onChange?.(undefined, []);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const dropdown = (
|
|
321
|
+
<Dropdown
|
|
322
|
+
ref={menuRef}
|
|
323
|
+
isOpen={isOpen}
|
|
324
|
+
onOpenChange={(isOpen: boolean) => setIsOpen(isOpen)}
|
|
325
|
+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
|
|
326
|
+
<MenuToggle ref={toggleRef} onClick={() => setIsOpen(!isOpen)} isExpanded={isOpen}>
|
|
327
|
+
{title}
|
|
328
|
+
</MenuToggle>
|
|
329
|
+
)}
|
|
330
|
+
ouiaId={ouiaId}
|
|
331
|
+
shouldFocusToggleOnSelect
|
|
332
|
+
>
|
|
333
|
+
<TreeView
|
|
334
|
+
hasAnimations
|
|
335
|
+
data={treeData}
|
|
336
|
+
onCheck={onCheck}
|
|
337
|
+
hasCheckboxes
|
|
338
|
+
className={classes.dataViewTreeFilterTreeView}
|
|
339
|
+
/>
|
|
340
|
+
</Dropdown>
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<ToolbarFilter
|
|
345
|
+
key={filterId}
|
|
346
|
+
data-ouia-component-id={ouiaId}
|
|
347
|
+
labels={value.map(item => ({ key: item, node: item }))}
|
|
348
|
+
deleteLabel={(_, label) => {
|
|
349
|
+
const labelKey = typeof label === 'string' ? label : label.key;
|
|
350
|
+
onChange?.(undefined, value.filter(item => item !== labelKey));
|
|
351
|
+
onFilterSelectorClear(labelKey);
|
|
352
|
+
}}
|
|
353
|
+
deleteLabelGroup={uncheckAllItems}
|
|
354
|
+
categoryName={title}
|
|
355
|
+
showToolbarItem={showToolbarItem}>
|
|
356
|
+
{dropdown}
|
|
357
|
+
</ToolbarFilter>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export default DataViewTreeFilter;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`DataViewTreeFilter component should render correctly 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="pf-v6-c-toolbar"
|
|
7
|
+
data-ouia-component-id="DataViewToolbar"
|
|
8
|
+
data-ouia-component-type="PF6/Toolbar"
|
|
9
|
+
data-ouia-safe="true"
|
|
10
|
+
id="pf-random-id-0"
|
|
11
|
+
>
|
|
12
|
+
<div
|
|
13
|
+
class="pf-v6-c-toolbar__content"
|
|
14
|
+
>
|
|
15
|
+
<div
|
|
16
|
+
class="pf-v6-c-toolbar__content-section"
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
class="pf-v6-c-toolbar__item"
|
|
20
|
+
>
|
|
21
|
+
<div
|
|
22
|
+
class="pf-v6-c-toolbar__item"
|
|
23
|
+
data-ouia-component-id="DataViewTreeFilter"
|
|
24
|
+
>
|
|
25
|
+
<button
|
|
26
|
+
aria-expanded="false"
|
|
27
|
+
class="pf-v6-c-menu-toggle"
|
|
28
|
+
data-ouia-component-id="OUIA-Generated-MenuToggle-2"
|
|
29
|
+
data-ouia-component-type="PF6/MenuToggle"
|
|
30
|
+
data-ouia-safe="true"
|
|
31
|
+
type="button"
|
|
32
|
+
>
|
|
33
|
+
<span
|
|
34
|
+
class="pf-v6-c-menu-toggle__text"
|
|
35
|
+
>
|
|
36
|
+
Test Tree Filter
|
|
37
|
+
</span>
|
|
38
|
+
<span
|
|
39
|
+
class="pf-v6-c-menu-toggle__controls"
|
|
40
|
+
>
|
|
41
|
+
<span
|
|
42
|
+
class="pf-v6-c-menu-toggle__toggle-icon"
|
|
43
|
+
>
|
|
44
|
+
<svg
|
|
45
|
+
aria-hidden="true"
|
|
46
|
+
class="pf-v6-svg"
|
|
47
|
+
fill="currentColor"
|
|
48
|
+
height="1em"
|
|
49
|
+
role="img"
|
|
50
|
+
viewBox="0 0 320 512"
|
|
51
|
+
width="1em"
|
|
52
|
+
>
|
|
53
|
+
<path
|
|
54
|
+
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
55
|
+
/>
|
|
56
|
+
</svg>
|
|
57
|
+
</span>
|
|
58
|
+
</span>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div
|
|
65
|
+
class="pf-v6-c-toolbar__content"
|
|
66
|
+
>
|
|
67
|
+
<div
|
|
68
|
+
class="pf-v6-c-toolbar__group"
|
|
69
|
+
>
|
|
70
|
+
<div
|
|
71
|
+
class="pf-v6-c-toolbar__item pf-m-label-group pf-m-label-group"
|
|
72
|
+
>
|
|
73
|
+
<div
|
|
74
|
+
class="pf-v6-c-label-group pf-m-category"
|
|
75
|
+
>
|
|
76
|
+
<div
|
|
77
|
+
class="pf-v6-c-label-group__main"
|
|
78
|
+
>
|
|
79
|
+
<span
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
class="pf-v6-c-label-group__label"
|
|
82
|
+
id="pf-random-id-1"
|
|
83
|
+
>
|
|
84
|
+
Test Tree Filter
|
|
85
|
+
</span>
|
|
86
|
+
<ul
|
|
87
|
+
aria-labelledby="pf-random-id-1"
|
|
88
|
+
class="pf-v6-c-label-group__list"
|
|
89
|
+
role="list"
|
|
90
|
+
>
|
|
91
|
+
<li
|
|
92
|
+
class="pf-v6-c-label-group__list-item"
|
|
93
|
+
>
|
|
94
|
+
<span
|
|
95
|
+
class="pf-v6-c-label pf-m-filled"
|
|
96
|
+
>
|
|
97
|
+
<span
|
|
98
|
+
class="pf-v6-c-label__content"
|
|
99
|
+
>
|
|
100
|
+
<span
|
|
101
|
+
class="pf-v6-c-label__text"
|
|
102
|
+
>
|
|
103
|
+
Linux
|
|
104
|
+
</span>
|
|
105
|
+
</span>
|
|
106
|
+
<span
|
|
107
|
+
class="pf-v6-c-label__actions"
|
|
108
|
+
>
|
|
109
|
+
<button
|
|
110
|
+
aria-label="Close Linux"
|
|
111
|
+
class="pf-v6-c-button pf-m-plain pf-m-no-padding"
|
|
112
|
+
data-ouia-component-id="OUIA-Generated-Button-plain-1"
|
|
113
|
+
data-ouia-component-type="PF6/Button"
|
|
114
|
+
data-ouia-safe="true"
|
|
115
|
+
type="button"
|
|
116
|
+
>
|
|
117
|
+
<span
|
|
118
|
+
class="pf-v6-c-button__icon"
|
|
119
|
+
>
|
|
120
|
+
<svg
|
|
121
|
+
aria-hidden="true"
|
|
122
|
+
class="pf-v6-svg"
|
|
123
|
+
fill="currentColor"
|
|
124
|
+
height="1em"
|
|
125
|
+
role="img"
|
|
126
|
+
viewBox="0 0 352 512"
|
|
127
|
+
width="1em"
|
|
128
|
+
>
|
|
129
|
+
<path
|
|
130
|
+
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
|
|
131
|
+
/>
|
|
132
|
+
</svg>
|
|
133
|
+
</span>
|
|
134
|
+
</button>
|
|
135
|
+
</span>
|
|
136
|
+
</span>
|
|
137
|
+
</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
<div
|
|
141
|
+
class="pf-v6-c-label-group__close"
|
|
142
|
+
>
|
|
143
|
+
<button
|
|
144
|
+
aria-label="Close label group"
|
|
145
|
+
aria-labelledby="remove_group_pf-random-id-1 pf-random-id-1"
|
|
146
|
+
class="pf-v6-c-button pf-m-plain pf-m-small"
|
|
147
|
+
data-ouia-component-id="OUIA-Generated-Button-plain-2"
|
|
148
|
+
data-ouia-component-type="PF6/Button"
|
|
149
|
+
data-ouia-safe="true"
|
|
150
|
+
id="remove_group_pf-random-id-1"
|
|
151
|
+
type="button"
|
|
152
|
+
>
|
|
153
|
+
<span
|
|
154
|
+
class="pf-v6-c-button__icon"
|
|
155
|
+
>
|
|
156
|
+
<svg
|
|
157
|
+
aria-hidden="true"
|
|
158
|
+
class="pf-v6-svg"
|
|
159
|
+
fill="currentColor"
|
|
160
|
+
height="1em"
|
|
161
|
+
role="img"
|
|
162
|
+
viewBox="0 0 352 512"
|
|
163
|
+
width="1em"
|
|
164
|
+
>
|
|
165
|
+
<path
|
|
166
|
+
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
|
|
167
|
+
/>
|
|
168
|
+
</svg>
|
|
169
|
+
</span>
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div
|
|
176
|
+
class="pf-v6-c-toolbar__group pf-m-action-group-inline"
|
|
177
|
+
>
|
|
178
|
+
<div
|
|
179
|
+
class="pf-v6-c-toolbar__item"
|
|
180
|
+
>
|
|
181
|
+
<button
|
|
182
|
+
class="pf-v6-c-button pf-m-link pf-m-inline"
|
|
183
|
+
data-ouia-component-id="DataViewToolbar-clear-all-filters"
|
|
184
|
+
data-ouia-component-type="PF6/Button"
|
|
185
|
+
data-ouia-safe="true"
|
|
186
|
+
type="button"
|
|
187
|
+
>
|
|
188
|
+
<span
|
|
189
|
+
class="pf-v6-c-button__text"
|
|
190
|
+
>
|
|
191
|
+
Clear filters
|
|
192
|
+
</span>
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
`;
|
|
@@ -9,6 +9,7 @@ describe('useDataViewSelection', () => {
|
|
|
9
9
|
selected: [],
|
|
10
10
|
onSelect: expect.any(Function),
|
|
11
11
|
isSelected: expect.any(Function),
|
|
12
|
+
setSelected: expect.any(Function),
|
|
12
13
|
})
|
|
13
14
|
});
|
|
14
15
|
|
|
@@ -19,6 +20,7 @@ describe('useDataViewSelection', () => {
|
|
|
19
20
|
selected: initialSelected,
|
|
20
21
|
onSelect: expect.any(Function),
|
|
21
22
|
isSelected: expect.any(Function),
|
|
23
|
+
setSelected: expect.any(Function),
|
|
22
24
|
})
|
|
23
25
|
});
|
|
24
26
|
|
|
@@ -49,4 +51,66 @@ describe('useDataViewSelection', () => {
|
|
|
49
51
|
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(true);
|
|
50
52
|
expect(result.current.isSelected({ id: 3, name: 'test2' })).toBe(false);
|
|
51
53
|
});
|
|
52
|
-
|
|
54
|
+
|
|
55
|
+
it('should have setSelected function in return object', () => {
|
|
56
|
+
const { result } = renderHook(() => useDataViewSelection({ matchOption: (a, b) => a.id === b.id }))
|
|
57
|
+
expect(result.current).toEqual({
|
|
58
|
+
selected: [],
|
|
59
|
+
onSelect: expect.any(Function),
|
|
60
|
+
isSelected: expect.any(Function),
|
|
61
|
+
setSelected: expect.any(Function),
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should set selected items directly using setSelected - objects', async () => {
|
|
66
|
+
const initialSelected = [ { id: 1, name: 'test1' } ];
|
|
67
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
|
|
68
|
+
|
|
69
|
+
const newSelected = [ { id: 2, name: 'test2' }, { id: 3, name: 'test3' } ];
|
|
70
|
+
|
|
71
|
+
await act(async () => {
|
|
72
|
+
result.current.setSelected(newSelected);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should set selected items directly using setSelected - strings', async () => {
|
|
79
|
+
const initialSelected = [ 'test1', 'test2' ];
|
|
80
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a === b }))
|
|
81
|
+
|
|
82
|
+
const newSelected = [ 'test3', 'test4', 'test5' ];
|
|
83
|
+
|
|
84
|
+
await act(async () => {
|
|
85
|
+
result.current.setSelected(newSelected);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should clear all selections using setSelected with empty array', async () => {
|
|
92
|
+
const initialSelected = [ { id: 1, name: 'test1' }, { id: 2, name: 'test2' } ];
|
|
93
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
|
|
94
|
+
|
|
95
|
+
await act(async () => {
|
|
96
|
+
result.current.setSelected([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(result.current.selected).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should update isSelected correctly after using setSelected', async () => {
|
|
103
|
+
const initialSelected = [ { id: 1, name: 'test1' } ];
|
|
104
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
|
|
105
|
+
|
|
106
|
+
const newSelected = [ { id: 2, name: 'test2' }, { id: 3, name: 'test3' } ];
|
|
107
|
+
|
|
108
|
+
await act(async () => {
|
|
109
|
+
result.current.setSelected(newSelected);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(false);
|
|
113
|
+
expect(result.current.isSelected({ id: 2, name: 'test2' })).toBe(true);
|
|
114
|
+
expect(result.current.isSelected({ id: 3, name: 'test3' })).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
package/src/Hooks/selection.ts
CHANGED
|
@@ -24,9 +24,14 @@ export const useDataViewSelection = (props?: UseDataViewSelectionProps) => {
|
|
|
24
24
|
|
|
25
25
|
const isSelected = (item: any): boolean => Boolean(selected.find(selected => matchOption(selected, item)));
|
|
26
26
|
|
|
27
|
+
const setSelectedItems = (items: any[]) => {
|
|
28
|
+
setSelected(items);
|
|
29
|
+
};
|
|
30
|
+
|
|
27
31
|
return {
|
|
28
32
|
selected,
|
|
29
33
|
onSelect,
|
|
30
|
-
isSelected
|
|
34
|
+
isSelected,
|
|
35
|
+
setSelected: setSelectedItems
|
|
31
36
|
};
|
|
32
37
|
};
|
|
@@ -6,6 +6,8 @@ export interface DataViewSelection {
|
|
|
6
6
|
onSelect: (isSelecting: boolean, items?: any[] | any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
7
7
|
/** Checks if a specific item is currently selected */
|
|
8
8
|
isSelected: (item: any) => boolean; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
9
|
+
/** Directly sets the selected items */
|
|
10
|
+
setSelected?: (items: any[]) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
9
11
|
/** Determines if selection is disabled for a given item */
|
|
10
12
|
isSelectDisabled?: (item: any) => boolean; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
11
13
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,9 +4,15 @@ export { default as InternalContext } from './InternalContext';
|
|
|
4
4
|
export * from './InternalContext';
|
|
5
5
|
export * from './Hooks';
|
|
6
6
|
|
|
7
|
+
export { default as DataViewTreeFilter } from './DataViewTreeFilter';
|
|
8
|
+
export * from './DataViewTreeFilter';
|
|
9
|
+
|
|
7
10
|
export { default as DataViewToolbar } from './DataViewToolbar';
|
|
8
11
|
export * from './DataViewToolbar';
|
|
9
12
|
|
|
13
|
+
export { default as DataViewTh } from './DataViewTh';
|
|
14
|
+
export * from './DataViewTh';
|
|
15
|
+
|
|
10
16
|
export { default as DataViewTextFilter } from './DataViewTextFilter';
|
|
11
17
|
export * from './DataViewTextFilter';
|
|
12
18
|
|
|
@@ -22,6 +28,9 @@ export * from './DataViewTableBasic';
|
|
|
22
28
|
export { default as DataViewTable } from './DataViewTable';
|
|
23
29
|
export * from './DataViewTable';
|
|
24
30
|
|
|
31
|
+
export { default as DataViewFilters } from './DataViewFilters';
|
|
32
|
+
export * from './DataViewFilters';
|
|
33
|
+
|
|
25
34
|
export { default as DataViewEventsContext } from './DataViewEventsContext';
|
|
26
35
|
export * from './DataViewEventsContext';
|
|
27
36
|
|