@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.
- package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +2 -0
- package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.js +3 -2
- package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -1
- package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +8 -2
- package/dist/cjs/DataViewTable/DataViewTable.d.ts +2 -1
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +11 -0
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.js +46 -6
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.test.js +47 -9
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.d.ts +4 -0
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +31 -2
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.js +88 -0
- package/dist/cjs/DataViewTh/DataViewTh.d.ts +4 -4
- package/dist/cjs/DataViewTh/DataViewTh.js +8 -1
- package/dist/cjs/DataViewTh/index.d.ts +2 -0
- package/dist/cjs/DataViewTh/index.js +23 -0
- package/dist/cjs/DataViewToolbar/DataViewToolbar.js +13 -1
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.d.ts +28 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.js +230 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.js +176 -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 +8 -8
- 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/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +2 -0
- package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.js +3 -2
- package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -1
- package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +9 -3
- package/dist/esm/DataViewTable/DataViewTable.d.ts +2 -1
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +11 -0
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.js +48 -8
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.test.js +45 -10
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.d.ts +4 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +31 -2
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.js +90 -2
- package/dist/esm/DataViewTh/DataViewTh.d.ts +4 -4
- package/dist/esm/DataViewTh/DataViewTh.js +8 -1
- package/dist/esm/DataViewTh/index.d.ts +2 -0
- package/dist/esm/DataViewTh/index.js +2 -0
- package/dist/esm/DataViewToolbar/DataViewToolbar.js +13 -1
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.d.ts +28 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.js +226 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.js +171 -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 +8 -8
- 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 +6 -6
- package/patternfly-docs/content/extensions/data-view/examples/DataView/DataView.md +10 -4
- package/patternfly-docs/content/extensions/data-view/examples/DataView/PredefinedLayoutFullExample.tsx +2 -1
- 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/DataViewTableStickyExample.tsx +90 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +63 -2
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/FiltersExample.tsx +3 -2
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/PaginationExample.tsx +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +9 -2
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/TreeFilterExample.tsx +248 -0
- package/patternfly-docs/patternfly-docs.config.js +4 -1
- package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx +16 -7
- package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx +5 -1
- package/src/DataViewTable/DataViewTable.tsx +3 -1
- package/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap +7 -7
- package/src/DataViewTableBasic/DataViewTableBasic.test.tsx +54 -12
- package/src/DataViewTableBasic/DataViewTableBasic.tsx +101 -10
- package/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap +10 -10
- package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +140 -1
- package/src/DataViewTextFilter/DataViewTextFilter.tsx +63 -22
- package/src/DataViewTh/DataViewTh.tsx +15 -7
- package/src/DataViewTh/index.ts +2 -0
- package/src/DataViewToolbar/DataViewToolbar.tsx +17 -2
- package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +288 -280
- package/src/DataViewTreeFilter/DataViewTreeFilter.test.tsx +233 -0
- package/src/DataViewTreeFilter/DataViewTreeFilter.tsx +365 -0
- package/src/DataViewTreeFilter/__snapshots__/DataViewTreeFilter.test.tsx.snap +199 -0
- package/src/DataViewTreeFilter/index.ts +2 -0
- package/src/Hooks/selection.ts +8 -8
- package/src/index.ts +9 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Pagination } from '@patternfly/react-core';
|
|
3
|
+
import { BrowserRouter, useSearchParams } from 'react-router-dom';
|
|
4
|
+
import { TreeViewDataItem } from '@patternfly/react-core';
|
|
5
|
+
import { useDataViewFilters, useDataViewPagination } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
6
|
+
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
|
|
7
|
+
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
8
|
+
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
|
|
9
|
+
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
|
|
10
|
+
import { DataViewTreeFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTreeFilter';
|
|
11
|
+
|
|
12
|
+
const perPageOptions = [
|
|
13
|
+
{ title: '5', value: 5 },
|
|
14
|
+
{ title: '10', value: 10 }
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
interface Repository {
|
|
18
|
+
name: string;
|
|
19
|
+
workspace: string;
|
|
20
|
+
tags: string[];
|
|
21
|
+
os: string;
|
|
22
|
+
lastSeen: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface RepositoryFilters {
|
|
26
|
+
name: string;
|
|
27
|
+
workspace: string[];
|
|
28
|
+
os: string[];
|
|
29
|
+
tags: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const repositories: Repository[] = [
|
|
33
|
+
{ name: 'Server-001', workspace: 'Development Workspace', tags: ['web', 'frontend'], os: 'Ubuntu 22.04', lastSeen: '2 hours ago' },
|
|
34
|
+
{ name: 'Server-002', workspace: 'Development Workspace', tags: ['api', 'backend'], os: 'RHEL 9', lastSeen: '5 hours ago' },
|
|
35
|
+
{ name: 'Server-003', workspace: 'Development Workspace', tags: ['database'], os: 'Windows Server 2022', lastSeen: '1 day ago' },
|
|
36
|
+
{ name: 'Server-004', workspace: 'Production Workspace', tags: ['web', 'frontend'], os: 'Ubuntu 22.04', lastSeen: '30 minutes ago' },
|
|
37
|
+
{ name: 'Server-005', workspace: 'Production Workspace', tags: ['api', 'backend'], os: 'Debian 12', lastSeen: '1 hour ago' },
|
|
38
|
+
{ name: 'Server-006', workspace: 'Production Workspace', tags: ['monitoring'], os: 'macOS Ventura', lastSeen: '3 hours ago' },
|
|
39
|
+
{ name: 'Server-007', workspace: 'Production Workspace', tags: ['cache'], os: 'macOS Sonoma', lastSeen: '2 days ago' },
|
|
40
|
+
{ name: 'Server-008', workspace: 'Testing Workspace', tags: ['test', 'frontend'], os: 'CentOS 8', lastSeen: '6 hours ago' },
|
|
41
|
+
{ name: 'Server-009', workspace: 'Testing Workspace', tags: ['test', 'backend'], os: 'Fedora 38', lastSeen: '4 hours ago' }
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const osOptions: TreeViewDataItem[] = [
|
|
46
|
+
{
|
|
47
|
+
name: 'Linux',
|
|
48
|
+
id: 'os-linux',
|
|
49
|
+
checkProps: { 'aria-label': 'linux-check', checked: false },
|
|
50
|
+
children: [
|
|
51
|
+
{
|
|
52
|
+
name: 'Ubuntu 22.04',
|
|
53
|
+
id: 'os-ubuntu',
|
|
54
|
+
checkProps: { checked: false }
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'RHEL 9',
|
|
58
|
+
id: 'os-rhel',
|
|
59
|
+
checkProps: { checked: false }
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Debian 12',
|
|
63
|
+
id: 'os-debian',
|
|
64
|
+
checkProps: { checked: false }
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'CentOS 8',
|
|
68
|
+
id: 'os-centos',
|
|
69
|
+
checkProps: { checked: false }
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Fedora 38',
|
|
73
|
+
id: 'os-fedora',
|
|
74
|
+
checkProps: { checked: false }
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
defaultExpanded: true
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Windows',
|
|
81
|
+
id: 'os-windows',
|
|
82
|
+
checkProps: { 'aria-label': 'windows-check', checked: false },
|
|
83
|
+
children: [
|
|
84
|
+
{
|
|
85
|
+
name: 'Windows Server 2022',
|
|
86
|
+
id: 'os-windows-2022',
|
|
87
|
+
checkProps: { checked: false }
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'macOS',
|
|
93
|
+
id: 'os-macos',
|
|
94
|
+
checkProps: { 'aria-label': 'macos-check', checked: false },
|
|
95
|
+
children: [
|
|
96
|
+
{
|
|
97
|
+
name: 'macOS Ventura',
|
|
98
|
+
id: 'os-macos-ventura',
|
|
99
|
+
checkProps: { checked: false }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'macOS Sonoma',
|
|
103
|
+
id: 'os-macos-sonoma',
|
|
104
|
+
checkProps: { checked: false }
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const tagOptions: TreeViewDataItem[] = [
|
|
111
|
+
{
|
|
112
|
+
name: 'Environment',
|
|
113
|
+
id: 'tags-env',
|
|
114
|
+
checkProps: { 'aria-label': 'env-check', checked: false },
|
|
115
|
+
children: [
|
|
116
|
+
{
|
|
117
|
+
name: 'web',
|
|
118
|
+
id: 'tag-web',
|
|
119
|
+
checkProps: { checked: false }
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'api',
|
|
123
|
+
id: 'tag-api',
|
|
124
|
+
checkProps: { checked: false }
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'database',
|
|
128
|
+
id: 'tag-database',
|
|
129
|
+
checkProps: { checked: false }
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
defaultExpanded: true
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'Layer',
|
|
136
|
+
id: 'tags-layer',
|
|
137
|
+
checkProps: { 'aria-label': 'layer-check', checked: false },
|
|
138
|
+
children: [
|
|
139
|
+
{
|
|
140
|
+
name: 'frontend',
|
|
141
|
+
id: 'tag-frontend',
|
|
142
|
+
checkProps: { checked: false }
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'backend',
|
|
146
|
+
id: 'tag-backend',
|
|
147
|
+
checkProps: { checked: false }
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'Other',
|
|
153
|
+
id: 'tags-other',
|
|
154
|
+
checkProps: { 'aria-label': 'other-check', checked: false },
|
|
155
|
+
children: [
|
|
156
|
+
{
|
|
157
|
+
name: 'monitoring',
|
|
158
|
+
id: 'tag-monitoring',
|
|
159
|
+
checkProps: { checked: false }
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'cache',
|
|
163
|
+
id: 'tag-cache',
|
|
164
|
+
checkProps: { checked: false }
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'test',
|
|
168
|
+
id: 'tag-test',
|
|
169
|
+
checkProps: { checked: false }
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const columns = ['Name', 'Workspace', 'Tags', 'OS', 'Last seen'];
|
|
176
|
+
|
|
177
|
+
const ouiaId = 'TreeFilterExample';
|
|
178
|
+
|
|
179
|
+
const MyTable: React.FunctionComponent = () => {
|
|
180
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
181
|
+
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<RepositoryFilters>({
|
|
182
|
+
initialFilters: { name: '', workspace: [], os: [], tags: [] },
|
|
183
|
+
searchParams,
|
|
184
|
+
setSearchParams,
|
|
185
|
+
});
|
|
186
|
+
const pagination = useDataViewPagination({ perPage: 5 });
|
|
187
|
+
const { page, perPage } = pagination;
|
|
188
|
+
|
|
189
|
+
const filteredData = useMemo(
|
|
190
|
+
() =>
|
|
191
|
+
repositories.filter(
|
|
192
|
+
(item) =>
|
|
193
|
+
(!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) &&
|
|
194
|
+
(!filters.workspace || filters.workspace.length === 0 || filters.workspace.includes(item.workspace)) &&
|
|
195
|
+
(!filters.os || filters.os.length === 0 || filters.os.includes(item.os)) &&
|
|
196
|
+
(!filters.tags || filters.tags.length === 0 || filters.tags.some(tag => item.tags.includes(tag)))
|
|
197
|
+
),
|
|
198
|
+
[filters]
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const pageRows = useMemo(
|
|
202
|
+
() =>
|
|
203
|
+
filteredData
|
|
204
|
+
.slice((page - 1) * perPage, (page - 1) * perPage + perPage)
|
|
205
|
+
.map((item) => [item.name, item.workspace, item.tags.join(', '), item.os, item.lastSeen]),
|
|
206
|
+
[page, perPage, filteredData]
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<DataView>
|
|
211
|
+
<DataViewToolbar
|
|
212
|
+
ouiaId="TreeFilterExampleHeader"
|
|
213
|
+
clearAllFilters={clearAllFilters}
|
|
214
|
+
pagination={<Pagination perPageOptions={perPageOptions} itemCount={filteredData.length} {...pagination} />}
|
|
215
|
+
filters={
|
|
216
|
+
<DataViewFilters onChange={(_e, values) => onSetFilters(values)} values={filters}>
|
|
217
|
+
<DataViewTreeFilter
|
|
218
|
+
filterId="os"
|
|
219
|
+
title="Operating System"
|
|
220
|
+
items={osOptions}
|
|
221
|
+
defaultExpanded={false}
|
|
222
|
+
/>
|
|
223
|
+
<DataViewTreeFilter
|
|
224
|
+
filterId="tags"
|
|
225
|
+
title="Tags"
|
|
226
|
+
items={tagOptions}
|
|
227
|
+
defaultExpanded={false}
|
|
228
|
+
defaultSelected={['tag-web', 'tag-api']}
|
|
229
|
+
/>
|
|
230
|
+
</DataViewFilters>
|
|
231
|
+
}
|
|
232
|
+
/>
|
|
233
|
+
<DataViewTable aria-label="Repositories table" ouiaId={ouiaId} columns={columns} rows={pageRows} />
|
|
234
|
+
<DataViewToolbar
|
|
235
|
+
ouiaId="TreeFilterExampleFooter"
|
|
236
|
+
pagination={
|
|
237
|
+
<Pagination isCompact variant="bottom" perPageOptions={perPageOptions} itemCount={filteredData.length} {...pagination} />
|
|
238
|
+
}
|
|
239
|
+
/>
|
|
240
|
+
</DataView>
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const TreeFilterExample: React.FunctionComponent = () => (
|
|
245
|
+
<BrowserRouter>
|
|
246
|
+
<MyTable />
|
|
247
|
+
</BrowserRouter>
|
|
248
|
+
);
|
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import DataViewCheckboxFilter, { DataViewCheckboxFilterProps } from './DataViewCheckboxFilter';
|
|
3
3
|
import DataViewToolbar from '../DataViewToolbar';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
4
5
|
|
|
5
6
|
describe('DataViewCheckboxFilter component', () => {
|
|
6
7
|
const defaultProps: DataViewCheckboxFilterProps = {
|
|
7
8
|
filterId: 'test-checkbox-filter',
|
|
8
9
|
title: 'Test Checkbox Filter',
|
|
9
|
-
value: [
|
|
10
|
+
value: ['workspace-one'],
|
|
10
11
|
options: [
|
|
11
12
|
{ label: 'Workspace one', value: 'workspace-one' },
|
|
12
13
|
{ label: 'Workspace two', value: 'workspace-two' },
|
|
13
|
-
{ label: 'Workspace three', value: 'workspace-three' }
|
|
14
|
-
]
|
|
14
|
+
{ label: 'Workspace three', value: 'workspace-three' }
|
|
15
|
+
]
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
it('should render correctly', () => {
|
|
18
|
-
const { container } = render(
|
|
19
|
-
<DataViewToolbar filters={<DataViewCheckboxFilter {...defaultProps} />} />
|
|
20
|
-
);
|
|
19
|
+
const { container } = render(<DataViewToolbar filters={<DataViewCheckboxFilter {...defaultProps} />} />);
|
|
21
20
|
expect(container).toMatchSnapshot();
|
|
22
21
|
});
|
|
22
|
+
|
|
23
|
+
it('should use chipTitle for the filter chip category when provided', () => {
|
|
24
|
+
render(
|
|
25
|
+
<DataViewToolbar
|
|
26
|
+
filters={<DataViewCheckboxFilter {...defaultProps} chipTitle="Short name" />}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
expect(screen.getByText('Short name')).toBeInTheDocument();
|
|
30
|
+
expect(screen.getByText('Test Checkbox Filter')).toBeInTheDocument();
|
|
31
|
+
});
|
|
23
32
|
});
|
|
@@ -32,6 +32,8 @@ export interface DataViewCheckboxFilterProps extends Omit<MenuProps, 'onSelect'
|
|
|
32
32
|
value?: string[];
|
|
33
33
|
/** Filter title displayed in the toolbar */
|
|
34
34
|
title: string;
|
|
35
|
+
/** Label for the applied filter chip / category; defaults to title */
|
|
36
|
+
chipTitle?: string;
|
|
35
37
|
/** Placeholder text of the menu */
|
|
36
38
|
placeholder?: string;
|
|
37
39
|
/** Filter options displayed */
|
|
@@ -51,6 +53,7 @@ export interface DataViewCheckboxFilterProps extends Omit<MenuProps, 'onSelect'
|
|
|
51
53
|
export const DataViewCheckboxFilter: FC<DataViewCheckboxFilterProps> = ({
|
|
52
54
|
filterId,
|
|
53
55
|
title,
|
|
56
|
+
chipTitle,
|
|
54
57
|
value = [],
|
|
55
58
|
onChange,
|
|
56
59
|
placeholder,
|
|
@@ -65,6 +68,7 @@ export const DataViewCheckboxFilter: FC<DataViewCheckboxFilterProps> = ({
|
|
|
65
68
|
const toggleRef = useRef<HTMLButtonElement>(null);
|
|
66
69
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
67
70
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
71
|
+
const categoryName = chipTitle ?? title;
|
|
68
72
|
|
|
69
73
|
const normalizeOptions = useMemo(
|
|
70
74
|
() =>
|
|
@@ -120,7 +124,7 @@ export const DataViewCheckboxFilter: FC<DataViewCheckboxFilterProps> = ({
|
|
|
120
124
|
deleteLabel={(_, label) =>
|
|
121
125
|
onChange?.(undefined, value.filter(item => item !== (isToolbarLabel(label) ? label.key : label)))
|
|
122
126
|
}
|
|
123
|
-
categoryName={
|
|
127
|
+
categoryName={categoryName}
|
|
124
128
|
showToolbarItem={showToolbarItem}
|
|
125
129
|
>
|
|
126
130
|
<Popper
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { FC, ReactNode } from 'react';
|
|
2
2
|
import { TdProps, ThProps, TrProps, InnerScrollContainer } from '@patternfly/react-table';
|
|
3
3
|
import { DataViewTableTree, DataViewTableTreeProps } from '../DataViewTableTree';
|
|
4
|
-
import { DataViewTableBasic, DataViewTableBasicProps } from '../DataViewTableBasic';
|
|
4
|
+
import { DataViewTableBasic, DataViewTableBasicProps, ExpandableContent } from '../DataViewTableBasic';
|
|
5
5
|
import { DataViewThResizableProps } from '../DataViewTh/DataViewTh';
|
|
6
6
|
|
|
7
|
+
export type { ExpandableContent };
|
|
8
|
+
|
|
7
9
|
// Table head typings
|
|
8
10
|
export type DataViewTh =
|
|
9
11
|
| ReactNode
|
|
@@ -4,7 +4,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
|
|
|
4
4
|
<div>
|
|
5
5
|
<table
|
|
6
6
|
aria-label="Repositories table"
|
|
7
|
-
class="pf-v6-c-table pf-m-grid-md"
|
|
7
|
+
class="pf-v6-c-table pf-m-grid-md pf-m-animate-expand"
|
|
8
8
|
data-ouia-component-id="TableExample"
|
|
9
9
|
data-ouia-component-type="PF6/Table"
|
|
10
10
|
data-ouia-safe="true"
|
|
@@ -67,7 +67,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
|
|
|
67
67
|
role="rowgroup"
|
|
68
68
|
>
|
|
69
69
|
<tr
|
|
70
|
-
class="pf-v6-c-table__tr"
|
|
70
|
+
class="pf-v6-c-table__tr pf-v6-c-table__control-row"
|
|
71
71
|
data-ouia-component-id="TableExample-tr-0"
|
|
72
72
|
data-ouia-component-type="PF6/TableRow"
|
|
73
73
|
data-ouia-safe="true"
|
|
@@ -109,7 +109,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
|
|
|
109
109
|
</td>
|
|
110
110
|
</tr>
|
|
111
111
|
<tr
|
|
112
|
-
class="pf-v6-c-table__tr"
|
|
112
|
+
class="pf-v6-c-table__tr pf-v6-c-table__control-row"
|
|
113
113
|
data-ouia-component-id="TableExample-tr-1"
|
|
114
114
|
data-ouia-component-type="PF6/TableRow"
|
|
115
115
|
data-ouia-safe="true"
|
|
@@ -151,7 +151,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
|
|
|
151
151
|
</td>
|
|
152
152
|
</tr>
|
|
153
153
|
<tr
|
|
154
|
-
class="pf-v6-c-table__tr"
|
|
154
|
+
class="pf-v6-c-table__tr pf-v6-c-table__control-row"
|
|
155
155
|
data-ouia-component-id="TableExample-tr-2"
|
|
156
156
|
data-ouia-component-type="PF6/TableRow"
|
|
157
157
|
data-ouia-safe="true"
|
|
@@ -193,7 +193,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
|
|
|
193
193
|
</td>
|
|
194
194
|
</tr>
|
|
195
195
|
<tr
|
|
196
|
-
class="pf-v6-c-table__tr"
|
|
196
|
+
class="pf-v6-c-table__tr pf-v6-c-table__control-row"
|
|
197
197
|
data-ouia-component-id="TableExample-tr-3"
|
|
198
198
|
data-ouia-component-type="PF6/TableRow"
|
|
199
199
|
data-ouia-safe="true"
|
|
@@ -235,7 +235,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
|
|
|
235
235
|
</td>
|
|
236
236
|
</tr>
|
|
237
237
|
<tr
|
|
238
|
-
class="pf-v6-c-table__tr"
|
|
238
|
+
class="pf-v6-c-table__tr pf-v6-c-table__control-row"
|
|
239
239
|
data-ouia-component-id="TableExample-tr-4"
|
|
240
240
|
data-ouia-component-type="PF6/TableRow"
|
|
241
241
|
data-ouia-safe="true"
|
|
@@ -277,7 +277,7 @@ exports[`DataViewTable component should render a basic table correctly 1`] = `
|
|
|
277
277
|
</td>
|
|
278
278
|
</tr>
|
|
279
279
|
<tr
|
|
280
|
-
class="pf-v6-c-table__tr"
|
|
280
|
+
class="pf-v6-c-table__tr pf-v6-c-table__control-row"
|
|
281
281
|
data-ouia-component-id="TableExample-tr-5"
|
|
282
282
|
data-ouia-component-type="PF6/TableRow"
|
|
283
283
|
data-ouia-safe="true"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
2
3
|
import { DataView } from '../DataView';
|
|
3
|
-
import { DataViewTableBasic } from './DataViewTableBasic';
|
|
4
|
+
import { DataViewTableBasic, ExpandableContent } from './DataViewTableBasic';
|
|
4
5
|
|
|
5
6
|
interface Repository {
|
|
7
|
+
id: number;
|
|
6
8
|
name: string;
|
|
7
9
|
branches: string | null;
|
|
8
10
|
prs: string | null;
|
|
@@ -11,20 +13,28 @@ interface Repository {
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
const repositories: Repository[] = [
|
|
14
|
-
{ name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
15
|
-
{ name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
16
|
-
{ name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
17
|
-
{ name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
18
|
-
{ name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
19
|
-
{ name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
16
|
+
{ id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
17
|
+
{ id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
18
|
+
{ id: 3, name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
19
|
+
{ id: 4, name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
20
|
+
{ id: 5, name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
21
|
+
{ id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
20
22
|
];
|
|
21
23
|
|
|
22
|
-
const rows = repositories.map(
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const rows = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [
|
|
25
|
+
{ id, cell: name },
|
|
26
|
+
branches,
|
|
27
|
+
prs,
|
|
28
|
+
workspaces,
|
|
29
|
+
lastCommit
|
|
30
|
+
]);
|
|
25
31
|
|
|
26
32
|
const columns = [ 'Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit' ];
|
|
27
33
|
|
|
34
|
+
const expandableContents: ExpandableContent[] = [
|
|
35
|
+
{ rowId: 1, columnId: 1, content: <div>Branch details for Repository one</div> },
|
|
36
|
+
];
|
|
37
|
+
|
|
28
38
|
const ouiaId = 'TableExample';
|
|
29
39
|
|
|
30
40
|
describe('DataViewTable component', () => {
|
|
@@ -57,8 +67,40 @@ describe('DataViewTable component', () => {
|
|
|
57
67
|
const { container } = render(
|
|
58
68
|
<DataView activeState="loading">
|
|
59
69
|
<DataViewTableBasic aria-label='Repositories table' ouiaId={ouiaId} columns={columns} bodyStates={{ loading: "Data is loading" }} rows={[]} />
|
|
60
|
-
</DataView>
|
|
70
|
+
</DataView>
|
|
61
71
|
);
|
|
62
72
|
expect(container).toMatchSnapshot();
|
|
63
73
|
});
|
|
74
|
+
|
|
75
|
+
test('when isExpandable cell should be clickable and expandable', async () => {
|
|
76
|
+
const user = userEvent.setup();
|
|
77
|
+
|
|
78
|
+
render(
|
|
79
|
+
<DataViewTableBasic
|
|
80
|
+
aria-label='Repositories table'
|
|
81
|
+
ouiaId={ouiaId}
|
|
82
|
+
columns={columns}
|
|
83
|
+
rows={rows}
|
|
84
|
+
isExpandable={true}
|
|
85
|
+
expandedRows={expandableContents}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Initially, expandable content is rendered but should be hidden (not visible)
|
|
90
|
+
const initialBranchContent = screen.getByText('Branch details for Repository one');
|
|
91
|
+
expect(initialBranchContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeFalsy();
|
|
92
|
+
|
|
93
|
+
// Find the first expandable button by ID
|
|
94
|
+
const branchExpandButton = document.getElementById('expandable-0-0-1');
|
|
95
|
+
expect(branchExpandButton).toBeTruthy();
|
|
96
|
+
// Verify the button is in the cell with "Branch one" text
|
|
97
|
+
expect(branchExpandButton?.closest('td')?.textContent).toContain('Branch one');
|
|
98
|
+
|
|
99
|
+
// Click the expand button for Branches column
|
|
100
|
+
await user.click(branchExpandButton!);
|
|
101
|
+
|
|
102
|
+
// After clicking, the expandable content should be visible
|
|
103
|
+
const branchContent = screen.getByText('Branch details for Repository one');
|
|
104
|
+
expect(branchContent.closest('tr')?.classList.contains('pf-m-expanded')).toBeTruthy();
|
|
105
|
+
});
|
|
64
106
|
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { FC, useMemo } from 'react';
|
|
1
|
+
import { FC, useMemo, useState, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
ExpandableRowContent,
|
|
4
|
+
InnerScrollContainer,
|
|
5
|
+
OuterScrollContainer,
|
|
3
6
|
Table,
|
|
4
7
|
TableProps,
|
|
5
8
|
Tbody,
|
|
@@ -11,12 +14,20 @@ import { DataViewTableHead } from '../DataViewTableHead';
|
|
|
11
14
|
import { DataViewTh, DataViewTr, isDataViewTdObject, isDataViewTrObject } from '../DataViewTable';
|
|
12
15
|
import { DataViewState } from '../DataView/DataView';
|
|
13
16
|
|
|
17
|
+
export interface ExpandableContent {
|
|
18
|
+
rowId: number;
|
|
19
|
+
columnId: number;
|
|
20
|
+
content: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
/** extends TableProps */
|
|
15
24
|
export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | 'rows'> {
|
|
16
25
|
/** Columns definition */
|
|
17
26
|
columns: DataViewTh[];
|
|
18
27
|
/** Current page rows */
|
|
19
28
|
rows: DataViewTr[];
|
|
29
|
+
/** Expanded rows content */
|
|
30
|
+
expandedRows?: ExpandableContent[];
|
|
20
31
|
/** Table head states to be displayed when active */
|
|
21
32
|
headStates?: Partial<Record<DataViewState | string, React.ReactNode>>
|
|
22
33
|
/** Table body states to be displayed when active */
|
|
@@ -25,15 +36,22 @@ export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | '
|
|
|
25
36
|
ouiaId?: string;
|
|
26
37
|
/** @hide Indicates if the table is resizable */
|
|
27
38
|
hasResizableColumns?: boolean;
|
|
39
|
+
/** Toggles expandable */
|
|
40
|
+
isExpandable?: boolean;
|
|
41
|
+
/** Toggles sticky columns and header */
|
|
42
|
+
isSticky?: boolean;
|
|
28
43
|
}
|
|
29
44
|
|
|
30
45
|
export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
|
|
31
46
|
columns,
|
|
32
47
|
rows,
|
|
48
|
+
expandedRows,
|
|
33
49
|
ouiaId = 'DataViewTableBasic',
|
|
34
50
|
headStates,
|
|
35
51
|
bodyStates,
|
|
36
52
|
hasResizableColumns,
|
|
53
|
+
isExpandable = false,
|
|
54
|
+
isSticky = false,
|
|
37
55
|
...props
|
|
38
56
|
}: DataViewTableBasicProps) => {
|
|
39
57
|
const { selection, activeState, isSelectable } = useInternalContext();
|
|
@@ -42,10 +60,30 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
|
|
|
42
60
|
const activeHeadState = useMemo(() => activeState ? headStates?.[activeState] : undefined, [ activeState, headStates ]);
|
|
43
61
|
const activeBodyState = useMemo(() => activeState ? bodyStates?.[activeState] : undefined, [ activeState, bodyStates ]);
|
|
44
62
|
|
|
63
|
+
const [expandedRowsState, setExpandedRowsState] = useState<Record<number, boolean>>({})
|
|
64
|
+
const [expandedColumnIndex, setExpandedColumnIndex] = useState<Record<number, number>>({})
|
|
65
|
+
|
|
66
|
+
const tableRef = useRef<HTMLTableElement>(null);
|
|
67
|
+
|
|
68
|
+
const needsSeparateTbody = isExpandable;
|
|
69
|
+
|
|
45
70
|
const renderedRows = useMemo(() => rows.map((row, rowIndex) => {
|
|
46
71
|
const rowIsObject = isDataViewTrObject(row);
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
const isRowExpanded = expandedRowsState[rowIndex] || false;
|
|
73
|
+
const expandedColIndex = expandedColumnIndex[rowIndex];
|
|
74
|
+
|
|
75
|
+
// Get the first cell to extract the row ID
|
|
76
|
+
const rowData = rowIsObject ? row.row : row;
|
|
77
|
+
const firstCell = rowData[0];
|
|
78
|
+
const rowId = isDataViewTdObject(firstCell) ? (firstCell as { id?: number }).id : undefined;
|
|
79
|
+
|
|
80
|
+
// Find all expandable contents for this row
|
|
81
|
+
const rowExpandableContents = isExpandable ? expandedRows?.filter(
|
|
82
|
+
(content) => content.rowId === rowId
|
|
83
|
+
) : [];
|
|
84
|
+
|
|
85
|
+
const rowContent = (
|
|
86
|
+
<Tr key={needsSeparateTbody ? undefined : rowIndex} ouiaId={`${ouiaId}-tr-${rowIndex}`} {...(rowIsObject && row?.props)} isContentExpanded={isRowExpanded} isControlRow>
|
|
49
87
|
{isSelectable && (
|
|
50
88
|
<Td
|
|
51
89
|
key={`select-${rowIndex}`}
|
|
@@ -61,10 +99,29 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
|
|
|
61
99
|
)}
|
|
62
100
|
{(rowIsObject ? row.row : row).map((cell, colIndex) => {
|
|
63
101
|
const cellIsObject = isDataViewTdObject(cell);
|
|
102
|
+
const cellExpandableContent = isExpandable ? expandedRows?.find(
|
|
103
|
+
(content) => content.rowId === rowId && content.columnId === colIndex
|
|
104
|
+
) : undefined;
|
|
64
105
|
return (
|
|
65
106
|
<Td
|
|
66
107
|
key={colIndex}
|
|
67
108
|
{...(cellIsObject && (cell?.props ?? {}))}
|
|
109
|
+
{...(cellExpandableContent != null && {
|
|
110
|
+
compoundExpand: {
|
|
111
|
+
isExpanded: isRowExpanded && expandedColIndex === colIndex,
|
|
112
|
+
expandId: `expandable-${rowIndex}`,
|
|
113
|
+
onToggle: () => {
|
|
114
|
+
setExpandedRowsState(prev => {
|
|
115
|
+
const isSameColumn = expandedColIndex === colIndex;
|
|
116
|
+
const wasExpanded = prev[rowIndex];
|
|
117
|
+
return { ...prev, [rowIndex]: isSameColumn ? !wasExpanded : true };
|
|
118
|
+
});
|
|
119
|
+
setExpandedColumnIndex(prev => ({ ...prev, [rowIndex]: colIndex }));
|
|
120
|
+
},
|
|
121
|
+
rowIndex,
|
|
122
|
+
columnIndex: colIndex
|
|
123
|
+
}
|
|
124
|
+
})}
|
|
68
125
|
data-ouia-component-id={`${ouiaId}-td-${rowIndex}-${colIndex}`}
|
|
69
126
|
>
|
|
70
127
|
{cellIsObject ? cell.cell : cell}
|
|
@@ -73,14 +130,48 @@ export const DataViewTableBasic: FC<DataViewTableBasicProps> = ({
|
|
|
73
130
|
})}
|
|
74
131
|
</Tr>
|
|
75
132
|
);
|
|
76
|
-
}), [ rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId ]);
|
|
77
133
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
134
|
+
if (needsSeparateTbody) {
|
|
135
|
+
return (
|
|
136
|
+
<Tbody key={rowIndex} isExpanded={isRowExpanded}>
|
|
137
|
+
{rowContent}
|
|
138
|
+
{rowExpandableContents?.map((expandableContent) => (
|
|
139
|
+
<Tr key={`expand-${rowIndex}-${expandableContent.columnId}`} isExpanded={isRowExpanded && expandedColIndex === expandableContent.columnId}>
|
|
140
|
+
<Td colSpan={rowData.length + (isSelectable ? 1 : 0)} data-expanded-column-index={expandableContent.columnId}>
|
|
141
|
+
<ExpandableRowContent>
|
|
142
|
+
{expandableContent.content}
|
|
143
|
+
</ExpandableRowContent>
|
|
144
|
+
</Td>
|
|
145
|
+
</Tr>
|
|
146
|
+
))}
|
|
147
|
+
</Tbody>
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
return rowContent;
|
|
151
|
+
}
|
|
152
|
+
}), [ rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId, expandedRowsState, expandedColumnIndex, expandedRows, isExpandable, needsSeparateTbody ]);
|
|
153
|
+
|
|
154
|
+
const bodyContent = activeBodyState || (needsSeparateTbody ? renderedRows : <Tbody>{renderedRows}</Tbody>);
|
|
155
|
+
|
|
156
|
+
if (isSticky) {
|
|
157
|
+
return (
|
|
158
|
+
<OuterScrollContainer>
|
|
159
|
+
<InnerScrollContainer>
|
|
160
|
+
<Table ref={tableRef} aria-label="Data table" ouiaId={ouiaId} isExpandable={isExpandable} hasAnimations {...props} isStickyHeader >
|
|
161
|
+
{ activeHeadState || <DataViewTableHead columns={columns} ouiaId={ouiaId} hasResizableColumns={hasResizableColumns} /> }
|
|
162
|
+
{ bodyContent }
|
|
163
|
+
</Table>
|
|
164
|
+
</InnerScrollContainer>
|
|
165
|
+
</OuterScrollContainer>
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
return (
|
|
169
|
+
<Table ref={tableRef} aria-label="Data table" ouiaId={ouiaId} isExpandable={isExpandable} hasAnimations {...props}>
|
|
170
|
+
{ activeHeadState || <DataViewTableHead columns={columns} ouiaId={ouiaId} hasResizableColumns={hasResizableColumns} /> }
|
|
171
|
+
{ bodyContent }
|
|
172
|
+
</Table>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
84
175
|
};
|
|
85
176
|
|
|
86
177
|
export default DataViewTableBasic;
|