@patternfly/react-data-view 6.3.0-prerelease.3 → 6.4.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.
- 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/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/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/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/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableTreeExample.tsx +1 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +2 -0
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/SelectionExample.tsx +14 -3
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +1 -0
- package/release.config.js +1 -1
- package/src/DataViewTableTree/DataViewTableTree.test.tsx +9 -0
- package/src/DataViewTableTree/DataViewTableTree.tsx +35 -1
- package/src/DataViewTableTree/__snapshots__/DataViewTableTree.test.tsx.snap +965 -0
- package/src/Hooks/selection.test.tsx +65 -1
- package/src/Hooks/selection.ts +6 -1
- package/src/InternalContext/InternalContext.tsx +2 -0
|
@@ -18,6 +18,8 @@ export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'r
|
|
|
18
18
|
expandedIcon?: React.ReactNode;
|
|
19
19
|
/** Optional icon for the collapsed parent rows */
|
|
20
20
|
collapsedIcon?: React.ReactNode;
|
|
21
|
+
/** Expand all expandable nodes on initial load */
|
|
22
|
+
expandAll?: boolean;
|
|
21
23
|
/** Custom OUIA ID */
|
|
22
24
|
ouiaId?: string;
|
|
23
25
|
}
|
|
@@ -45,11 +45,38 @@ const getNodesAffectedBySelection = (allRows, node, isChecking, isSelected) => {
|
|
|
45
45
|
return Array.from(affectedNodes);
|
|
46
46
|
};
|
|
47
47
|
const DataViewTableTree = (_a) => {
|
|
48
|
-
var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "ouiaId"]);
|
|
48
|
+
var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, expandAll = false, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "expandAll", "ouiaId"]);
|
|
49
49
|
const { selection, activeState } = (0, InternalContext_1.useInternalContext)();
|
|
50
50
|
const { onSelect, isSelected, isSelectDisabled } = selection !== null && selection !== void 0 ? selection : {};
|
|
51
51
|
const [expandedNodeIds, setExpandedNodeIds] = (0, react_1.useState)([]);
|
|
52
52
|
const [expandedDetailsNodeNames, setExpandedDetailsNodeIds] = (0, react_1.useState)([]);
|
|
53
|
+
// Helper function to collect all node IDs that have children (are expandable)
|
|
54
|
+
const getExpandableNodeIds = (nodes) => {
|
|
55
|
+
const expandableIds = [];
|
|
56
|
+
const traverse = (nodeList) => {
|
|
57
|
+
nodeList.forEach(node => {
|
|
58
|
+
if (node.children && node.children.length > 0) {
|
|
59
|
+
expandableIds.push(node.id);
|
|
60
|
+
traverse(node.children);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
traverse(nodes);
|
|
65
|
+
return expandableIds;
|
|
66
|
+
};
|
|
67
|
+
// Effect to handle expandAll behavior
|
|
68
|
+
// Memoize the expandable IDs to avoid recalculating when rows object reference changes but structure is the same
|
|
69
|
+
const expandableIds = (0, react_1.useMemo)(() => getExpandableNodeIds(rows), [rows]);
|
|
70
|
+
// Effect to handle expandAll behavior - only runs when IDs actually change
|
|
71
|
+
(0, react_1.useEffect)(() => {
|
|
72
|
+
if (expandAll) {
|
|
73
|
+
setExpandedNodeIds(expandableIds);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
setExpandedNodeIds([]);
|
|
77
|
+
}
|
|
78
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
79
|
+
}, [expandAll, expandableIds.join(',')]);
|
|
53
80
|
const activeHeadState = (0, react_1.useMemo)(() => activeState ? headStates === null || headStates === void 0 ? void 0 : headStates[activeState] : undefined, [activeState, headStates]);
|
|
54
81
|
const activeBodyState = (0, react_1.useMemo)(() => activeState ? bodyStates === null || bodyStates === void 0 ? void 0 : bodyStates[activeState] : undefined, [activeState, bodyStates]);
|
|
55
82
|
const nodes = (0, react_1.useMemo)(() => {
|
|
@@ -76,6 +76,10 @@ describe('DataViewTableTree component', () => {
|
|
|
76
76
|
const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(DataView_1.DataView, { activeState: "error", children: (0, jsx_runtime_1.jsx)(DataViewTable_1.DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { error: "Some error" }, rows: [] }) }));
|
|
77
77
|
expect(container).toMatchSnapshot();
|
|
78
78
|
});
|
|
79
|
+
test('should render tree table with all expandable nodes expanded', () => {
|
|
80
|
+
const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(DataView_1.DataView, { selection: mockSelection, children: (0, jsx_runtime_1.jsx)(DataViewTable_1.DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, expandAll: true, rows: rows, leafIcon: (0, jsx_runtime_1.jsx)(react_icons_1.LeafIcon, {}), expandedIcon: (0, jsx_runtime_1.jsx)(react_icons_1.FolderOpenIcon, { "aria-hidden": true }), collapsedIcon: (0, jsx_runtime_1.jsx)(react_icons_1.FolderIcon, { "aria-hidden": true }) }) }));
|
|
81
|
+
expect(container).toMatchSnapshot();
|
|
82
|
+
});
|
|
79
83
|
test('should render tree table with a loading state', () => {
|
|
80
84
|
const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(DataView_1.DataView, { activeState: "loading", children: (0, jsx_runtime_1.jsx)(DataViewTable_1.DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { loading: "Data is loading" }, rows: [] }) }));
|
|
81
85
|
expect(container).toMatchSnapshot();
|
|
@@ -17,10 +17,14 @@ const useDataViewSelection = (props) => {
|
|
|
17
17
|
: setSelected(items ? prev => prev.filter(prevSelected => !(Array.isArray(items) ? items : [items]).some(item => matchOption(item, prevSelected))) : []);
|
|
18
18
|
};
|
|
19
19
|
const isSelected = (item) => Boolean(selected.find(selected => matchOption(selected, item)));
|
|
20
|
+
const setSelectedItems = (items) => {
|
|
21
|
+
setSelected(items);
|
|
22
|
+
};
|
|
20
23
|
return {
|
|
21
24
|
selected,
|
|
22
25
|
onSelect,
|
|
23
|
-
isSelected
|
|
26
|
+
isSelected,
|
|
27
|
+
setSelected: setSelectedItems
|
|
24
28
|
};
|
|
25
29
|
};
|
|
26
30
|
exports.useDataViewSelection = useDataViewSelection;
|
|
@@ -19,6 +19,7 @@ describe('useDataViewSelection', () => {
|
|
|
19
19
|
selected: [],
|
|
20
20
|
onSelect: expect.any(Function),
|
|
21
21
|
isSelected: expect.any(Function),
|
|
22
|
+
setSelected: expect.any(Function),
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
25
|
it('should get initial state correctly - with initialSelected', () => {
|
|
@@ -28,6 +29,7 @@ describe('useDataViewSelection', () => {
|
|
|
28
29
|
selected: initialSelected,
|
|
29
30
|
onSelect: expect.any(Function),
|
|
30
31
|
isSelected: expect.any(Function),
|
|
32
|
+
setSelected: expect.any(Function),
|
|
31
33
|
});
|
|
32
34
|
});
|
|
33
35
|
it('should select items correctly - objects', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -52,4 +54,50 @@ describe('useDataViewSelection', () => {
|
|
|
52
54
|
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(true);
|
|
53
55
|
expect(result.current.isSelected({ id: 3, name: 'test2' })).toBe(false);
|
|
54
56
|
});
|
|
57
|
+
it('should have setSelected function in return object', () => {
|
|
58
|
+
const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ matchOption: (a, b) => a.id === b.id }));
|
|
59
|
+
expect(result.current).toEqual({
|
|
60
|
+
selected: [],
|
|
61
|
+
onSelect: expect.any(Function),
|
|
62
|
+
isSelected: expect.any(Function),
|
|
63
|
+
setSelected: expect.any(Function),
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
it('should set selected items directly using setSelected - objects', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
const initialSelected = [{ id: 1, name: 'test1' }];
|
|
68
|
+
const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a.id === b.id }));
|
|
69
|
+
const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
|
|
70
|
+
yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
71
|
+
result.current.setSelected(newSelected);
|
|
72
|
+
}));
|
|
73
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
74
|
+
}));
|
|
75
|
+
it('should set selected items directly using setSelected - strings', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
|
+
const initialSelected = ['test1', 'test2'];
|
|
77
|
+
const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a === b }));
|
|
78
|
+
const newSelected = ['test3', 'test4', 'test5'];
|
|
79
|
+
yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
80
|
+
result.current.setSelected(newSelected);
|
|
81
|
+
}));
|
|
82
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
83
|
+
}));
|
|
84
|
+
it('should clear all selections using setSelected with empty array', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
85
|
+
const initialSelected = [{ id: 1, name: 'test1' }, { id: 2, name: 'test2' }];
|
|
86
|
+
const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a.id === b.id }));
|
|
87
|
+
yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
result.current.setSelected([]);
|
|
89
|
+
}));
|
|
90
|
+
expect(result.current.selected).toEqual([]);
|
|
91
|
+
}));
|
|
92
|
+
it('should update isSelected correctly after using setSelected', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
const initialSelected = [{ id: 1, name: 'test1' }];
|
|
94
|
+
const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a.id === b.id }));
|
|
95
|
+
const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
|
|
96
|
+
yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
+
result.current.setSelected(newSelected);
|
|
98
|
+
}));
|
|
99
|
+
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(false);
|
|
100
|
+
expect(result.current.isSelected({ id: 2, name: 'test2' })).toBe(true);
|
|
101
|
+
expect(result.current.isSelected({ id: 3, name: 'test3' })).toBe(true);
|
|
102
|
+
}));
|
|
55
103
|
});
|
|
@@ -5,6 +5,8 @@ export interface DataViewSelection {
|
|
|
5
5
|
onSelect: (isSelecting: boolean, items?: any[] | any) => void;
|
|
6
6
|
/** Checks if a specific item is currently selected */
|
|
7
7
|
isSelected: (item: any) => boolean;
|
|
8
|
+
/** Directly sets the selected items */
|
|
9
|
+
setSelected?: (items: any[]) => void;
|
|
8
10
|
/** Determines if selection is disabled for a given item */
|
|
9
11
|
isSelectDisabled?: (item: any) => boolean;
|
|
10
12
|
}
|
|
@@ -18,6 +18,8 @@ export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'r
|
|
|
18
18
|
expandedIcon?: React.ReactNode;
|
|
19
19
|
/** Optional icon for the collapsed parent rows */
|
|
20
20
|
collapsedIcon?: React.ReactNode;
|
|
21
|
+
/** Expand all expandable nodes on initial load */
|
|
22
|
+
expandAll?: boolean;
|
|
21
23
|
/** Custom OUIA ID */
|
|
22
24
|
ouiaId?: string;
|
|
23
25
|
}
|
|
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
-
import { useState, useMemo } from 'react';
|
|
13
|
+
import { useState, useMemo, useEffect } from 'react';
|
|
14
14
|
import { Table, Tbody, Td, TreeRowWrapper, } from '@patternfly/react-table';
|
|
15
15
|
import { useInternalContext } from '../InternalContext';
|
|
16
16
|
import { DataViewTableHead } from '../DataViewTableHead';
|
|
@@ -42,11 +42,38 @@ const getNodesAffectedBySelection = (allRows, node, isChecking, isSelected) => {
|
|
|
42
42
|
return Array.from(affectedNodes);
|
|
43
43
|
};
|
|
44
44
|
export const DataViewTableTree = (_a) => {
|
|
45
|
-
var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "ouiaId"]);
|
|
45
|
+
var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, expandAll = false, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "expandAll", "ouiaId"]);
|
|
46
46
|
const { selection, activeState } = useInternalContext();
|
|
47
47
|
const { onSelect, isSelected, isSelectDisabled } = selection !== null && selection !== void 0 ? selection : {};
|
|
48
48
|
const [expandedNodeIds, setExpandedNodeIds] = useState([]);
|
|
49
49
|
const [expandedDetailsNodeNames, setExpandedDetailsNodeIds] = useState([]);
|
|
50
|
+
// Helper function to collect all node IDs that have children (are expandable)
|
|
51
|
+
const getExpandableNodeIds = (nodes) => {
|
|
52
|
+
const expandableIds = [];
|
|
53
|
+
const traverse = (nodeList) => {
|
|
54
|
+
nodeList.forEach(node => {
|
|
55
|
+
if (node.children && node.children.length > 0) {
|
|
56
|
+
expandableIds.push(node.id);
|
|
57
|
+
traverse(node.children);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
traverse(nodes);
|
|
62
|
+
return expandableIds;
|
|
63
|
+
};
|
|
64
|
+
// Effect to handle expandAll behavior
|
|
65
|
+
// Memoize the expandable IDs to avoid recalculating when rows object reference changes but structure is the same
|
|
66
|
+
const expandableIds = useMemo(() => getExpandableNodeIds(rows), [rows]);
|
|
67
|
+
// Effect to handle expandAll behavior - only runs when IDs actually change
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (expandAll) {
|
|
70
|
+
setExpandedNodeIds(expandableIds);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
setExpandedNodeIds([]);
|
|
74
|
+
}
|
|
75
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
76
|
+
}, [expandAll, expandableIds.join(',')]);
|
|
50
77
|
const activeHeadState = useMemo(() => activeState ? headStates === null || headStates === void 0 ? void 0 : headStates[activeState] : undefined, [activeState, headStates]);
|
|
51
78
|
const activeBodyState = useMemo(() => activeState ? bodyStates === null || bodyStates === void 0 ? void 0 : bodyStates[activeState] : undefined, [activeState, bodyStates]);
|
|
52
79
|
const nodes = useMemo(() => {
|
|
@@ -74,6 +74,10 @@ describe('DataViewTableTree component', () => {
|
|
|
74
74
|
const { container } = render(_jsx(DataView, { activeState: "error", children: _jsx(DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { error: "Some error" }, rows: [] }) }));
|
|
75
75
|
expect(container).toMatchSnapshot();
|
|
76
76
|
});
|
|
77
|
+
test('should render tree table with all expandable nodes expanded', () => {
|
|
78
|
+
const { container } = render(_jsx(DataView, { selection: mockSelection, children: _jsx(DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, expandAll: true, rows: rows, leafIcon: _jsx(LeafIcon, {}), expandedIcon: _jsx(FolderOpenIcon, { "aria-hidden": true }), collapsedIcon: _jsx(FolderIcon, { "aria-hidden": true }) }) }));
|
|
79
|
+
expect(container).toMatchSnapshot();
|
|
80
|
+
});
|
|
77
81
|
test('should render tree table with a loading state', () => {
|
|
78
82
|
const { container } = render(_jsx(DataView, { activeState: "loading", children: _jsx(DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { loading: "Data is loading" }, rows: [] }) }));
|
|
79
83
|
expect(container).toMatchSnapshot();
|
|
@@ -14,9 +14,13 @@ export const useDataViewSelection = (props) => {
|
|
|
14
14
|
: setSelected(items ? prev => prev.filter(prevSelected => !(Array.isArray(items) ? items : [items]).some(item => matchOption(item, prevSelected))) : []);
|
|
15
15
|
};
|
|
16
16
|
const isSelected = (item) => Boolean(selected.find(selected => matchOption(selected, item)));
|
|
17
|
+
const setSelectedItems = (items) => {
|
|
18
|
+
setSelected(items);
|
|
19
|
+
};
|
|
17
20
|
return {
|
|
18
21
|
selected,
|
|
19
22
|
onSelect,
|
|
20
|
-
isSelected
|
|
23
|
+
isSelected,
|
|
24
|
+
setSelected: setSelectedItems
|
|
21
25
|
};
|
|
22
26
|
};
|
|
@@ -17,6 +17,7 @@ describe('useDataViewSelection', () => {
|
|
|
17
17
|
selected: [],
|
|
18
18
|
onSelect: expect.any(Function),
|
|
19
19
|
isSelected: expect.any(Function),
|
|
20
|
+
setSelected: expect.any(Function),
|
|
20
21
|
});
|
|
21
22
|
});
|
|
22
23
|
it('should get initial state correctly - with initialSelected', () => {
|
|
@@ -26,6 +27,7 @@ describe('useDataViewSelection', () => {
|
|
|
26
27
|
selected: initialSelected,
|
|
27
28
|
onSelect: expect.any(Function),
|
|
28
29
|
isSelected: expect.any(Function),
|
|
30
|
+
setSelected: expect.any(Function),
|
|
29
31
|
});
|
|
30
32
|
});
|
|
31
33
|
it('should select items correctly - objects', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -50,4 +52,50 @@ describe('useDataViewSelection', () => {
|
|
|
50
52
|
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(true);
|
|
51
53
|
expect(result.current.isSelected({ id: 3, name: 'test2' })).toBe(false);
|
|
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
|
+
it('should set selected items directly using setSelected - objects', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
const initialSelected = [{ id: 1, name: 'test1' }];
|
|
66
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }));
|
|
67
|
+
const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
|
|
68
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
|
+
result.current.setSelected(newSelected);
|
|
70
|
+
}));
|
|
71
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
72
|
+
}));
|
|
73
|
+
it('should set selected items directly using setSelected - strings', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
|
+
const initialSelected = ['test1', 'test2'];
|
|
75
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a === b }));
|
|
76
|
+
const newSelected = ['test3', 'test4', 'test5'];
|
|
77
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
+
result.current.setSelected(newSelected);
|
|
79
|
+
}));
|
|
80
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
81
|
+
}));
|
|
82
|
+
it('should clear all selections using setSelected with empty array', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
+
const initialSelected = [{ id: 1, name: 'test1' }, { id: 2, name: 'test2' }];
|
|
84
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }));
|
|
85
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
86
|
+
result.current.setSelected([]);
|
|
87
|
+
}));
|
|
88
|
+
expect(result.current.selected).toEqual([]);
|
|
89
|
+
}));
|
|
90
|
+
it('should update isSelected correctly after using setSelected', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
const initialSelected = [{ id: 1, name: 'test1' }];
|
|
92
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }));
|
|
93
|
+
const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
|
|
94
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
+
result.current.setSelected(newSelected);
|
|
96
|
+
}));
|
|
97
|
+
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(false);
|
|
98
|
+
expect(result.current.isSelected({ id: 2, name: 'test2' })).toBe(true);
|
|
99
|
+
expect(result.current.isSelected({ id: 3, name: 'test3' })).toBe(true);
|
|
100
|
+
}));
|
|
53
101
|
});
|
|
@@ -5,6 +5,8 @@ export interface DataViewSelection {
|
|
|
5
5
|
onSelect: (isSelecting: boolean, items?: any[] | any) => void;
|
|
6
6
|
/** Checks if a specific item is currently selected */
|
|
7
7
|
isSelected: (item: any) => boolean;
|
|
8
|
+
/** Directly sets the selected items */
|
|
9
|
+
setSelected?: (items: any[]) => void;
|
|
8
10
|
/** Determines if selection is disabled for a given item */
|
|
9
11
|
isSelectDisabled?: (item: any) => boolean;
|
|
10
12
|
}
|
|
@@ -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/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.
|
|
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/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.2"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/react-data-view",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.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",
|
|
@@ -55,6 +55,6 @@
|
|
|
55
55
|
"react-router": "^6.30.1",
|
|
56
56
|
"react-router-dom": "^6.30.1",
|
|
57
57
|
"rimraf": "^6.0.1",
|
|
58
|
-
"typescript": "^5.
|
|
58
|
+
"typescript": "^5.9.2"
|
|
59
59
|
}
|
|
60
60
|
}
|
package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableTreeExample.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { DataViewTable, DataViewTh, DataViewTrTree } from '@patternfly/react-dat
|
|
|
4
4
|
import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
5
5
|
import { FolderIcon, FolderOpenIcon, LeafIcon } from '@patternfly/react-icons';
|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
interface Repository {
|
|
8
9
|
name: string;
|
|
9
10
|
branches: string | null;
|
|
@@ -39,6 +39,8 @@ To define rows and columns for your table, use these props:
|
|
|
39
39
|
|
|
40
40
|
It is also possible to disable row selection using the `isSelectDisabled` function, which can be passed to the wrapping `DataView` component through the `selection` prop.
|
|
41
41
|
|
|
42
|
+
If you want to have all expandable nodes open on initial load pass the `expandAll` prop to the DataViewTable component
|
|
43
|
+
|
|
42
44
|
### Table example
|
|
43
45
|
```js file="./DataViewTableExample.tsx"
|
|
44
46
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
3
3
|
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
|
|
4
|
+
import { Button } from '@patternfly/react-core';
|
|
4
5
|
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
|
|
5
6
|
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
|
|
6
7
|
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
@@ -30,13 +31,18 @@ const ouiaId = 'LayoutExample';
|
|
|
30
31
|
|
|
31
32
|
export const BasicExample: React.FunctionComponent = () => {
|
|
32
33
|
const selection = useDataViewSelection({ matchOption: (a, b) => a[0] === b[0] });
|
|
33
|
-
const { selected, onSelect } = selection;
|
|
34
|
+
const { selected, onSelect, setSelected } = selection;
|
|
34
35
|
|
|
35
36
|
const handleBulkSelect = (value: BulkSelectValue) => {
|
|
36
37
|
value === BulkSelectValue.none && onSelect(false);
|
|
37
38
|
value === BulkSelectValue.all && onSelect(true, rows);
|
|
38
39
|
};
|
|
39
40
|
|
|
41
|
+
// Example: Select first two rows programmatically using setSelected
|
|
42
|
+
const handleSelectFirstTwo = () => {
|
|
43
|
+
setSelected(rows.slice(0, 2));
|
|
44
|
+
};
|
|
45
|
+
|
|
40
46
|
return (
|
|
41
47
|
<DataView selection={selection}>
|
|
42
48
|
<DataViewToolbar
|
|
@@ -49,9 +55,14 @@ export const BasicExample: React.FunctionComponent = () => {
|
|
|
49
55
|
selectedCount={selected.length}
|
|
50
56
|
onSelect={handleBulkSelect}
|
|
51
57
|
/>
|
|
52
|
-
}
|
|
58
|
+
}
|
|
59
|
+
actions={
|
|
60
|
+
<Button variant="secondary" onClick={handleSelectFirstTwo}>
|
|
61
|
+
Select First Two
|
|
62
|
+
</Button>
|
|
63
|
+
}
|
|
53
64
|
/>
|
|
54
65
|
<DataViewTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} rows={rows} />
|
|
55
66
|
</DataView>
|
|
56
67
|
);
|
|
57
|
-
}
|
|
68
|
+
}
|
|
@@ -105,6 +105,7 @@ The `useDataViewSelection` hook manages the selection state of the data view.
|
|
|
105
105
|
- `selected` array of currently selected records.
|
|
106
106
|
- `isSelected` function returning the selection state for the record.
|
|
107
107
|
- `onSelect` callback to modify the selection state. This accepts the `isSelecting` flag (indicates if records are being selected or deselected) and `items` (affected records).
|
|
108
|
+
- `setSelected` function to directly set the selected items array. This is useful for programmatically setting a specific selection state without needing to use the `onSelect` callback.
|
|
108
109
|
|
|
109
110
|
### Selection example
|
|
110
111
|
|
package/release.config.js
CHANGED
|
@@ -101,6 +101,15 @@ describe('DataViewTableTree component', () => {
|
|
|
101
101
|
expect(container).toMatchSnapshot();
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test('should render tree table with all expandable nodes expanded', () => {
|
|
105
|
+
const { container } = render(
|
|
106
|
+
<DataView selection={mockSelection}>
|
|
107
|
+
<DataViewTable isTreeTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} expandAll rows={rows} leafIcon={<LeafIcon/>} expandedIcon={<FolderOpenIcon aria-hidden />} collapsedIcon={<FolderIcon aria-hidden />} />
|
|
108
|
+
</DataView>
|
|
109
|
+
);
|
|
110
|
+
expect(container).toMatchSnapshot();
|
|
111
|
+
});
|
|
112
|
+
|
|
104
113
|
test('should render tree table with a loading state', () => {
|
|
105
114
|
const { container } = render(
|
|
106
115
|
<DataView activeState="loading">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FC, useState, useMemo, ReactNode } from 'react';
|
|
1
|
+
import { FC, useState, useMemo, useEffect, ReactNode } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Table,
|
|
4
4
|
TableProps,
|
|
@@ -67,6 +67,8 @@ export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'r
|
|
|
67
67
|
expandedIcon?: React.ReactNode;
|
|
68
68
|
/** Optional icon for the collapsed parent rows */
|
|
69
69
|
collapsedIcon?: React.ReactNode;
|
|
70
|
+
/** Expand all expandable nodes on initial load */
|
|
71
|
+
expandAll?: boolean;
|
|
70
72
|
/** Custom OUIA ID */
|
|
71
73
|
ouiaId?: string;
|
|
72
74
|
}
|
|
@@ -79,6 +81,7 @@ export const DataViewTableTree: FC<DataViewTableTreeProps> = ({
|
|
|
79
81
|
leafIcon = null,
|
|
80
82
|
expandedIcon = null,
|
|
81
83
|
collapsedIcon = null,
|
|
84
|
+
expandAll = false,
|
|
82
85
|
ouiaId = 'DataViewTableTree',
|
|
83
86
|
...props
|
|
84
87
|
}: DataViewTableTreeProps) => {
|
|
@@ -87,6 +90,37 @@ export const DataViewTableTree: FC<DataViewTableTreeProps> = ({
|
|
|
87
90
|
const [ expandedNodeIds, setExpandedNodeIds ] = useState<string[]>([]);
|
|
88
91
|
const [ expandedDetailsNodeNames, setExpandedDetailsNodeIds ] = useState<string[]>([]);
|
|
89
92
|
|
|
93
|
+
// Helper function to collect all node IDs that have children (are expandable)
|
|
94
|
+
const getExpandableNodeIds = (nodes: DataViewTrTree[]): string[] => {
|
|
95
|
+
const expandableIds: string[] = [];
|
|
96
|
+
|
|
97
|
+
const traverse = (nodeList: DataViewTrTree[]) => {
|
|
98
|
+
nodeList.forEach(node => {
|
|
99
|
+
if (node.children && node.children.length > 0) {
|
|
100
|
+
expandableIds.push(node.id);
|
|
101
|
+
traverse(node.children);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
traverse(nodes);
|
|
107
|
+
return expandableIds;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Effect to handle expandAll behavior
|
|
111
|
+
// Memoize the expandable IDs to avoid recalculating when rows object reference changes but structure is the same
|
|
112
|
+
const expandableIds = useMemo(() => getExpandableNodeIds(rows), [ rows ]);
|
|
113
|
+
|
|
114
|
+
// Effect to handle expandAll behavior - only runs when IDs actually change
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (expandAll) {
|
|
117
|
+
setExpandedNodeIds(expandableIds);
|
|
118
|
+
} else {
|
|
119
|
+
setExpandedNodeIds([]);
|
|
120
|
+
}
|
|
121
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
+
}, [ expandAll, expandableIds.join(',') ]);
|
|
123
|
+
|
|
90
124
|
const activeHeadState = useMemo(() => activeState ? headStates?.[activeState] : undefined, [ activeState, headStates ]);
|
|
91
125
|
const activeBodyState = useMemo(() => activeState ? bodyStates?.[activeState] : undefined, [ activeState, bodyStates ]);
|
|
92
126
|
|