@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.
Files changed (92) hide show
  1. package/dist/cjs/DataView/DataView.d.ts +3 -1
  2. package/dist/cjs/DataViewFilters/DataViewFilters.d.ts +19 -0
  3. package/dist/cjs/DataViewFilters/DataViewFilters.js +70 -0
  4. package/dist/cjs/DataViewFilters/DataViewFilters.test.d.ts +1 -0
  5. package/dist/cjs/DataViewFilters/DataViewFilters.test.js +19 -0
  6. package/dist/cjs/DataViewFilters/index.d.ts +2 -0
  7. package/dist/cjs/DataViewFilters/index.js +23 -0
  8. package/dist/cjs/DataViewTable/DataViewTable.d.ts +8 -0
  9. package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +1 -0
  10. package/dist/cjs/DataViewTableHead/DataViewTableHead.d.ts +1 -0
  11. package/dist/cjs/DataViewTableTree/DataViewTableTree.d.ts +1 -0
  12. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.d.ts +21 -0
  13. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +26 -0
  14. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -0
  15. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.js +22 -0
  16. package/dist/cjs/DataViewTextFilter/index.d.ts +2 -0
  17. package/dist/cjs/DataViewTextFilter/index.js +23 -0
  18. package/dist/cjs/DataViewToolbar/DataViewToolbar.d.ts +10 -4
  19. package/dist/cjs/DataViewToolbar/DataViewToolbar.js +29 -6
  20. package/dist/cjs/Hooks/filters.d.ts +14 -0
  21. package/dist/cjs/Hooks/filters.js +70 -0
  22. package/dist/cjs/Hooks/filters.test.d.ts +1 -0
  23. package/dist/cjs/Hooks/filters.test.js +50 -0
  24. package/dist/cjs/Hooks/index.d.ts +1 -0
  25. package/dist/cjs/Hooks/index.js +1 -0
  26. package/dist/cjs/Hooks/pagination.d.ts +1 -0
  27. package/dist/cjs/Hooks/selection.d.ts +1 -1
  28. package/dist/cjs/Hooks/selection.js +4 -2
  29. package/dist/cjs/InternalContext/InternalContext.d.ts +1 -0
  30. package/dist/cjs/index.d.ts +2 -0
  31. package/dist/cjs/index.js +4 -1
  32. package/dist/dynamic/DataViewFilters/package.json +1 -0
  33. package/dist/dynamic/DataViewTextFilter/package.json +1 -0
  34. package/dist/esm/DataView/DataView.d.ts +3 -1
  35. package/dist/esm/DataViewFilters/DataViewFilters.d.ts +19 -0
  36. package/dist/esm/DataViewFilters/DataViewFilters.js +43 -0
  37. package/dist/esm/DataViewFilters/DataViewFilters.test.d.ts +1 -0
  38. package/dist/esm/DataViewFilters/DataViewFilters.test.js +14 -0
  39. package/dist/esm/DataViewFilters/index.d.ts +2 -0
  40. package/dist/esm/DataViewFilters/index.js +2 -0
  41. package/dist/esm/DataViewTable/DataViewTable.d.ts +8 -0
  42. package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +1 -0
  43. package/dist/esm/DataViewTableHead/DataViewTableHead.d.ts +1 -0
  44. package/dist/esm/DataViewTableTree/DataViewTableTree.d.ts +1 -0
  45. package/dist/esm/DataViewTextFilter/DataViewTextFilter.d.ts +21 -0
  46. package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +19 -0
  47. package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -0
  48. package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.js +17 -0
  49. package/dist/esm/DataViewTextFilter/index.d.ts +2 -0
  50. package/dist/esm/DataViewTextFilter/index.js +2 -0
  51. package/dist/esm/DataViewToolbar/DataViewToolbar.d.ts +10 -4
  52. package/dist/esm/DataViewToolbar/DataViewToolbar.js +7 -4
  53. package/dist/esm/Hooks/filters.d.ts +14 -0
  54. package/dist/esm/Hooks/filters.js +66 -0
  55. package/dist/esm/Hooks/filters.test.d.ts +1 -0
  56. package/dist/esm/Hooks/filters.test.js +48 -0
  57. package/dist/esm/Hooks/index.d.ts +1 -0
  58. package/dist/esm/Hooks/index.js +1 -0
  59. package/dist/esm/Hooks/pagination.d.ts +1 -0
  60. package/dist/esm/Hooks/selection.d.ts +1 -1
  61. package/dist/esm/Hooks/selection.js +4 -2
  62. package/dist/esm/InternalContext/InternalContext.d.ts +1 -0
  63. package/dist/esm/index.d.ts +2 -0
  64. package/dist/esm/index.js +2 -0
  65. package/package.json +1 -1
  66. package/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +5 -3
  67. package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md +1 -0
  68. package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +27 -5
  69. package/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx +92 -0
  70. package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +36 -2
  71. package/src/DataView/DataView.tsx +3 -2
  72. package/src/DataViewFilters/DataViewFilters.test.tsx +21 -0
  73. package/src/DataViewFilters/DataViewFilters.tsx +125 -0
  74. package/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap +182 -0
  75. package/src/DataViewFilters/index.tsx +2 -0
  76. package/src/DataViewTable/DataViewTable.tsx +23 -3
  77. package/src/DataViewTableBasic/DataViewTableBasic.tsx +1 -0
  78. package/src/DataViewTableHead/DataViewTableHead.tsx +1 -0
  79. package/src/DataViewTableTree/DataViewTableTree.tsx +1 -0
  80. package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +24 -0
  81. package/src/DataViewTextFilter/DataViewTextFilter.tsx +53 -0
  82. package/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap +196 -0
  83. package/src/DataViewTextFilter/index.ts +2 -0
  84. package/src/DataViewToolbar/DataViewToolbar.tsx +47 -28
  85. package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +28 -0
  86. package/src/Hooks/filters.test.tsx +62 -0
  87. package/src/Hooks/filters.ts +96 -0
  88. package/src/Hooks/index.ts +1 -0
  89. package/src/Hooks/pagination.ts +1 -0
  90. package/src/Hooks/selection.ts +3 -2
  91. package/src/InternalContext/InternalContext.tsx +1 -0
  92. package/src/index.ts +3 -0
@@ -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
- trigger(EventTypes.rowClick, repo);
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
  );
@@ -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
+ )
@@ -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 { useDataViewPagination, useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
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 { BrowserRouter, useSearchParams } from 'react-router-dom';
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
- export interface DataViewProps {
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
+ `;
@@ -0,0 +1,2 @@
1
+ export { default } from './DataViewFilters';
2
+ export * from './DataViewFilters';
@@ -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 | { cell: ReactNode; props?: ThProps };
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 { row: DataViewTd[], id?: string, props?: TrProps }
16
- export type DataViewTd = ReactNode | { cell: ReactNode; props?: TdProps };
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
+ });