@patternfly/react-data-view 5.4.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/DataView/DataView.d.ts +3 -1
- package/dist/cjs/DataViewFilters/DataViewFilters.d.ts +19 -0
- package/dist/cjs/DataViewFilters/DataViewFilters.js +70 -0
- package/dist/cjs/DataViewFilters/DataViewFilters.test.d.ts +1 -0
- package/dist/cjs/DataViewFilters/DataViewFilters.test.js +19 -0
- package/dist/cjs/DataViewFilters/index.d.ts +2 -0
- package/dist/cjs/DataViewFilters/index.js +23 -0
- package/dist/cjs/DataViewTable/DataViewTable.d.ts +8 -0
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +1 -0
- package/dist/cjs/DataViewTableHead/DataViewTableHead.d.ts +1 -0
- package/dist/cjs/DataViewTableTree/DataViewTableTree.d.ts +1 -0
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.d.ts +21 -0
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +26 -0
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -0
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.js +22 -0
- package/dist/cjs/DataViewTextFilter/index.d.ts +2 -0
- package/dist/cjs/DataViewTextFilter/index.js +23 -0
- package/dist/cjs/DataViewToolbar/DataViewToolbar.d.ts +10 -4
- package/dist/cjs/DataViewToolbar/DataViewToolbar.js +29 -6
- package/dist/cjs/Hooks/filters.d.ts +14 -0
- package/dist/cjs/Hooks/filters.js +70 -0
- package/dist/cjs/Hooks/filters.test.d.ts +1 -0
- package/dist/cjs/Hooks/filters.test.js +50 -0
- package/dist/cjs/Hooks/index.d.ts +1 -0
- package/dist/cjs/Hooks/index.js +1 -0
- package/dist/cjs/Hooks/pagination.d.ts +1 -0
- package/dist/cjs/Hooks/selection.d.ts +1 -1
- package/dist/cjs/Hooks/selection.js +4 -2
- package/dist/cjs/InternalContext/InternalContext.d.ts +1 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -1
- package/dist/dynamic/DataViewFilters/package.json +1 -0
- package/dist/dynamic/DataViewTextFilter/package.json +1 -0
- package/dist/esm/DataView/DataView.d.ts +3 -1
- package/dist/esm/DataViewFilters/DataViewFilters.d.ts +19 -0
- package/dist/esm/DataViewFilters/DataViewFilters.js +43 -0
- package/dist/esm/DataViewFilters/DataViewFilters.test.d.ts +1 -0
- package/dist/esm/DataViewFilters/DataViewFilters.test.js +14 -0
- package/dist/esm/DataViewFilters/index.d.ts +2 -0
- package/dist/esm/DataViewFilters/index.js +2 -0
- package/dist/esm/DataViewTable/DataViewTable.d.ts +8 -0
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +1 -0
- package/dist/esm/DataViewTableHead/DataViewTableHead.d.ts +1 -0
- package/dist/esm/DataViewTableTree/DataViewTableTree.d.ts +1 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.d.ts +21 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +19 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.js +17 -0
- package/dist/esm/DataViewTextFilter/index.d.ts +2 -0
- package/dist/esm/DataViewTextFilter/index.js +2 -0
- package/dist/esm/DataViewToolbar/DataViewToolbar.d.ts +10 -4
- package/dist/esm/DataViewToolbar/DataViewToolbar.js +7 -4
- package/dist/esm/Hooks/filters.d.ts +14 -0
- package/dist/esm/Hooks/filters.js +66 -0
- package/dist/esm/Hooks/filters.test.d.ts +1 -0
- package/dist/esm/Hooks/filters.test.js +48 -0
- package/dist/esm/Hooks/index.d.ts +1 -0
- package/dist/esm/Hooks/index.js +1 -0
- package/dist/esm/Hooks/pagination.d.ts +1 -0
- package/dist/esm/Hooks/selection.d.ts +1 -1
- package/dist/esm/Hooks/selection.js +4 -2
- package/dist/esm/InternalContext/InternalContext.d.ts +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +5 -3
- package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md +1 -0
- package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +27 -5
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx +92 -0
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +36 -2
- package/src/DataView/DataView.tsx +3 -2
- package/src/DataViewFilters/DataViewFilters.test.tsx +21 -0
- package/src/DataViewFilters/DataViewFilters.tsx +125 -0
- package/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap +182 -0
- package/src/DataViewFilters/index.tsx +2 -0
- package/src/DataViewTable/DataViewTable.tsx +23 -3
- package/src/DataViewTableBasic/DataViewTableBasic.tsx +1 -0
- package/src/DataViewTableHead/DataViewTableHead.tsx +1 -0
- package/src/DataViewTableTree/DataViewTableTree.tsx +1 -0
- package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +24 -0
- package/src/DataViewTextFilter/DataViewTextFilter.tsx +53 -0
- package/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap +196 -0
- package/src/DataViewTextFilter/index.ts +2 -0
- package/src/DataViewToolbar/DataViewToolbar.tsx +47 -28
- package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +28 -0
- package/src/Hooks/filters.test.tsx +62 -0
- package/src/Hooks/filters.ts +96 -0
- package/src/Hooks/index.ts +1 -0
- package/src/Hooks/pagination.ts +1 -0
- package/src/Hooks/selection.ts +3 -2
- package/src/InternalContext/InternalContext.tsx +1 -0
- package/src/index.ts +3 -0
package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx
CHANGED
|
@@ -3,6 +3,8 @@ import { Drawer, DrawerActions, DrawerCloseButton, DrawerContent, DrawerContentB
|
|
|
3
3
|
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
|
|
4
4
|
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
5
5
|
import { DataViewEventsProvider, EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view/dist/dynamic/DataViewEventsContext';
|
|
6
|
+
import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
7
|
+
import { ActionsColumn } from '@patternfly/react-table';
|
|
6
8
|
|
|
7
9
|
interface Repository {
|
|
8
10
|
name: string;
|
|
@@ -64,25 +66,45 @@ interface RepositoriesTableProps {
|
|
|
64
66
|
selectedRepo?: Repository;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
const rowActions = [
|
|
70
|
+
{
|
|
71
|
+
title: 'Some action',
|
|
72
|
+
onClick: () => console.log('clicked on Some action') // eslint-disable-line no-console
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
title: <div>Another action</div>,
|
|
76
|
+
onClick: () => console.log('clicked on Another action') // eslint-disable-line no-console
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
isSeparator: true
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: 'Third action',
|
|
83
|
+
onClick: () => console.log('clicked on Third action') // eslint-disable-line no-console
|
|
84
|
+
}
|
|
85
|
+
];
|
|
86
|
+
|
|
67
87
|
const RepositoriesTable: React.FunctionComponent<RepositoriesTableProps> = ({ selectedRepo = undefined }) => {
|
|
88
|
+
const selection = useDataViewSelection({ matchOption: (a, b) => a.row[0] === b.row[0] });
|
|
68
89
|
const { trigger } = useDataViewEventsContext();
|
|
69
90
|
const rows = useMemo(() => {
|
|
70
|
-
const handleRowClick = (repo: Repository | undefined) => {
|
|
71
|
-
|
|
91
|
+
const handleRowClick = (event, repo: Repository | undefined) => {
|
|
92
|
+
// prevents drawer toggle on actions or checkbox click
|
|
93
|
+
(event.target.matches('td') || event.target.matches('tr')) && trigger(EventTypes.rowClick, repo);
|
|
72
94
|
};
|
|
73
95
|
|
|
74
96
|
return repositories.map(repo => ({
|
|
75
|
-
row: Object.values(repo),
|
|
97
|
+
row: [ ...Object.values(repo), { cell: <ActionsColumn items={rowActions}/>, props: { isActionCell: true } } ],
|
|
76
98
|
props: {
|
|
77
99
|
isClickable: true,
|
|
78
|
-
onRowClick: () => handleRowClick(selectedRepo?.name === repo.name ? undefined : repo),
|
|
100
|
+
onRowClick: (event) => handleRowClick(event, selectedRepo?.name === repo.name ? undefined : repo),
|
|
79
101
|
isRowSelected: selectedRepo?.name === repo.name
|
|
80
102
|
}
|
|
81
103
|
}));
|
|
82
104
|
}, [ selectedRepo?.name, trigger ]);
|
|
83
105
|
|
|
84
106
|
return (
|
|
85
|
-
<DataView>
|
|
107
|
+
<DataView selection={selection}>
|
|
86
108
|
<DataViewTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} rows={rows} />
|
|
87
109
|
</DataView>
|
|
88
110
|
);
|
package/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Pagination } from '@patternfly/react-core';
|
|
3
|
+
import { BrowserRouter, useSearchParams } from 'react-router-dom';
|
|
4
|
+
import { useDataViewFilters, useDataViewPagination } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
5
|
+
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
|
|
6
|
+
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
7
|
+
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
|
|
8
|
+
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
|
|
9
|
+
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
|
|
10
|
+
|
|
11
|
+
const perPageOptions = [
|
|
12
|
+
{ title: '5', value: 5 },
|
|
13
|
+
{ title: '10', value: 10 }
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
interface Repository {
|
|
17
|
+
name: string;
|
|
18
|
+
branch: string | null;
|
|
19
|
+
prs: string | null;
|
|
20
|
+
workspaces: string;
|
|
21
|
+
lastCommit: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface RepositoryFilters {
|
|
25
|
+
name: string,
|
|
26
|
+
branch: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const repositories: Repository[] = [
|
|
30
|
+
{ name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
31
|
+
{ name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
32
|
+
{ name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
33
|
+
{ name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
34
|
+
{ name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
35
|
+
{ name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const columns = [ 'Name', 'Branch', 'Pull requests', 'Workspaces', 'Last commit' ];
|
|
39
|
+
|
|
40
|
+
const ouiaId = 'LayoutExample';
|
|
41
|
+
|
|
42
|
+
const MyTable: React.FunctionComponent = () => {
|
|
43
|
+
const [ searchParams, setSearchParams ] = useSearchParams();
|
|
44
|
+
const pagination = useDataViewPagination({ perPage: 5 });
|
|
45
|
+
const { page, perPage } = pagination;
|
|
46
|
+
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<RepositoryFilters>({ initialFilters: { name: '', branch: '' }, searchParams, setSearchParams });
|
|
47
|
+
|
|
48
|
+
const pageRows = useMemo(() => repositories
|
|
49
|
+
.filter(item => (!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) && (!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase())))
|
|
50
|
+
.slice((page - 1) * perPage, ((page - 1) * perPage) + perPage)
|
|
51
|
+
.map(item => Object.values(item)), [ page, perPage, filters ]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<DataView>
|
|
55
|
+
<DataViewToolbar
|
|
56
|
+
ouiaId='LayoutExampleHeader'
|
|
57
|
+
clearAllFilters = {clearAllFilters}
|
|
58
|
+
pagination={
|
|
59
|
+
<Pagination
|
|
60
|
+
perPageOptions={perPageOptions}
|
|
61
|
+
itemCount={repositories.length}
|
|
62
|
+
{...pagination}
|
|
63
|
+
/>
|
|
64
|
+
}
|
|
65
|
+
filters={
|
|
66
|
+
<DataViewFilters onChange={(_e, values) => onSetFilters(values)} values={filters}>
|
|
67
|
+
<DataViewTextFilter filterId="name" title='Name' placeholder='Filter by name' />
|
|
68
|
+
<DataViewTextFilter filterId="branch" title='Branch' placeholder='Filter by branch' />
|
|
69
|
+
</DataViewFilters>
|
|
70
|
+
}
|
|
71
|
+
/>
|
|
72
|
+
<DataViewTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} rows={pageRows} />
|
|
73
|
+
<DataViewToolbar
|
|
74
|
+
ouiaId='LayoutExampleFooter'
|
|
75
|
+
pagination={
|
|
76
|
+
<Pagination
|
|
77
|
+
isCompact
|
|
78
|
+
perPageOptions={perPageOptions}
|
|
79
|
+
itemCount={repositories.length}
|
|
80
|
+
{...pagination}
|
|
81
|
+
/>
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
</DataView>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const BasicExample: React.FunctionComponent = () => (
|
|
89
|
+
<BrowserRouter>
|
|
90
|
+
<MyTable/>
|
|
91
|
+
</BrowserRouter>
|
|
92
|
+
)
|
package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
CHANGED
|
@@ -11,15 +11,18 @@ source: react
|
|
|
11
11
|
# If you use typescript, the name of the interface to display props for
|
|
12
12
|
# These are found through the sourceProps function provided in patternfly-docs.source.js
|
|
13
13
|
sortValue: 3
|
|
14
|
+
propComponents: ['DataViewFilters', 'DataViewTextFilter']
|
|
14
15
|
sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
|
|
15
16
|
---
|
|
16
17
|
import { useMemo } from 'react';
|
|
17
|
-
import {
|
|
18
|
+
import { BrowserRouter, useSearchParams } from 'react-router-dom';
|
|
19
|
+
import { useDataViewPagination, useDataViewSelection, useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
18
20
|
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
|
|
19
21
|
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
|
|
20
22
|
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
|
|
21
23
|
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
22
|
-
import {
|
|
24
|
+
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
|
|
25
|
+
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
|
|
23
26
|
|
|
24
27
|
This is a list of functionality you can use to manage data displayed in the **data view**.
|
|
25
28
|
|
|
@@ -84,3 +87,34 @@ The `useDataViewSelection` hook manages the selection state of the data view.
|
|
|
84
87
|
```js file="./SelectionExample.tsx"
|
|
85
88
|
|
|
86
89
|
```
|
|
90
|
+
|
|
91
|
+
# Filters
|
|
92
|
+
Enables filtering of data records in the data view and displays the applied filter chips.
|
|
93
|
+
|
|
94
|
+
### Toolbar usage
|
|
95
|
+
The data view toolbar can include a set of filters by passing a React node to the `filters` property. You can use predefined components `DataViewFilters` and `DataViewTextFilter` to customize and handle filtering directly in the toolbar. The `DataViewFilters` is a wrapper allowing conditional filtering using multiple attributes. If you need just a single filter, you can use `DataViewTextFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page.
|
|
96
|
+
|
|
97
|
+
You can decide between passing `value` and `onChange` event to every filter separately or pass `values` and `onChange` to the `DataViewFilters` wrapper which make them available to its children. Props directly passed to child filters have a higher priority than the "inherited" ones.
|
|
98
|
+
|
|
99
|
+
### Filters state
|
|
100
|
+
|
|
101
|
+
The `useDataViewFilters` hook manages the filter state of the data view. It allows you to define default filter values, synchronize filter state with URL parameters, and handle filter changes efficiently.
|
|
102
|
+
|
|
103
|
+
**Initial values:**
|
|
104
|
+
- `initialFilters` object with default filter values
|
|
105
|
+
- optional `searchParams` object for managing URL-based filter state
|
|
106
|
+
- optional `setSearchParams` function to update the URL when filters are modified
|
|
107
|
+
|
|
108
|
+
The `useDataViewFilters` hook works well with the React Router library to support URL-based filtering. Alternatively, you can manage filter state in the URL using `URLSearchParams` and `window.history.pushState` APIs, or other routing libraries. If no URL parameters are provided, the filter state is managed internally.
|
|
109
|
+
|
|
110
|
+
**Return values:**
|
|
111
|
+
- `filters` object representing the current filter values
|
|
112
|
+
- `onSetFilters` function to update the filter state
|
|
113
|
+
- `clearAllFilters` function to reset all filters to their initial values
|
|
114
|
+
|
|
115
|
+
### Filtering example
|
|
116
|
+
This example demonstrates the setup and usage of filters within the data view. It includes text filters for different attributes, the ability to clear all filters, and persistence of filter state in the URL.
|
|
117
|
+
|
|
118
|
+
```js file="./FiltersExample.tsx"
|
|
119
|
+
|
|
120
|
+
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Stack, StackItem } from '@patternfly/react-core';
|
|
2
|
+
import { Stack, StackItem, StackProps } from '@patternfly/react-core';
|
|
3
3
|
import { DataViewSelection, InternalContextProvider } from '../InternalContext';
|
|
4
4
|
|
|
5
5
|
export const DataViewState = {
|
|
@@ -10,7 +10,8 @@ export const DataViewState = {
|
|
|
10
10
|
|
|
11
11
|
export type DataViewState = typeof DataViewState[keyof typeof DataViewState];
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/** extends StackProps */
|
|
14
|
+
export interface DataViewProps extends StackProps {
|
|
14
15
|
/** Content rendered inside the data view */
|
|
15
16
|
children: React.ReactNode;
|
|
16
17
|
/** Custom OUIA ID */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import DataViewFilters from './DataViewFilters';
|
|
4
|
+
import DataViewToolbar from '../DataViewToolbar';
|
|
5
|
+
import DataViewTextFilter from '../DataViewTextFilter';
|
|
6
|
+
|
|
7
|
+
describe('DataViewFilters component', () => {
|
|
8
|
+
const mockOnChange = jest.fn();
|
|
9
|
+
|
|
10
|
+
it('should render correctly', () => {
|
|
11
|
+
const { container } = render(<DataViewToolbar
|
|
12
|
+
filters={
|
|
13
|
+
<DataViewFilters onChange={mockOnChange} values={{}}>
|
|
14
|
+
<DataViewTextFilter filterId="one" title="One" />
|
|
15
|
+
<DataViewTextFilter filterId="two" title="Two" />
|
|
16
|
+
</DataViewFilters>
|
|
17
|
+
}
|
|
18
|
+
/>);
|
|
19
|
+
expect(container).toMatchSnapshot();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, { useMemo, useState, useRef, useEffect, ReactElement } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Menu, MenuContent, MenuItem, MenuList, MenuToggle, Popper, ToolbarGroup, ToolbarToggleGroup, ToolbarToggleGroupProps,
|
|
4
|
+
} from '@patternfly/react-core';
|
|
5
|
+
import { FilterIcon } from '@patternfly/react-icons';
|
|
6
|
+
|
|
7
|
+
// helper interface to generate attribute menu
|
|
8
|
+
interface DataViewFilterIdentifiers {
|
|
9
|
+
filterId: string;
|
|
10
|
+
title: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** extends ToolbarToggleGroupProps */
|
|
14
|
+
export interface DataViewFiltersProps<T extends object> extends Omit<ToolbarToggleGroupProps, 'toggleIcon' | 'breakpoint' | 'onChange'> {
|
|
15
|
+
/** Content rendered inside the data view */
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
/** Optional onChange callback shared across filters */
|
|
18
|
+
onChange?: (key: string, newValues: Partial<T>) => void;
|
|
19
|
+
/** Optional values shared across filters */
|
|
20
|
+
values?: T;
|
|
21
|
+
/** Icon for the toolbar toggle group */
|
|
22
|
+
toggleIcon?: ToolbarToggleGroupProps['toggleIcon'];
|
|
23
|
+
/** Breakpoint for the toolbar toggle group */
|
|
24
|
+
breakpoint?: ToolbarToggleGroupProps['breakpoint'];
|
|
25
|
+
/** Custom OUIA ID */
|
|
26
|
+
ouiaId?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export const DataViewFilters = <T extends object>({
|
|
31
|
+
children,
|
|
32
|
+
ouiaId = 'DataViewFilters',
|
|
33
|
+
toggleIcon = <FilterIcon />,
|
|
34
|
+
breakpoint = 'xl',
|
|
35
|
+
onChange,
|
|
36
|
+
values,
|
|
37
|
+
...props
|
|
38
|
+
}: DataViewFiltersProps<T>) => {
|
|
39
|
+
const [ activeAttributeMenu, setActiveAttributeMenu ] = useState<string>('');
|
|
40
|
+
const [ isAttributeMenuOpen, setIsAttributeMenuOpen ] = useState(false);
|
|
41
|
+
const attributeToggleRef = useRef<HTMLButtonElement>(null);
|
|
42
|
+
const attributeMenuRef = useRef<HTMLDivElement>(null);
|
|
43
|
+
const attributeContainerRef = useRef<HTMLDivElement>(null);
|
|
44
|
+
|
|
45
|
+
const childrenHash = useMemo(() => JSON.stringify(
|
|
46
|
+
React.Children.map(children, (child) =>
|
|
47
|
+
React.isValidElement(child) ? { type: child.type, key: child.key, props: child.props } : child
|
|
48
|
+
)
|
|
49
|
+
), [ children ]);
|
|
50
|
+
|
|
51
|
+
const filterItems: DataViewFilterIdentifiers[] = useMemo(() => React.Children.toArray(children)
|
|
52
|
+
.map(child =>
|
|
53
|
+
React.isValidElement(child) ? { filterId: String(child.props.filterId), title: String(child.props.title) } : undefined
|
|
54
|
+
).filter((item): item is DataViewFilterIdentifiers => !!item), [ childrenHash ]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title);
|
|
58
|
+
}, [ filterItems ]);
|
|
59
|
+
|
|
60
|
+
const attributeToggle = (
|
|
61
|
+
<MenuToggle
|
|
62
|
+
ref={attributeToggleRef}
|
|
63
|
+
onClick={() => setIsAttributeMenuOpen(!isAttributeMenuOpen)}
|
|
64
|
+
isExpanded={isAttributeMenuOpen}
|
|
65
|
+
icon={toggleIcon}
|
|
66
|
+
>
|
|
67
|
+
{activeAttributeMenu}
|
|
68
|
+
</MenuToggle>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const attributeMenu = (
|
|
72
|
+
<Menu
|
|
73
|
+
ref={attributeMenuRef}
|
|
74
|
+
onSelect={(_ev, itemId) => {
|
|
75
|
+
const selectedItem = filterItems.find(item => item.filterId === itemId);
|
|
76
|
+
selectedItem && setActiveAttributeMenu(selectedItem.title);
|
|
77
|
+
setIsAttributeMenuOpen(false);
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<MenuContent>
|
|
81
|
+
<MenuList>
|
|
82
|
+
{filterItems.map(item => (
|
|
83
|
+
<MenuItem key={item.filterId} itemId={item.filterId}>
|
|
84
|
+
{item.title}
|
|
85
|
+
</MenuItem>
|
|
86
|
+
))}
|
|
87
|
+
</MenuList>
|
|
88
|
+
</MenuContent>
|
|
89
|
+
</Menu>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<ToolbarToggleGroup data-ouia-component-id={ouiaId} toggleIcon={toggleIcon} breakpoint={breakpoint} {...props}>
|
|
94
|
+
<ToolbarGroup variant="filter-group">
|
|
95
|
+
<div ref={attributeContainerRef}>
|
|
96
|
+
<Popper
|
|
97
|
+
trigger={attributeToggle}
|
|
98
|
+
triggerRef={attributeToggleRef}
|
|
99
|
+
popper={attributeMenu}
|
|
100
|
+
popperRef={attributeMenuRef}
|
|
101
|
+
appendTo={attributeContainerRef.current || undefined}
|
|
102
|
+
isVisible={isAttributeMenuOpen}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
{React.Children.map(children, (child) => (
|
|
106
|
+
React.isValidElement(child) ? (
|
|
107
|
+
React.cloneElement(child as ReactElement<{
|
|
108
|
+
showToolbarItem: boolean;
|
|
109
|
+
onChange: (_e: unknown, values: unknown) => void;
|
|
110
|
+
value: unknown;
|
|
111
|
+
}>, {
|
|
112
|
+
showToolbarItem: activeAttributeMenu === child.props.title,
|
|
113
|
+
onChange: (event, value) => onChange?.(event, { [child.props.filterId]: value } as Partial<T>),
|
|
114
|
+
value: values?.[child.props.filterId],
|
|
115
|
+
...child.props
|
|
116
|
+
})
|
|
117
|
+
) : child
|
|
118
|
+
))}
|
|
119
|
+
|
|
120
|
+
</ToolbarGroup>
|
|
121
|
+
</ToolbarToggleGroup>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default DataViewFilters;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`DataViewFilters component should render correctly 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="pf-v5-c-toolbar"
|
|
7
|
+
data-ouia-component-id="DataViewToolbar"
|
|
8
|
+
data-ouia-component-type="PF5/Toolbar"
|
|
9
|
+
data-ouia-safe="true"
|
|
10
|
+
id="pf-random-id-0"
|
|
11
|
+
>
|
|
12
|
+
<div
|
|
13
|
+
class="pf-v5-c-toolbar__content"
|
|
14
|
+
>
|
|
15
|
+
<div
|
|
16
|
+
class="pf-v5-c-toolbar__content-section"
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
class="pf-v5-c-toolbar__item pf-m-search-filter"
|
|
20
|
+
>
|
|
21
|
+
<div
|
|
22
|
+
class="pf-v5-c-toolbar__group pf-m-toggle-group pf-m-show-on-xl"
|
|
23
|
+
data-ouia-component-id="DataViewFilters"
|
|
24
|
+
>
|
|
25
|
+
<div
|
|
26
|
+
class="pf-v5-c-toolbar__toggle"
|
|
27
|
+
>
|
|
28
|
+
<button
|
|
29
|
+
aria-disabled="false"
|
|
30
|
+
aria-haspopup="false"
|
|
31
|
+
aria-label="Show Filters"
|
|
32
|
+
class="pf-v5-c-button pf-m-plain"
|
|
33
|
+
data-ouia-component-id="OUIA-Generated-Button-plain-1"
|
|
34
|
+
data-ouia-component-type="PF5/Button"
|
|
35
|
+
data-ouia-safe="true"
|
|
36
|
+
type="button"
|
|
37
|
+
>
|
|
38
|
+
<svg
|
|
39
|
+
aria-hidden="true"
|
|
40
|
+
class="pf-v5-svg"
|
|
41
|
+
fill="currentColor"
|
|
42
|
+
height="1em"
|
|
43
|
+
role="img"
|
|
44
|
+
viewBox="0 0 512 512"
|
|
45
|
+
width="1em"
|
|
46
|
+
>
|
|
47
|
+
<path
|
|
48
|
+
d="M487.976 0H24.028C2.71 0-8.047 25.866 7.058 40.971L192 225.941V432c0 7.831 3.821 15.17 10.237 19.662l80 55.98C298.02 518.69 320 507.493 320 487.98V225.941l184.947-184.97C520.021 25.896 509.338 0 487.976 0z"
|
|
49
|
+
/>
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
<div
|
|
54
|
+
class="pf-v5-c-toolbar__group pf-m-filter-group"
|
|
55
|
+
>
|
|
56
|
+
<div>
|
|
57
|
+
<button
|
|
58
|
+
aria-expanded="false"
|
|
59
|
+
class="pf-v5-c-menu-toggle"
|
|
60
|
+
data-ouia-component-id="OUIA-Generated-MenuToggle-1"
|
|
61
|
+
data-ouia-component-type="PF5/MenuToggle"
|
|
62
|
+
data-ouia-safe="true"
|
|
63
|
+
type="button"
|
|
64
|
+
>
|
|
65
|
+
<span
|
|
66
|
+
class="pf-v5-c-menu-toggle__icon"
|
|
67
|
+
>
|
|
68
|
+
<svg
|
|
69
|
+
aria-hidden="true"
|
|
70
|
+
class="pf-v5-svg"
|
|
71
|
+
fill="currentColor"
|
|
72
|
+
height="1em"
|
|
73
|
+
role="img"
|
|
74
|
+
viewBox="0 0 512 512"
|
|
75
|
+
width="1em"
|
|
76
|
+
>
|
|
77
|
+
<path
|
|
78
|
+
d="M487.976 0H24.028C2.71 0-8.047 25.866 7.058 40.971L192 225.941V432c0 7.831 3.821 15.17 10.237 19.662l80 55.98C298.02 518.69 320 507.493 320 487.98V225.941l184.947-184.97C520.021 25.896 509.338 0 487.976 0z"
|
|
79
|
+
/>
|
|
80
|
+
</svg>
|
|
81
|
+
</span>
|
|
82
|
+
<span
|
|
83
|
+
class="pf-v5-c-menu-toggle__text"
|
|
84
|
+
>
|
|
85
|
+
One
|
|
86
|
+
</span>
|
|
87
|
+
<span
|
|
88
|
+
class="pf-v5-c-menu-toggle__controls"
|
|
89
|
+
>
|
|
90
|
+
<span
|
|
91
|
+
class="pf-v5-c-menu-toggle__toggle-icon"
|
|
92
|
+
>
|
|
93
|
+
<svg
|
|
94
|
+
aria-hidden="true"
|
|
95
|
+
class="pf-v5-svg"
|
|
96
|
+
fill="currentColor"
|
|
97
|
+
height="1em"
|
|
98
|
+
role="img"
|
|
99
|
+
viewBox="0 0 320 512"
|
|
100
|
+
width="1em"
|
|
101
|
+
>
|
|
102
|
+
<path
|
|
103
|
+
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
|
104
|
+
/>
|
|
105
|
+
</svg>
|
|
106
|
+
</span>
|
|
107
|
+
</span>
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
<div
|
|
111
|
+
class="pf-v5-c-toolbar__item"
|
|
112
|
+
data-ouia-component-id="DataViewTextFilter"
|
|
113
|
+
>
|
|
114
|
+
<div
|
|
115
|
+
class="pf-v5-c-text-input-group"
|
|
116
|
+
data-ouia-component-id="DataViewTextFilter-input"
|
|
117
|
+
>
|
|
118
|
+
<div
|
|
119
|
+
class="pf-v5-c-text-input-group__main pf-m-icon"
|
|
120
|
+
>
|
|
121
|
+
<span
|
|
122
|
+
class="pf-v5-c-text-input-group__text"
|
|
123
|
+
>
|
|
124
|
+
<span
|
|
125
|
+
class="pf-v5-c-text-input-group__icon"
|
|
126
|
+
>
|
|
127
|
+
<svg
|
|
128
|
+
aria-hidden="true"
|
|
129
|
+
class="pf-v5-svg"
|
|
130
|
+
fill="currentColor"
|
|
131
|
+
height="1em"
|
|
132
|
+
role="img"
|
|
133
|
+
viewBox="0 0 512 512"
|
|
134
|
+
width="1em"
|
|
135
|
+
>
|
|
136
|
+
<path
|
|
137
|
+
d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
|
|
138
|
+
/>
|
|
139
|
+
</svg>
|
|
140
|
+
</span>
|
|
141
|
+
<input
|
|
142
|
+
aria-label="One filter"
|
|
143
|
+
class="pf-v5-c-text-input-group__text-input"
|
|
144
|
+
id="one"
|
|
145
|
+
placeholder="Filter by One"
|
|
146
|
+
type="text"
|
|
147
|
+
value=""
|
|
148
|
+
/>
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<div
|
|
159
|
+
class="pf-v5-c-toolbar__content pf-m-hidden"
|
|
160
|
+
hidden=""
|
|
161
|
+
>
|
|
162
|
+
<div
|
|
163
|
+
class="pf-v5-c-toolbar__group"
|
|
164
|
+
/>
|
|
165
|
+
<div
|
|
166
|
+
class="pf-v5-c-toolbar__item"
|
|
167
|
+
>
|
|
168
|
+
<button
|
|
169
|
+
aria-disabled="false"
|
|
170
|
+
class="pf-v5-c-button pf-m-link pf-m-inline"
|
|
171
|
+
data-ouia-component-id="DataViewToolbar-clear-all-filters"
|
|
172
|
+
data-ouia-component-type="PF5/Button"
|
|
173
|
+
data-ouia-safe="true"
|
|
174
|
+
type="button"
|
|
175
|
+
>
|
|
176
|
+
Clear filters
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
`;
|
|
@@ -8,18 +8,38 @@ import { DataViewTableTree, DataViewTableTreeProps } from '../DataViewTableTree'
|
|
|
8
8
|
import { DataViewTableBasic, DataViewTableBasicProps } from '../DataViewTableBasic';
|
|
9
9
|
|
|
10
10
|
// Table head typings
|
|
11
|
-
export type DataViewTh = ReactNode | {
|
|
11
|
+
export type DataViewTh = ReactNode | {
|
|
12
|
+
/** Table head cell node */
|
|
13
|
+
cell: ReactNode;
|
|
14
|
+
/** Props passed to Th */
|
|
15
|
+
props?: ThProps
|
|
16
|
+
};
|
|
12
17
|
export const isDataViewThObject = (value: DataViewTh): value is { cell: ReactNode; props?: ThProps } => value != null && typeof value === 'object' && 'cell' in value;
|
|
13
18
|
|
|
14
19
|
// Basic table typings
|
|
15
|
-
export interface DataViewTrObject {
|
|
16
|
-
|
|
20
|
+
export interface DataViewTrObject {
|
|
21
|
+
/** Array of rows */
|
|
22
|
+
row: DataViewTd[],
|
|
23
|
+
/** Unique identifier of a row */
|
|
24
|
+
id?: string,
|
|
25
|
+
/** Props passed to Tr */
|
|
26
|
+
props?: TrProps
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type DataViewTd = ReactNode | {
|
|
30
|
+
/** Table body cell node */
|
|
31
|
+
cell: ReactNode;
|
|
32
|
+
/** Props passed to Td */
|
|
33
|
+
props?: TdProps
|
|
34
|
+
};
|
|
35
|
+
|
|
17
36
|
export type DataViewTr = DataViewTd[] | DataViewTrObject;
|
|
18
37
|
|
|
19
38
|
export const isDataViewTdObject = (value: DataViewTd): value is { cell: ReactNode; props?: TdProps } => value != null && typeof value === 'object' && 'cell' in value;
|
|
20
39
|
export const isDataViewTrObject = (value: DataViewTr): value is { row: DataViewTd[], id?: string } => value != null && typeof value === 'object' && 'row' in value;
|
|
21
40
|
|
|
22
41
|
// Tree table typings
|
|
42
|
+
/** extends DataViewTrObject */
|
|
23
43
|
export interface DataViewTrTree extends DataViewTrObject { id: string, children?: DataViewTrTree[] }
|
|
24
44
|
|
|
25
45
|
export type DataViewTableProps =
|
|
@@ -11,6 +11,7 @@ import { DataViewTableHead } from '../DataViewTableHead';
|
|
|
11
11
|
import { DataViewTh, DataViewTr, isDataViewTdObject, isDataViewTrObject } from '../DataViewTable';
|
|
12
12
|
import { DataViewState } from '../DataView/DataView';
|
|
13
13
|
|
|
14
|
+
/** extends TableProps */
|
|
14
15
|
export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | 'rows'> {
|
|
15
16
|
/** Columns definition */
|
|
16
17
|
columns: DataViewTh[];
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { useInternalContext } from '../InternalContext';
|
|
9
9
|
import { DataViewTh, isDataViewThObject } from '../DataViewTable';
|
|
10
10
|
|
|
11
|
+
/** extends TheadProps */
|
|
11
12
|
export interface DataViewTableHeadProps extends TheadProps {
|
|
12
13
|
/** Indicates whether table is a tree */
|
|
13
14
|
isTreeTable?: boolean;
|
|
@@ -30,6 +30,7 @@ const isNodeChecked = (node: DataViewTrTree, isSelected: (node: DataViewTrTree)
|
|
|
30
30
|
return allSelected;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
/** extends TableProps */
|
|
33
34
|
export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'rows'> {
|
|
34
35
|
/** Columns definition */
|
|
35
36
|
columns: DataViewTh[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import DataViewTextFilter, { DataViewTextFilterProps } from './DataViewTextFilter';
|
|
4
|
+
import DataViewToolbar from '../DataViewToolbar';
|
|
5
|
+
|
|
6
|
+
describe('DataViewTextFilter component', () => {
|
|
7
|
+
const mockOnChange = jest.fn();
|
|
8
|
+
|
|
9
|
+
const defaultProps: DataViewTextFilterProps = {
|
|
10
|
+
filterId: 'test-filter',
|
|
11
|
+
title: 'Test Filter',
|
|
12
|
+
value: 'initial value',
|
|
13
|
+
onChange: mockOnChange,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
it('should render correctly', () => {
|
|
17
|
+
const { container } = render(<DataViewToolbar
|
|
18
|
+
filters={
|
|
19
|
+
<DataViewTextFilter {...defaultProps} />
|
|
20
|
+
}
|
|
21
|
+
/>);
|
|
22
|
+
expect(container).toMatchSnapshot();
|
|
23
|
+
});
|
|
24
|
+
});
|