@patternfly/react-data-view 6.4.0 → 6.5.0-prerelease.2

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 (89) hide show
  1. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +2 -0
  2. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.js +3 -2
  3. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -1
  4. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +8 -2
  5. package/dist/cjs/DataViewTable/DataViewTable.d.ts +2 -1
  6. package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +11 -0
  7. package/dist/cjs/DataViewTableBasic/DataViewTableBasic.js +46 -6
  8. package/dist/cjs/DataViewTableBasic/DataViewTableBasic.test.js +47 -9
  9. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.d.ts +4 -0
  10. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +31 -2
  11. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
  12. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.js +88 -0
  13. package/dist/cjs/DataViewTh/DataViewTh.d.ts +4 -4
  14. package/dist/cjs/DataViewTh/DataViewTh.js +8 -1
  15. package/dist/cjs/DataViewTh/index.d.ts +2 -0
  16. package/dist/cjs/DataViewTh/index.js +23 -0
  17. package/dist/cjs/DataViewToolbar/DataViewToolbar.js +13 -1
  18. package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.d.ts +28 -0
  19. package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.js +230 -0
  20. package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
  21. package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.js +176 -0
  22. package/dist/cjs/DataViewTreeFilter/index.d.ts +2 -0
  23. package/dist/cjs/DataViewTreeFilter/index.js +23 -0
  24. package/dist/cjs/Hooks/selection.d.ts +8 -8
  25. package/dist/cjs/index.d.ts +6 -0
  26. package/dist/cjs/index.js +10 -1
  27. package/dist/dynamic/DataViewTh/package.json +1 -0
  28. package/dist/dynamic/DataViewTreeFilter/package.json +1 -0
  29. package/dist/dynamic-modules.json +62 -0
  30. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +2 -0
  31. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.js +3 -2
  32. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -1
  33. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +9 -3
  34. package/dist/esm/DataViewTable/DataViewTable.d.ts +2 -1
  35. package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +11 -0
  36. package/dist/esm/DataViewTableBasic/DataViewTableBasic.js +48 -8
  37. package/dist/esm/DataViewTableBasic/DataViewTableBasic.test.js +45 -10
  38. package/dist/esm/DataViewTextFilter/DataViewTextFilter.d.ts +4 -0
  39. package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +31 -2
  40. package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
  41. package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.js +90 -2
  42. package/dist/esm/DataViewTh/DataViewTh.d.ts +4 -4
  43. package/dist/esm/DataViewTh/DataViewTh.js +8 -1
  44. package/dist/esm/DataViewTh/index.d.ts +2 -0
  45. package/dist/esm/DataViewTh/index.js +2 -0
  46. package/dist/esm/DataViewToolbar/DataViewToolbar.js +13 -1
  47. package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.d.ts +28 -0
  48. package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.js +226 -0
  49. package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
  50. package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.js +171 -0
  51. package/dist/esm/DataViewTreeFilter/index.d.ts +2 -0
  52. package/dist/esm/DataViewTreeFilter/index.js +2 -0
  53. package/dist/esm/Hooks/selection.d.ts +8 -8
  54. package/dist/esm/index.d.ts +6 -0
  55. package/dist/esm/index.js +6 -0
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/generate-fed-package-json.js +18 -0
  58. package/generate-index.js +2 -2
  59. package/package.json +6 -6
  60. package/patternfly-docs/content/extensions/data-view/examples/DataView/DataView.md +10 -4
  61. package/patternfly-docs/content/extensions/data-view/examples/DataView/PredefinedLayoutFullExample.tsx +2 -1
  62. package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx +108 -0
  63. package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx +148 -0
  64. package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickyExample.tsx +90 -0
  65. package/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +63 -2
  66. package/patternfly-docs/content/extensions/data-view/examples/Toolbar/FiltersExample.tsx +3 -2
  67. package/patternfly-docs/content/extensions/data-view/examples/Toolbar/PaginationExample.tsx +1 -1
  68. package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +9 -2
  69. package/patternfly-docs/content/extensions/data-view/examples/Toolbar/TreeFilterExample.tsx +248 -0
  70. package/patternfly-docs/patternfly-docs.config.js +4 -1
  71. package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx +16 -7
  72. package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx +5 -1
  73. package/src/DataViewTable/DataViewTable.tsx +3 -1
  74. package/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap +7 -7
  75. package/src/DataViewTableBasic/DataViewTableBasic.test.tsx +54 -12
  76. package/src/DataViewTableBasic/DataViewTableBasic.tsx +101 -10
  77. package/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap +10 -10
  78. package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +140 -1
  79. package/src/DataViewTextFilter/DataViewTextFilter.tsx +63 -22
  80. package/src/DataViewTh/DataViewTh.tsx +15 -7
  81. package/src/DataViewTh/index.ts +2 -0
  82. package/src/DataViewToolbar/DataViewToolbar.tsx +17 -2
  83. package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +288 -280
  84. package/src/DataViewTreeFilter/DataViewTreeFilter.test.tsx +233 -0
  85. package/src/DataViewTreeFilter/DataViewTreeFilter.tsx +365 -0
  86. package/src/DataViewTreeFilter/__snapshots__/DataViewTreeFilter.test.tsx.snap +199 -0
  87. package/src/DataViewTreeFilter/index.ts +2 -0
  88. package/src/Hooks/selection.ts +8 -8
  89. package/src/index.ts +9 -0
@@ -0,0 +1,226 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Dropdown, MenuToggle, ToolbarFilter, TreeView } from '@patternfly/react-core';
3
+ import { useState, useRef, useEffect } from 'react';
4
+ import { createUseStyles } from 'react-jss';
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
+ // Generic helper to collect items from tree based on predicate
19
+ const collectTreeItems = (items, predicate, leafOnly = false) => {
20
+ const collected = [];
21
+ const collect = (item) => {
22
+ var _a;
23
+ const isLeaf = !item.children || item.children.length === 0;
24
+ if (predicate(item) && (!leafOnly || isLeaf)) {
25
+ collected.push(item);
26
+ }
27
+ (_a = item.children) === null || _a === void 0 ? void 0 : _a.forEach(child => collect(child));
28
+ };
29
+ items.forEach(item => collect(item));
30
+ return collected;
31
+ };
32
+ // Helper function to get all checked items (not just leaf nodes)
33
+ const getAllCheckedItems = (items) => collectTreeItems(items, item => { var _a; return ((_a = item.checkProps) === null || _a === void 0 ? void 0 : _a.checked) === true; }, false);
34
+ // Get all checked leaf items (returns array of names)
35
+ const getAllCheckedLeafItems = (items) => collectTreeItems(items, item => { var _a; return ((_a = item.checkProps) === null || _a === void 0 ? void 0 : _a.checked) === true; }, true).map(item => String(item.name));
36
+ // Helper function to expand all nodes in the tree
37
+ const expandAllNodes = (items) => items.map(item => (Object.assign(Object.assign({}, item), { defaultExpanded: true, children: item.children ? expandAllNodes(item.children) : undefined })));
38
+ // Helper function to set pre-selected items
39
+ const setPreSelectedItems = (items, selectedIds) => items.map(item => {
40
+ var _a, _b;
41
+ const isSelected = selectedIds.includes(String(item.id));
42
+ const hasSelectedChildren = (_b = (_a = item.children) === null || _a === void 0 ? void 0 : _a.some(child => selectedIds.includes(String(child.id)))) !== null && _b !== void 0 ? _b : false;
43
+ return Object.assign(Object.assign({}, item), { checkProps: item.checkProps ? Object.assign(Object.assign({}, item.checkProps), { checked: isSelected || hasSelectedChildren }) : undefined, children: item.children ? setPreSelectedItems(item.children, selectedIds) : undefined });
44
+ });
45
+ // Helper function to uncheck all items recursively
46
+ const uncheckRecursive = (items) => items.map(item => (Object.assign(Object.assign({}, item), { checkProps: item.checkProps ? Object.assign(Object.assign({}, item.checkProps), { checked: false }) : undefined, children: item.children ? uncheckRecursive(item.children) : undefined })));
47
+ export const DataViewTreeFilter = ({ filterId, title, chipTitle, value = [], onChange, showToolbarItem, ouiaId = 'DataViewTreeFilter', items, defaultExpanded = false, onSelect, defaultSelected = [] }) => {
48
+ const categoryName = chipTitle !== null && chipTitle !== void 0 ? chipTitle : title;
49
+ const classes = useStyles();
50
+ const [isOpen, setIsOpen] = useState(false);
51
+ const [treeData, setTreeData] = useState(items || []);
52
+ const menuRef = useRef(null);
53
+ const isInitialMount = useRef(true);
54
+ const hasCalledInitialOnChange = useRef(false);
55
+ // Initialize tree data with defaultExpanded and defaultSelected (only on first mount)
56
+ useEffect(() => {
57
+ if (!items) {
58
+ return;
59
+ }
60
+ let initializedData = [...items];
61
+ // Apply default expansion
62
+ if (defaultExpanded) {
63
+ initializedData = expandAllNodes(initializedData);
64
+ }
65
+ // Apply pre-selected items only on initial mount
66
+ if (isInitialMount.current && defaultSelected.length > 0) {
67
+ initializedData = setPreSelectedItems(initializedData, defaultSelected);
68
+ }
69
+ setTreeData(initializedData);
70
+ if (isInitialMount.current) {
71
+ isInitialMount.current = false;
72
+ }
73
+ }, [items, defaultExpanded]);
74
+ // Call onChange and onSelect after tree data is initialized with default selections
75
+ useEffect(() => {
76
+ if (!hasCalledInitialOnChange.current && defaultSelected.length > 0 && treeData.length > 0) {
77
+ const selectedValues = getAllCheckedLeafItems(treeData);
78
+ // Only call if there are actually selected values
79
+ if (selectedValues.length > 0) {
80
+ // Calculate both values synchronously before calling callbacks
81
+ const selectedItems = getAllCheckedItems(treeData);
82
+ // useEffect already runs after render, so this is safe
83
+ if (onChange) {
84
+ onChange(undefined, selectedValues);
85
+ }
86
+ if (onSelect) {
87
+ onSelect(selectedItems);
88
+ }
89
+ hasCalledInitialOnChange.current = true;
90
+ }
91
+ }
92
+ }, [treeData, onChange, onSelect, defaultSelected.length]);
93
+ // Sync tree checkboxes when value prop changes (when clearAllFilters is called)
94
+ useEffect(() => {
95
+ if (value.length === 0) {
96
+ setTreeData(currentTreeData => {
97
+ if (currentTreeData.length === 0) {
98
+ return currentTreeData;
99
+ }
100
+ const currentCheckedItems = getAllCheckedLeafItems(currentTreeData);
101
+ // Only update if there are checked items that need to be unchecked
102
+ if (currentCheckedItems.length > 0) {
103
+ return uncheckRecursive(currentTreeData);
104
+ }
105
+ return currentTreeData;
106
+ });
107
+ }
108
+ }, [value]);
109
+ // Check if all children are checked (recursive)
110
+ const areAllChildrenChecked = (item) => {
111
+ var _a, _b;
112
+ if (!((_a = item.children) === null || _a === void 0 ? void 0 : _a.length)) {
113
+ return ((_b = item.checkProps) === null || _b === void 0 ? void 0 : _b.checked) === true;
114
+ }
115
+ return item.children.every(child => areAllChildrenChecked(child));
116
+ };
117
+ // Check if some children are checked (recursive)
118
+ const areSomeChildrenChecked = (item) => {
119
+ var _a, _b;
120
+ if (!((_a = item.children) === null || _a === void 0 ? void 0 : _a.length)) {
121
+ return ((_b = item.checkProps) === null || _b === void 0 ? void 0 : _b.checked) === true;
122
+ }
123
+ return item.children.some(child => areSomeChildrenChecked(child));
124
+ };
125
+ // Find tree item by name
126
+ const findItemByName = (items, name) => {
127
+ for (const item of items) {
128
+ if (item.name === name) {
129
+ return item;
130
+ }
131
+ if (item.children) {
132
+ const found = findItemByName(item.children, name);
133
+ if (found) {
134
+ return found;
135
+ }
136
+ }
137
+ }
138
+ return null;
139
+ };
140
+ // Find parent item by child ID
141
+ const findParentById = (items, childId) => {
142
+ var _a;
143
+ for (const item of items) {
144
+ if ((_a = item.children) === null || _a === void 0 ? void 0 : _a.some(child => child.id === childId)) {
145
+ return item;
146
+ }
147
+ if (item.children) {
148
+ const found = findParentById(item.children, childId);
149
+ if (found) {
150
+ return found;
151
+ }
152
+ }
153
+ }
154
+ return null;
155
+ };
156
+ // Update parent checkbox states based on children (recursive)
157
+ const onCheckParentHandle = (childId) => {
158
+ const parent = findParentById(treeData, childId);
159
+ if (!parent) {
160
+ return;
161
+ }
162
+ if (parent.checkProps) {
163
+ const allChildrenChecked = areAllChildrenChecked(parent);
164
+ const someChildrenChecked = areSomeChildrenChecked(parent);
165
+ if (allChildrenChecked) {
166
+ parent.checkProps.checked = true;
167
+ }
168
+ else if (someChildrenChecked) {
169
+ parent.checkProps.checked = null;
170
+ }
171
+ else {
172
+ parent.checkProps.checked = false;
173
+ }
174
+ }
175
+ if (parent.id) {
176
+ onCheckParentHandle(parent.id);
177
+ }
178
+ };
179
+ // Check/uncheck item and all its children (recursive)
180
+ const onCheckHandle = (treeViewItem, checked) => {
181
+ var _a;
182
+ if (treeViewItem.checkProps) {
183
+ treeViewItem.checkProps.checked = checked;
184
+ }
185
+ (_a = treeViewItem.children) === null || _a === void 0 ? void 0 : _a.forEach(child => onCheckHandle(child, checked));
186
+ };
187
+ // Handle checkbox change event
188
+ const onCheck = (event, treeViewItem) => {
189
+ const checked = event.target.checked;
190
+ onCheckHandle(treeViewItem, checked);
191
+ if (treeViewItem.id) {
192
+ onCheckParentHandle(treeViewItem.id);
193
+ }
194
+ setTreeData(prev => [...prev]);
195
+ const selectedValues = getAllCheckedLeafItems(treeData);
196
+ onChange === null || onChange === void 0 ? void 0 : onChange(event, selectedValues);
197
+ if (onSelect) {
198
+ const selectedItems = getAllCheckedItems(treeData);
199
+ onSelect(selectedItems);
200
+ }
201
+ };
202
+ // Clear a specific filter by name (when label chip is removed)
203
+ const onFilterSelectorClear = (itemName) => {
204
+ const treeViewItem = findItemByName(treeData, itemName);
205
+ if (!treeViewItem) {
206
+ return;
207
+ }
208
+ onCheckHandle(treeViewItem, false);
209
+ if (treeViewItem.id) {
210
+ onCheckParentHandle(treeViewItem.id);
211
+ }
212
+ };
213
+ // Uncheck all items in the tree
214
+ const uncheckAllItems = () => {
215
+ const updatedTreeData = uncheckRecursive(treeData);
216
+ setTreeData(updatedTreeData);
217
+ onChange === null || onChange === void 0 ? void 0 : onChange(undefined, []);
218
+ };
219
+ const dropdown = (_jsx(Dropdown, { ref: menuRef, isOpen: isOpen, onOpenChange: (isOpen) => setIsOpen(isOpen), toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => setIsOpen(!isOpen), isExpanded: isOpen, children: title })), ouiaId: ouiaId, shouldFocusToggleOnSelect: true, children: _jsx(TreeView, { hasAnimations: true, data: treeData, onCheck: onCheck, hasCheckboxes: true, className: classes.dataViewTreeFilterTreeView }) }));
220
+ return (_jsx(ToolbarFilter, { "data-ouia-component-id": ouiaId, labels: value.map(item => ({ key: item, node: item })), deleteLabel: (_, label) => {
221
+ const labelKey = typeof label === 'string' ? label : label.key;
222
+ onChange === null || onChange === void 0 ? void 0 : onChange(undefined, value.filter(item => item !== labelKey));
223
+ onFilterSelectorClear(labelKey);
224
+ }, deleteLabelGroup: uncheckAllItems, categoryName: categoryName, showToolbarItem: showToolbarItem, children: dropdown }, filterId));
225
+ };
226
+ export default DataViewTreeFilter;
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,171 @@
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
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { render, screen, waitFor } from '@testing-library/react';
12
+ import '@testing-library/jest-dom';
13
+ import userEvent from '@testing-library/user-event';
14
+ import DataViewTreeFilter from './DataViewTreeFilter';
15
+ import DataViewToolbar from '../DataViewToolbar';
16
+ describe('DataViewTreeFilter component', () => {
17
+ const treeItems = [
18
+ {
19
+ name: 'Linux',
20
+ id: 'os-linux',
21
+ checkProps: { 'aria-label': 'linux-check', checked: false },
22
+ children: [
23
+ {
24
+ name: 'Ubuntu 22.04',
25
+ id: 'os-ubuntu',
26
+ checkProps: { checked: false }
27
+ },
28
+ {
29
+ name: 'RHEL 9',
30
+ id: 'os-rhel',
31
+ checkProps: { checked: false }
32
+ },
33
+ {
34
+ name: 'Debian 12',
35
+ id: 'os-debian',
36
+ checkProps: { checked: false }
37
+ },
38
+ {
39
+ name: 'CentOS 8',
40
+ id: 'os-centos',
41
+ checkProps: { checked: false }
42
+ },
43
+ {
44
+ name: 'Fedora 38',
45
+ id: 'os-fedora',
46
+ checkProps: { checked: false }
47
+ }
48
+ ],
49
+ defaultExpanded: true
50
+ },
51
+ {
52
+ name: 'Windows',
53
+ id: 'os-windows',
54
+ checkProps: { 'aria-label': 'windows-check', checked: false },
55
+ children: [
56
+ {
57
+ name: 'Windows Server 2022',
58
+ id: 'os-windows-2022',
59
+ checkProps: { checked: false }
60
+ }
61
+ ]
62
+ },
63
+ {
64
+ name: 'macOS',
65
+ id: 'os-macos',
66
+ checkProps: { 'aria-label': 'macos-check', checked: false },
67
+ children: [
68
+ {
69
+ name: 'macOS Ventura',
70
+ id: 'os-macos-ventura',
71
+ checkProps: { checked: false }
72
+ },
73
+ {
74
+ name: 'macOS Sonoma',
75
+ id: 'os-macos-sonoma',
76
+ checkProps: { checked: false }
77
+ }
78
+ ]
79
+ }
80
+ ];
81
+ const defaultProps = {
82
+ filterId: 'test-tree-filter',
83
+ title: 'Test Tree Filter',
84
+ value: ['Linux'],
85
+ items: treeItems
86
+ };
87
+ beforeEach(() => {
88
+ jest.clearAllMocks();
89
+ });
90
+ it('should render correctly', () => {
91
+ const { container } = render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTreeFilter, Object.assign({}, defaultProps)) }));
92
+ expect(container).toMatchSnapshot();
93
+ });
94
+ it('should use chipTitle for the filter chip category when provided', () => {
95
+ render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTreeFilter, Object.assign({}, defaultProps, { chipTitle: "Short name" })) }));
96
+ expect(screen.getByText('Short name')).toBeInTheDocument();
97
+ expect(screen.getByText('Test Tree Filter')).toBeInTheDocument();
98
+ });
99
+ describe('defaultExpanded', () => {
100
+ it('should have expanded items by default', () => __awaiter(void 0, void 0, void 0, function* () {
101
+ render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTreeFilter, { filterId: "os", title: "Operating System", items: treeItems, defaultExpanded: true }) }));
102
+ const openMenu = screen.getByRole('button', { name: /operating system/i });
103
+ yield userEvent.click(openMenu);
104
+ yield waitFor(() => {
105
+ const node = screen.getByText('Ubuntu 22.04');
106
+ expect(node).toHaveClass('pf-v6-c-tree-view__node-text');
107
+ expect(node).toBeInTheDocument();
108
+ });
109
+ }));
110
+ });
111
+ describe('onChange callback', () => {
112
+ it('onChange should be called on toggle of node', () => __awaiter(void 0, void 0, void 0, function* () {
113
+ const mockOnChange = jest.fn();
114
+ render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTreeFilter, { filterId: "os", title: "Operating System", items: treeItems, defaultExpanded: true, onChange: mockOnChange }) }));
115
+ const openMenu = screen.getByRole('button', { name: /operating system/i });
116
+ yield userEvent.click(openMenu);
117
+ yield waitFor(() => {
118
+ const node = screen.getByText('Ubuntu 22.04');
119
+ expect(node).toBeInTheDocument();
120
+ });
121
+ const node = screen.getByText('Ubuntu 22.04');
122
+ yield userEvent.click(node);
123
+ yield waitFor(() => {
124
+ expect(mockOnChange).toHaveBeenCalled();
125
+ });
126
+ }));
127
+ });
128
+ describe('onSelect callback', () => {
129
+ it('onSelect should return list of selected items when item is selected', () => __awaiter(void 0, void 0, void 0, function* () {
130
+ const mockOnSelect = jest.fn();
131
+ render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTreeFilter, { filterId: "os", title: "Operating System", items: treeItems, defaultExpanded: true, onSelect: mockOnSelect }) }));
132
+ const openMenu = screen.getByRole('button', { name: /operating system/i });
133
+ yield userEvent.click(openMenu);
134
+ yield waitFor(() => {
135
+ const node = screen.getByText('Ubuntu 22.04');
136
+ expect(node).toBeInTheDocument();
137
+ });
138
+ const node = screen.getByText('Ubuntu 22.04');
139
+ yield userEvent.click(node);
140
+ yield waitFor(() => {
141
+ expect(mockOnSelect).toHaveBeenCalled();
142
+ expect(mockOnSelect).toHaveBeenCalledWith(expect.arrayContaining([
143
+ expect.objectContaining({
144
+ name: 'Ubuntu 22.04',
145
+ id: 'os-ubuntu'
146
+ })
147
+ ]));
148
+ });
149
+ }));
150
+ });
151
+ describe('rendering all items', () => {
152
+ it('all tree items should be rendered', () => __awaiter(void 0, void 0, void 0, function* () {
153
+ render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTreeFilter, { filterId: "os", title: "Operating System", items: treeItems, defaultExpanded: true }) }));
154
+ const openMenu = screen.getByRole('button', { name: /operating system/i });
155
+ yield userEvent.click(openMenu);
156
+ yield waitFor(() => {
157
+ expect(screen.getByText('Linux')).toBeInTheDocument();
158
+ expect(screen.getByText('Windows')).toBeInTheDocument();
159
+ expect(screen.getByText('macOS')).toBeInTheDocument();
160
+ expect(screen.getByText('Ubuntu 22.04')).toBeInTheDocument();
161
+ expect(screen.getByText('RHEL 9')).toBeInTheDocument();
162
+ expect(screen.getByText('Debian 12')).toBeInTheDocument();
163
+ expect(screen.getByText('CentOS 8')).toBeInTheDocument();
164
+ expect(screen.getByText('Fedora 38')).toBeInTheDocument();
165
+ expect(screen.getByText('Windows Server 2022')).toBeInTheDocument();
166
+ expect(screen.getByText('macOS Ventura')).toBeInTheDocument();
167
+ expect(screen.getByText('macOS Sonoma')).toBeInTheDocument();
168
+ });
169
+ }));
170
+ });
171
+ });
@@ -0,0 +1,2 @@
1
+ export { default } from './DataViewTreeFilter';
2
+ export * from './DataViewTreeFilter';
@@ -0,0 +1,2 @@
1
+ export { default } from './DataViewTreeFilter';
2
+ export * from './DataViewTreeFilter';
@@ -1,12 +1,12 @@
1
- export interface UseDataViewSelectionProps {
1
+ export interface UseDataViewSelectionProps<T = any> {
2
2
  /** Function to compare items when checking if item is selected */
3
- matchOption: (item: any, another: any) => boolean;
3
+ matchOption: (item: T, another: T) => boolean;
4
4
  /** Array of initially selected entries */
5
- initialSelected?: (any)[];
5
+ initialSelected?: (T)[];
6
6
  }
7
- export declare const useDataViewSelection: (props?: UseDataViewSelectionProps) => {
8
- selected: any[];
9
- onSelect: (isSelecting: boolean, items?: any[] | any) => void;
10
- isSelected: (item: any) => boolean;
11
- setSelected: (items: any[]) => void;
7
+ export declare const useDataViewSelection: <T = any>(props?: UseDataViewSelectionProps<T>) => {
8
+ selected: T[];
9
+ onSelect: (isSelecting: boolean, items?: T[] | T) => void;
10
+ isSelected: (item: T) => boolean;
11
+ setSelected: (items: T[]) => void;
12
12
  };
@@ -1,8 +1,12 @@
1
1
  export { default as InternalContext } from './InternalContext';
2
2
  export * from './InternalContext';
3
3
  export * from './Hooks';
4
+ export { default as DataViewTreeFilter } from './DataViewTreeFilter';
5
+ export * from './DataViewTreeFilter';
4
6
  export { default as DataViewToolbar } from './DataViewToolbar';
5
7
  export * from './DataViewToolbar';
8
+ export { default as DataViewTh } from './DataViewTh';
9
+ export * from './DataViewTh';
6
10
  export { default as DataViewTextFilter } from './DataViewTextFilter';
7
11
  export * from './DataViewTextFilter';
8
12
  export { default as DataViewTableTree } from './DataViewTableTree';
@@ -13,6 +17,8 @@ export { default as DataViewTableBasic } from './DataViewTableBasic';
13
17
  export * from './DataViewTableBasic';
14
18
  export { default as DataViewTable } from './DataViewTable';
15
19
  export * from './DataViewTable';
20
+ export { default as DataViewFilters } from './DataViewFilters';
21
+ export * from './DataViewFilters';
16
22
  export { default as DataViewEventsContext } from './DataViewEventsContext';
17
23
  export * from './DataViewEventsContext';
18
24
  export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter';
package/dist/esm/index.js CHANGED
@@ -2,8 +2,12 @@
2
2
  export { default as InternalContext } from './InternalContext';
3
3
  export * from './InternalContext';
4
4
  export * from './Hooks';
5
+ export { default as DataViewTreeFilter } from './DataViewTreeFilter';
6
+ export * from './DataViewTreeFilter';
5
7
  export { default as DataViewToolbar } from './DataViewToolbar';
6
8
  export * from './DataViewToolbar';
9
+ export { default as DataViewTh } from './DataViewTh';
10
+ export * from './DataViewTh';
7
11
  export { default as DataViewTextFilter } from './DataViewTextFilter';
8
12
  export * from './DataViewTextFilter';
9
13
  export { default as DataViewTableTree } from './DataViewTableTree';
@@ -14,6 +18,8 @@ export { default as DataViewTableBasic } from './DataViewTableBasic';
14
18
  export * from './DataViewTableBasic';
15
19
  export { default as DataViewTable } from './DataViewTable';
16
20
  export * from './DataViewTable';
21
+ export { default as DataViewFilters } from './DataViewFilters';
22
+ export * from './DataViewFilters';
17
23
  export { default as DataViewEventsContext } from './DataViewEventsContext';
18
24
  export * from './DataViewEventsContext';
19
25
  export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter';
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/DataView/DataView.test.tsx","../src/DataView/DataView.tsx","../src/DataView/index.ts","../src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx","../src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx","../src/DataViewCheckboxFilter/index.ts","../src/DataViewEventsContext/DataViewEventsContext.test.tsx","../src/DataViewEventsContext/DataViewEventsContext.tsx","../src/DataViewEventsContext/index.ts","../src/DataViewFilters/DataViewFilters.test.tsx","../src/DataViewFilters/DataViewFilters.tsx","../src/DataViewFilters/index.tsx","../src/DataViewTable/DataViewTable.test.tsx","../src/DataViewTable/DataViewTable.tsx","../src/DataViewTable/index.ts","../src/DataViewTableBasic/DataViewTableBasic.test.tsx","../src/DataViewTableBasic/DataViewTableBasic.tsx","../src/DataViewTableBasic/index.ts","../src/DataViewTableHead/DataViewTableHead.test.tsx","../src/DataViewTableHead/DataViewTableHead.tsx","../src/DataViewTableHead/index.ts","../src/DataViewTableTree/DataViewTableTree.test.tsx","../src/DataViewTableTree/DataViewTableTree.tsx","../src/DataViewTableTree/index.ts","../src/DataViewTextFilter/DataViewTextFilter.test.tsx","../src/DataViewTextFilter/DataViewTextFilter.tsx","../src/DataViewTextFilter/index.ts","../src/DataViewTh/DataViewTh.tsx","../src/DataViewToolbar/DataViewToolbar.test.tsx","../src/DataViewToolbar/DataViewToolbar.tsx","../src/DataViewToolbar/index.ts","../src/Hooks/filters.test.tsx","../src/Hooks/filters.ts","../src/Hooks/index.ts","../src/Hooks/pagination.test.tsx","../src/Hooks/pagination.ts","../src/Hooks/selection.test.tsx","../src/Hooks/selection.ts","../src/Hooks/sort.test.tsx","../src/Hooks/sort.ts","../src/InternalContext/InternalContext.test.tsx","../src/InternalContext/InternalContext.tsx","../src/InternalContext/index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/index.ts","../src/DataView/DataView.test.tsx","../src/DataView/DataView.tsx","../src/DataView/index.ts","../src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx","../src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx","../src/DataViewCheckboxFilter/index.ts","../src/DataViewEventsContext/DataViewEventsContext.test.tsx","../src/DataViewEventsContext/DataViewEventsContext.tsx","../src/DataViewEventsContext/index.ts","../src/DataViewFilters/DataViewFilters.test.tsx","../src/DataViewFilters/DataViewFilters.tsx","../src/DataViewFilters/index.tsx","../src/DataViewTable/DataViewTable.test.tsx","../src/DataViewTable/DataViewTable.tsx","../src/DataViewTable/index.ts","../src/DataViewTableBasic/DataViewTableBasic.test.tsx","../src/DataViewTableBasic/DataViewTableBasic.tsx","../src/DataViewTableBasic/index.ts","../src/DataViewTableHead/DataViewTableHead.test.tsx","../src/DataViewTableHead/DataViewTableHead.tsx","../src/DataViewTableHead/index.ts","../src/DataViewTableTree/DataViewTableTree.test.tsx","../src/DataViewTableTree/DataViewTableTree.tsx","../src/DataViewTableTree/index.ts","../src/DataViewTextFilter/DataViewTextFilter.test.tsx","../src/DataViewTextFilter/DataViewTextFilter.tsx","../src/DataViewTextFilter/index.ts","../src/DataViewTh/DataViewTh.tsx","../src/DataViewTh/index.ts","../src/DataViewToolbar/DataViewToolbar.test.tsx","../src/DataViewToolbar/DataViewToolbar.tsx","../src/DataViewToolbar/index.ts","../src/DataViewTreeFilter/DataViewTreeFilter.test.tsx","../src/DataViewTreeFilter/DataViewTreeFilter.tsx","../src/DataViewTreeFilter/index.ts","../src/Hooks/filters.test.tsx","../src/Hooks/filters.ts","../src/Hooks/index.ts","../src/Hooks/pagination.test.tsx","../src/Hooks/pagination.ts","../src/Hooks/selection.test.tsx","../src/Hooks/selection.ts","../src/Hooks/sort.test.tsx","../src/Hooks/sort.ts","../src/InternalContext/InternalContext.test.tsx","../src/InternalContext/InternalContext.tsx","../src/InternalContext/index.ts"],"version":"5.9.3"}
@@ -1,6 +1,7 @@
1
1
  const fse = require('fs-extra');
2
2
  const { globSync } = require('glob');
3
3
  const path = require('path');
4
+ const { default: getDynamicModuleMap } = require('../../scripts/parse-dynamic-modules.mjs');
4
5
 
5
6
  const root = process.cwd();
6
7
 
@@ -60,12 +61,29 @@ async function generatePackages(files) {
60
61
  return Promise.all(cmds);
61
62
  }
62
63
 
64
+ async function generateDynamicModuleMap() {
65
+ const moduleMap = getDynamicModuleMap(root);
66
+ // eslint-disable-next-line no-console
67
+ console.log('Generating dynamic module map for', Object.keys(moduleMap).length, 'modules');
68
+
69
+ if (Object.keys(moduleMap).length === 0) {
70
+ return Promise.resolve();
71
+ }
72
+
73
+ const moduleMapSorted = Object.keys(moduleMap)
74
+ .sort()
75
+ .reduce((acc, key) => ({ ...acc, [key]: moduleMap[key] }), {});
76
+
77
+ return fse.writeJSON(path.resolve(root, 'dist/dynamic-modules.json'), moduleMapSorted, { spaces: 2 });
78
+ }
79
+
63
80
  async function run(files) {
64
81
  try {
65
82
  await generatePackages(files);
66
83
  if (indexTypings.length === 1) {
67
84
  copyTypings(indexTypings, root);
68
85
  }
86
+ await generateDynamicModuleMap();
69
87
  } catch (error) {
70
88
  // eslint-disable-next-line no-console
71
89
  console.error(error);
package/generate-index.js CHANGED
@@ -6,7 +6,7 @@ const root = process.cwd();
6
6
 
7
7
  const ENV_AGNOSTIC_ROOT = `${root}/src`
8
8
 
9
- const sourceFiles = globSync(path.resolve(__dirname, './src/*/index.ts'))
9
+ const sourceFiles = globSync(path.resolve(__dirname, './src/*/index.{ts,tsx}'))
10
10
 
11
11
  async function generateIndex(files) {
12
12
  // ensure the dynamic root exists
@@ -19,7 +19,7 @@ async function generateIndex(files) {
19
19
  stream.write('// this file is autogenerated by generate-index.js, modifying it manually will have no effect\n');
20
20
 
21
21
  files.forEach(file => {
22
- const name = file.replace('/index.ts', '').split('/').pop();
22
+ const name = file.replace(/\/index\.(ts|tsx)$/, '').split('/').pop();
23
23
  // do not generate default exports for Hooks/
24
24
  name !== 'Hooks' && stream.write(`\nexport { default as ${name} } from './${name}';\n`);
25
25
  stream.write(`export * from './${name}';\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-data-view",
3
- "version": "6.4.0",
3
+ "version": "6.5.0-prerelease.2",
4
4
  "description": "Data view used for Red Hat projects.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -47,14 +47,14 @@
47
47
  "@patternfly/patternfly": "^6.4.0",
48
48
  "@patternfly/react-code-editor": "^6.4.0",
49
49
  "@patternfly/patternfly-a11y": "^5.1.0",
50
- "@types/react": "^18.3.23",
50
+ "@types/react": "^18.3.27",
51
51
  "@types/react-dom": "^18.3.7",
52
52
  "@types/react-router-dom": "^5.3.3",
53
53
  "react": "^18.3.1",
54
54
  "react-dom": "^18.3.1",
55
- "react-router": "^6.30.1",
56
- "react-router-dom": "^6.30.1",
57
- "rimraf": "^6.0.1",
58
- "typescript": "^5.9.2"
55
+ "react-router": "^6.30.2",
56
+ "react-router-dom": "^6.30.2",
57
+ "rimraf": "^6.1.2",
58
+ "typescript": "^5.9.3"
59
59
  }
60
60
  }
@@ -22,15 +22,17 @@ import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic
22
22
 
23
23
  **Note:** Data view lives in its own package [`@patternfly/react-data-view`](https://www.npmjs.com/package/@patternfly/react-data-view)
24
24
 
25
- If you notice a bug, or if you have a suggestion for improving the data view extension or its documentation, please file an issue in the [react-data-view](https://github.com/patternfly/react-data-view/issues) repository. Before doing so, please make sure there is not already a pre-existing issue.
25
+ The **data view** extension enables you to display datasets in organized layouts, with data representations and interactive toolbars for actions like selection and pagination.
26
26
 
27
- ---
27
+ ---
28
28
 
29
- The **data view** extension enables you to display datasets in organized layouts, with data representations and interactive toolbars for actions like selection and pagination.
29
+ ## How to structure and implement the data view
30
+
31
+ The **data view** extension provides a modular architecture that lets you efficiently create consistent data views, either by using predefined sub-components and hooks or by defining your own custom implementations.
30
32
 
31
33
  ### Layout
32
34
 
33
- A data view should contain a header, the data representation, and a footer. These parts are organized in a [stack layout](/layouts/stack).
35
+ A data view should contain a header, the data representation, and a footer. These parts are organized in a [stack layout](/foundations-and-styles/layouts/stack).
34
36
 
35
37
  The data view toolbars and sub-components that display the data (like a card view or table) are always passed as `children` to the `<DataView>` component.
36
38
 
@@ -61,3 +63,7 @@ This example uses the `<DataViewEventsProvider>` to display details about a sele
61
63
  ```js file="./EventsExample.tsx"
62
64
 
63
65
  ```
66
+
67
+ ---
68
+
69
+ If you notice a bug, or if you have a suggestion for improving the data view extension or its documentation, please file an issue in the [react-data-view](https://github.com/patternfly/react-data-view/issues) repository. Before doing so, please make sure there is not already a pre-existing issue.
@@ -244,7 +244,8 @@ const RepositoriesTable: FunctionComponent<RepositoriesTableProps> = ({ selected
244
244
  ouiaId='LayoutExampleFooter'
245
245
  pagination={
246
246
  <Pagination
247
- isCompact
247
+ isCompact
248
+ variant="bottom"
248
249
  perPageOptions={perPageOptions}
249
250
  itemCount={repositories.length}
250
251
  {...pagination}