@patternfly/react-data-view 7.0.0-prerelease.3 → 7.0.0-prerelease.5

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 (124) hide show
  1. package/dist/cjs/DataView/DataView.d.ts +3 -1
  2. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +29 -0
  3. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.js +70 -0
  4. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -0
  5. package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +25 -0
  6. package/dist/cjs/DataViewCheckboxFilter/index.d.ts +2 -0
  7. package/dist/cjs/DataViewCheckboxFilter/index.js +23 -0
  8. package/dist/cjs/DataViewFilters/DataViewFilters.d.ts +25 -0
  9. package/dist/cjs/DataViewFilters/DataViewFilters.js +85 -0
  10. package/dist/cjs/DataViewFilters/DataViewFilters.test.d.ts +1 -0
  11. package/dist/cjs/DataViewFilters/DataViewFilters.test.js +19 -0
  12. package/dist/cjs/DataViewFilters/index.d.ts +2 -0
  13. package/dist/cjs/DataViewFilters/index.js +23 -0
  14. package/dist/cjs/DataViewTable/DataViewTable.d.ts +8 -0
  15. package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +1 -0
  16. package/dist/cjs/DataViewTableHead/DataViewTableHead.d.ts +1 -0
  17. package/dist/cjs/DataViewTableTree/DataViewTableTree.d.ts +1 -0
  18. package/dist/cjs/DataViewTableTree/DataViewTableTree.js +26 -14
  19. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.d.ts +21 -0
  20. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +26 -0
  21. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -0
  22. package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.js +22 -0
  23. package/dist/cjs/DataViewTextFilter/index.d.ts +2 -0
  24. package/dist/cjs/DataViewTextFilter/index.js +23 -0
  25. package/dist/cjs/DataViewToolbar/DataViewToolbar.d.ts +10 -4
  26. package/dist/cjs/DataViewToolbar/DataViewToolbar.js +29 -6
  27. package/dist/cjs/Hooks/filters.d.ts +14 -0
  28. package/dist/cjs/Hooks/filters.js +69 -0
  29. package/dist/cjs/Hooks/filters.test.d.ts +1 -0
  30. package/dist/cjs/Hooks/filters.test.js +50 -0
  31. package/dist/cjs/Hooks/index.d.ts +2 -0
  32. package/dist/cjs/Hooks/index.js +2 -0
  33. package/dist/cjs/Hooks/pagination.d.ts +1 -0
  34. package/dist/cjs/Hooks/selection.d.ts +1 -1
  35. package/dist/cjs/Hooks/selection.js +4 -2
  36. package/dist/cjs/Hooks/sort.d.ts +32 -0
  37. package/dist/cjs/Hooks/sort.js +47 -0
  38. package/dist/cjs/Hooks/sort.test.d.ts +1 -0
  39. package/dist/cjs/Hooks/sort.test.js +68 -0
  40. package/dist/cjs/InternalContext/InternalContext.d.ts +1 -0
  41. package/dist/cjs/index.d.ts +4 -0
  42. package/dist/cjs/index.js +7 -1
  43. package/dist/dynamic/DataViewCheckboxFilter/package.json +1 -0
  44. package/dist/dynamic/DataViewFilters/package.json +1 -0
  45. package/dist/dynamic/DataViewTextFilter/package.json +1 -0
  46. package/dist/esm/DataView/DataView.d.ts +3 -1
  47. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +29 -0
  48. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.js +62 -0
  49. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -0
  50. package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +20 -0
  51. package/dist/esm/DataViewCheckboxFilter/index.d.ts +2 -0
  52. package/dist/esm/DataViewCheckboxFilter/index.js +2 -0
  53. package/dist/esm/DataViewFilters/DataViewFilters.d.ts +25 -0
  54. package/dist/esm/DataViewFilters/DataViewFilters.js +58 -0
  55. package/dist/esm/DataViewFilters/DataViewFilters.test.d.ts +1 -0
  56. package/dist/esm/DataViewFilters/DataViewFilters.test.js +14 -0
  57. package/dist/esm/DataViewFilters/index.d.ts +2 -0
  58. package/dist/esm/DataViewFilters/index.js +2 -0
  59. package/dist/esm/DataViewTable/DataViewTable.d.ts +8 -0
  60. package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +1 -0
  61. package/dist/esm/DataViewTableHead/DataViewTableHead.d.ts +1 -0
  62. package/dist/esm/DataViewTableTree/DataViewTableTree.d.ts +1 -0
  63. package/dist/esm/DataViewTableTree/DataViewTableTree.js +26 -14
  64. package/dist/esm/DataViewTextFilter/DataViewTextFilter.d.ts +21 -0
  65. package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +19 -0
  66. package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -0
  67. package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.js +17 -0
  68. package/dist/esm/DataViewTextFilter/index.d.ts +2 -0
  69. package/dist/esm/DataViewTextFilter/index.js +2 -0
  70. package/dist/esm/DataViewToolbar/DataViewToolbar.d.ts +10 -4
  71. package/dist/esm/DataViewToolbar/DataViewToolbar.js +7 -4
  72. package/dist/esm/Hooks/filters.d.ts +14 -0
  73. package/dist/esm/Hooks/filters.js +65 -0
  74. package/dist/esm/Hooks/filters.test.d.ts +1 -0
  75. package/dist/esm/Hooks/filters.test.js +48 -0
  76. package/dist/esm/Hooks/index.d.ts +2 -0
  77. package/dist/esm/Hooks/index.js +2 -0
  78. package/dist/esm/Hooks/pagination.d.ts +1 -0
  79. package/dist/esm/Hooks/selection.d.ts +1 -1
  80. package/dist/esm/Hooks/selection.js +4 -2
  81. package/dist/esm/Hooks/sort.d.ts +32 -0
  82. package/dist/esm/Hooks/sort.js +43 -0
  83. package/dist/esm/Hooks/sort.test.d.ts +1 -0
  84. package/dist/esm/Hooks/sort.test.js +66 -0
  85. package/dist/esm/InternalContext/InternalContext.d.ts +1 -0
  86. package/dist/esm/index.d.ts +4 -0
  87. package/dist/esm/index.js +4 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +10 -9
  90. package/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +5 -3
  91. package/patternfly-docs/content/extensions/data-view/examples/Components/DataViewTableExample.tsx +1 -1
  92. package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md +1 -0
  93. package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +28 -6
  94. package/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx +107 -0
  95. package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +67 -2
  96. package/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx +87 -0
  97. package/src/DataView/DataView.tsx +3 -2
  98. package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx +24 -0
  99. package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx +175 -0
  100. package/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap +197 -0
  101. package/src/DataViewCheckboxFilter/index.ts +2 -0
  102. package/src/DataViewFilters/DataViewFilters.test.tsx +21 -0
  103. package/src/DataViewFilters/DataViewFilters.tsx +144 -0
  104. package/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap +194 -0
  105. package/src/DataViewFilters/index.tsx +2 -0
  106. package/src/DataViewTable/DataViewTable.tsx +23 -3
  107. package/src/DataViewTableBasic/DataViewTableBasic.tsx +1 -0
  108. package/src/DataViewTableHead/DataViewTableHead.tsx +1 -0
  109. package/src/DataViewTableTree/DataViewTableTree.tsx +40 -18
  110. package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +24 -0
  111. package/src/DataViewTextFilter/DataViewTextFilter.tsx +54 -0
  112. package/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap +203 -0
  113. package/src/DataViewTextFilter/index.ts +2 -0
  114. package/src/DataViewToolbar/DataViewToolbar.tsx +47 -28
  115. package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +44 -0
  116. package/src/Hooks/filters.test.tsx +62 -0
  117. package/src/Hooks/filters.ts +97 -0
  118. package/src/Hooks/index.ts +2 -0
  119. package/src/Hooks/pagination.ts +1 -0
  120. package/src/Hooks/selection.ts +3 -2
  121. package/src/Hooks/sort.test.tsx +84 -0
  122. package/src/Hooks/sort.ts +87 -0
  123. package/src/InternalContext/InternalContext.tsx +1 -0
  124. package/src/index.ts +6 -0
@@ -0,0 +1,175 @@
1
+ import React from 'react';
2
+ import {
3
+ Badge,
4
+ Menu,
5
+ MenuContent,
6
+ MenuItem,
7
+ MenuList,
8
+ MenuProps,
9
+ MenuToggle,
10
+ Popper,
11
+ ToolbarLabel,
12
+ ToolbarFilter,
13
+ } from '@patternfly/react-core';
14
+ import { FilterIcon } from '@patternfly/react-icons';
15
+ import { DataViewFilterOption } from '../DataViewFilters';
16
+
17
+ const isToolbarLabel = (label: string | ToolbarLabel): label is ToolbarLabel =>
18
+ typeof label === 'object' && 'key' in label;
19
+
20
+ export const isDataViewFilterOption = (obj: unknown): obj is DataViewFilterOption =>
21
+ !!obj &&
22
+ typeof obj === 'object' &&
23
+ 'label' in obj &&
24
+ 'value' in obj &&
25
+ typeof (obj as DataViewFilterOption).value === 'string';
26
+
27
+ /** extends MenuProps */
28
+ export interface DataViewCheckboxFilterProps extends Omit<MenuProps, 'onSelect' | 'onChange'> {
29
+ /** Unique key for the filter attribute */
30
+ filterId: string;
31
+ /** Array of current filter values */
32
+ value?: string[];
33
+ /** Filter title displayed in the toolbar */
34
+ title: string;
35
+ /** Placeholder text of the menu */
36
+ placeholder?: string;
37
+ /** Filter options displayed */
38
+ options: (DataViewFilterOption | string)[];
39
+ /** Callback for updating when item selection changes. */
40
+ onChange?: (event?: React.MouseEvent, values?: string[]) => void;
41
+ /** Controls visibility of the filter in the toolbar */
42
+ showToolbarItem?: boolean;
43
+ /** Controls visibility of the filter icon */
44
+ showIcon?: boolean;
45
+ /** Controls visibility of the selected items badge */
46
+ showBadge?: boolean;
47
+ /** Custom OUIA ID */
48
+ ouiaId?: string;
49
+ }
50
+
51
+ export const DataViewCheckboxFilter: React.FC<DataViewCheckboxFilterProps> = ({
52
+ filterId,
53
+ title,
54
+ value = [],
55
+ onChange,
56
+ placeholder,
57
+ options = [],
58
+ showToolbarItem,
59
+ showIcon = !placeholder,
60
+ showBadge = !placeholder,
61
+ ouiaId = 'DataViewCheckboxFilter',
62
+ ...props
63
+ }: DataViewCheckboxFilterProps) => {
64
+ const [ isOpen, setIsOpen ] = React.useState(false);
65
+ const toggleRef = React.useRef<HTMLButtonElement>(null);
66
+ const menuRef = React.useRef<HTMLDivElement>(null);
67
+ const containerRef = React.useRef<HTMLDivElement>(null);
68
+
69
+ const normalizeOptions = React.useMemo(
70
+ () =>
71
+ options.map(option =>
72
+ typeof option === 'string'
73
+ ? { label: option, value: option }
74
+ : option
75
+ ),
76
+ [ options ]
77
+ );
78
+
79
+ const handleToggleClick = (event: React.MouseEvent) => {
80
+ event.stopPropagation();
81
+ setTimeout(() => {
82
+ const firstElement = menuRef.current?.querySelector('li > button:not(:disabled)') as HTMLElement;
83
+ firstElement?.focus();
84
+ }, 0);
85
+ setIsOpen(prev => !prev);
86
+ };
87
+
88
+ const handleSelect = (event?: React.MouseEvent, itemId?: string | number) => {
89
+ const activeItem = String(itemId);
90
+ const isSelected = value.includes(activeItem);
91
+
92
+ onChange?.(
93
+ event,
94
+ isSelected ? value.filter(item => item !== activeItem) : [ activeItem, ...value ]
95
+ );
96
+ };
97
+
98
+ const handleClickOutside = (event: MouseEvent) =>
99
+ isOpen &&
100
+ menuRef.current && toggleRef.current &&
101
+ !menuRef.current.contains(event.target as Node) && !toggleRef.current.contains(event.target as Node)
102
+ && setIsOpen(false);
103
+
104
+
105
+ React.useEffect(() => {
106
+ window.addEventListener('click', handleClickOutside);
107
+ return () => {
108
+ window.removeEventListener('click', handleClickOutside);
109
+ };
110
+ }, [ isOpen ]); // eslint-disable-line react-hooks/exhaustive-deps
111
+
112
+ return (
113
+ <ToolbarFilter
114
+ key={ouiaId}
115
+ data-ouia-component-id={ouiaId}
116
+ labels={value.map(item => {
117
+ const activeOption = normalizeOptions.find(option => option.value === item);
118
+ return ({ key: activeOption?.value as string, node: activeOption?.label })
119
+ })}
120
+ deleteLabel={(_, label) =>
121
+ onChange?.(undefined, value.filter(item => item !== (isToolbarLabel(label) ? label.key : label)))
122
+ }
123
+ categoryName={title}
124
+ showToolbarItem={showToolbarItem}
125
+ >
126
+ <Popper
127
+ trigger={
128
+ <MenuToggle
129
+ ouiaId={`${ouiaId}-toggle`}
130
+ ref={toggleRef}
131
+ onClick={handleToggleClick}
132
+ isExpanded={isOpen}
133
+ icon={showIcon ? <FilterIcon /> : undefined}
134
+ badge={value.length > 0 && showBadge ? <Badge data-ouia-component-id={`${ouiaId}-badge`} isRead>{value.length}</Badge> : undefined}
135
+ style={{ width: '200px' }}
136
+ >
137
+ {placeholder ?? title}
138
+ </MenuToggle>
139
+ }
140
+ triggerRef={toggleRef}
141
+ popper={
142
+ <Menu
143
+ ref={menuRef}
144
+ ouiaId={`${ouiaId}-menu`}
145
+ onSelect={handleSelect}
146
+ selected={value}
147
+ {...props}
148
+ >
149
+ <MenuContent>
150
+ <MenuList>
151
+ {normalizeOptions.map(option => (
152
+ <MenuItem
153
+ data-ouia-component-id={`${ouiaId}-filter-item-${option.value}`}
154
+ key={option.value}
155
+ itemId={option.value}
156
+ isSelected={value.includes(option.value)}
157
+ hasCheckbox
158
+ >
159
+ {option.label}
160
+ </MenuItem>
161
+ ))}
162
+ </MenuList>
163
+ </MenuContent>
164
+ </Menu>
165
+ }
166
+ popperRef={menuRef}
167
+ appendTo={containerRef.current || undefined}
168
+ aria-label={`${title ?? filterId} filter`}
169
+ isVisible={isOpen}
170
+ />
171
+ </ToolbarFilter>
172
+ );
173
+ };
174
+
175
+ export default DataViewCheckboxFilter;
@@ -0,0 +1,197 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`DataViewCheckboxFilter component should render correctly 1`] = `
4
+ <div>
5
+ <div
6
+ class="pf-v6-c-toolbar"
7
+ data-ouia-component-id="DataViewToolbar"
8
+ data-ouia-component-type="PF6/Toolbar"
9
+ data-ouia-safe="true"
10
+ id="pf-random-id-0"
11
+ >
12
+ <div
13
+ class="pf-v6-c-toolbar__content"
14
+ >
15
+ <div
16
+ class="pf-v6-c-toolbar__content-section"
17
+ >
18
+ <div
19
+ class="pf-v6-c-toolbar__item"
20
+ >
21
+ <div
22
+ class="pf-v6-c-toolbar__item"
23
+ data-ouia-component-id="DataViewCheckboxFilter"
24
+ >
25
+ <button
26
+ aria-expanded="false"
27
+ class="pf-v6-c-menu-toggle"
28
+ data-ouia-component-id="DataViewCheckboxFilter-toggle"
29
+ data-ouia-component-type="PF6/MenuToggle"
30
+ data-ouia-safe="true"
31
+ style="width: 200px;"
32
+ type="button"
33
+ >
34
+ <span
35
+ class="pf-v6-c-menu-toggle__icon"
36
+ >
37
+ <svg
38
+ aria-hidden="true"
39
+ class="pf-v6-svg"
40
+ fill="currentColor"
41
+ height="1em"
42
+ role="img"
43
+ viewBox="0 0 512 512"
44
+ width="1em"
45
+ >
46
+ <path
47
+ 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"
48
+ />
49
+ </svg>
50
+ </span>
51
+ <span
52
+ class="pf-v6-c-menu-toggle__text"
53
+ >
54
+ Test Checkbox Filter
55
+ </span>
56
+ <span
57
+ class="pf-v6-c-menu-toggle__count"
58
+ >
59
+ <span
60
+ class="pf-v6-c-badge pf-m-read"
61
+ data-ouia-component-id="DataViewCheckboxFilter-badge"
62
+ >
63
+ 1
64
+ </span>
65
+ </span>
66
+ <span
67
+ class="pf-v6-c-menu-toggle__controls"
68
+ >
69
+ <span
70
+ class="pf-v6-c-menu-toggle__toggle-icon"
71
+ >
72
+ <svg
73
+ aria-hidden="true"
74
+ class="pf-v6-svg"
75
+ fill="currentColor"
76
+ height="1em"
77
+ role="img"
78
+ viewBox="0 0 320 512"
79
+ width="1em"
80
+ >
81
+ <path
82
+ 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"
83
+ />
84
+ </svg>
85
+ </span>
86
+ </span>
87
+ </button>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ <div
93
+ class="pf-v6-c-toolbar__content"
94
+ >
95
+ <div
96
+ class="pf-v6-c-toolbar__group"
97
+ >
98
+ <div
99
+ class="pf-v6-c-toolbar__item pf-m-label-group pf-m-label-group"
100
+ >
101
+ <div
102
+ class="pf-v6-c-label-group pf-m-category"
103
+ >
104
+ <div
105
+ class="pf-v6-c-label-group__main"
106
+ >
107
+ <span
108
+ aria-hidden="true"
109
+ class="pf-v6-c-label-group__label"
110
+ id="pf-random-id-1"
111
+ >
112
+ Test Checkbox Filter
113
+ </span>
114
+ <ul
115
+ aria-labelledby="pf-random-id-1"
116
+ class="pf-v6-c-label-group__list"
117
+ role="list"
118
+ >
119
+ <li
120
+ class="pf-v6-c-label-group__list-item"
121
+ >
122
+ <span
123
+ class="pf-v6-c-label pf-m-filled"
124
+ >
125
+ <span
126
+ class="pf-v6-c-label__content"
127
+ >
128
+ <span
129
+ class="pf-v6-c-label__text"
130
+ >
131
+ Workspace one
132
+ </span>
133
+ </span>
134
+ <span
135
+ class="pf-v6-c-label__actions"
136
+ >
137
+ <button
138
+ aria-disabled="false"
139
+ aria-label="Close Workspace one"
140
+ class="pf-v6-c-button pf-m-plain pf-m-no-padding"
141
+ data-ouia-component-id="OUIA-Generated-Button-plain-1"
142
+ data-ouia-component-type="PF6/Button"
143
+ data-ouia-safe="true"
144
+ type="button"
145
+ >
146
+ <span
147
+ class="pf-v6-c-button__icon"
148
+ >
149
+ <svg
150
+ aria-hidden="true"
151
+ class="pf-v6-svg"
152
+ fill="currentColor"
153
+ height="1em"
154
+ role="img"
155
+ viewBox="0 0 352 512"
156
+ width="1em"
157
+ >
158
+ <path
159
+ d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
160
+ />
161
+ </svg>
162
+ </span>
163
+ </button>
164
+ </span>
165
+ </span>
166
+ </li>
167
+ </ul>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ <div
173
+ class="pf-v6-c-toolbar__group pf-m-action-group-inline"
174
+ >
175
+ <div
176
+ class="pf-v6-c-toolbar__item"
177
+ >
178
+ <button
179
+ aria-disabled="false"
180
+ class="pf-v6-c-button pf-m-link pf-m-inline"
181
+ data-ouia-component-id="DataViewToolbar-clear-all-filters"
182
+ data-ouia-component-type="PF6/Button"
183
+ data-ouia-safe="true"
184
+ type="button"
185
+ >
186
+ <span
187
+ class="pf-v6-c-button__text"
188
+ >
189
+ Clear filters
190
+ </span>
191
+ </button>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ `;
@@ -0,0 +1,2 @@
1
+ export { default } from './DataViewCheckboxFilter';
2
+ export * from './DataViewCheckboxFilter';
@@ -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,144 @@
1
+ import React, { useMemo, useState, useRef, useEffect, ReactElement, ReactNode } 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
+ export interface DataViewFilterOption {
8
+ /** Filter option label */
9
+ label: ReactNode;
10
+ /** Filter option value */
11
+ value: string;
12
+ }
13
+
14
+ // helper interface to generate attribute menu
15
+ interface DataViewFilterIdentifiers {
16
+ filterId: string;
17
+ title: string;
18
+ }
19
+
20
+ /** extends ToolbarToggleGroupProps */
21
+ export interface DataViewFiltersProps<T extends object> extends Omit<ToolbarToggleGroupProps, 'toggleIcon' | 'breakpoint' | 'onChange'> {
22
+ /** Content rendered inside the data view */
23
+ children: React.ReactNode;
24
+ /** Optional onChange callback shared across filters */
25
+ onChange?: (key: string, newValues: Partial<T>) => void;
26
+ /** Optional values shared across filters */
27
+ values?: T;
28
+ /** Icon for the toolbar toggle group */
29
+ toggleIcon?: ToolbarToggleGroupProps['toggleIcon'];
30
+ /** Breakpoint for the toolbar toggle group */
31
+ breakpoint?: ToolbarToggleGroupProps['breakpoint'];
32
+ /** Custom OUIA ID */
33
+ ouiaId?: string;
34
+ };
35
+
36
+
37
+ export const DataViewFilters = <T extends object>({
38
+ children,
39
+ ouiaId = 'DataViewFilters',
40
+ toggleIcon = <FilterIcon />,
41
+ breakpoint = 'xl',
42
+ onChange,
43
+ values,
44
+ ...props
45
+ }: DataViewFiltersProps<T>) => {
46
+ const [ activeAttributeMenu, setActiveAttributeMenu ] = useState<string>('');
47
+ const [ isAttributeMenuOpen, setIsAttributeMenuOpen ] = useState(false);
48
+ const attributeToggleRef = useRef<HTMLButtonElement>(null);
49
+ const attributeMenuRef = useRef<HTMLDivElement>(null);
50
+ const attributeContainerRef = useRef<HTMLDivElement>(null);
51
+
52
+ const childrenHash = useMemo(() => JSON.stringify(
53
+ React.Children.map(children, (child) =>
54
+ React.isValidElement(child) ? { type: child.type, key: child.key, props: child.props } : child
55
+ )
56
+ ), [ children ]);
57
+
58
+ const filterItems: DataViewFilterIdentifiers[] = useMemo(() => React.Children.toArray(children)
59
+ .map(child =>
60
+ React.isValidElement(child) ? { filterId: String(child.props.filterId), title: String(child.props.title) } : undefined
61
+ ).filter((item): item is DataViewFilterIdentifiers => !!item), [ childrenHash ]); // eslint-disable-line react-hooks/exhaustive-deps
62
+
63
+ useEffect(() => {
64
+ filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title);
65
+ }, [ filterItems ]);
66
+
67
+ const handleClickOutside = (event: MouseEvent) =>
68
+ isAttributeMenuOpen &&
69
+ !attributeMenuRef.current?.contains(event.target as Node) &&
70
+ !attributeToggleRef.current?.contains(event.target as Node)
71
+ && setIsAttributeMenuOpen(false);
72
+
73
+ useEffect(() => {
74
+ window.addEventListener('click', handleClickOutside);
75
+ return () => {
76
+ window.removeEventListener('click', handleClickOutside);
77
+ };
78
+ }, [ isAttributeMenuOpen ]); // eslint-disable-line react-hooks/exhaustive-deps
79
+
80
+ const attributeToggle = (
81
+ <MenuToggle
82
+ ref={attributeToggleRef}
83
+ onClick={() => setIsAttributeMenuOpen(!isAttributeMenuOpen)}
84
+ isExpanded={isAttributeMenuOpen}
85
+ icon={toggleIcon}
86
+ >
87
+ {activeAttributeMenu}
88
+ </MenuToggle>
89
+ );
90
+
91
+ const attributeMenu = (
92
+ <Menu
93
+ ref={attributeMenuRef}
94
+ onSelect={(_ev, itemId) => {
95
+ const selectedItem = filterItems.find(item => item.filterId === itemId);
96
+ selectedItem && setActiveAttributeMenu(selectedItem.title);
97
+ setIsAttributeMenuOpen(false);
98
+ }}
99
+ >
100
+ <MenuContent>
101
+ <MenuList>
102
+ {filterItems.map(item => (
103
+ <MenuItem key={item.filterId} itemId={item.filterId}>
104
+ {item.title}
105
+ </MenuItem>
106
+ ))}
107
+ </MenuList>
108
+ </MenuContent>
109
+ </Menu>
110
+ );
111
+
112
+ return (
113
+ <ToolbarToggleGroup data-ouia-component-id={ouiaId} toggleIcon={toggleIcon} breakpoint={breakpoint} {...props}>
114
+ <ToolbarGroup variant="filter-group">
115
+ <div ref={attributeContainerRef}>
116
+ <Popper
117
+ trigger={attributeToggle}
118
+ triggerRef={attributeToggleRef}
119
+ popper={attributeMenu}
120
+ popperRef={attributeMenuRef}
121
+ appendTo={attributeContainerRef.current || undefined}
122
+ isVisible={isAttributeMenuOpen}
123
+ />
124
+ </div>
125
+ {React.Children.map(children, (child) =>
126
+ React.isValidElement(child)
127
+ ? React.cloneElement(child as ReactElement<{
128
+ showToolbarItem: boolean;
129
+ onChange: (_e: unknown, values: unknown) => void;
130
+ value: unknown;
131
+ }>, {
132
+ showToolbarItem: activeAttributeMenu === child.props.title,
133
+ onChange: (event, value) => onChange?.(event, { [child.props.filterId]: value } as Partial<T>),
134
+ value: values?.[child.props.filterId],
135
+ ...child.props
136
+ })
137
+ : child
138
+ )}
139
+ </ToolbarGroup>
140
+ </ToolbarToggleGroup>
141
+ );
142
+ };
143
+
144
+ export default DataViewFilters;