@patternfly/react-data-view 7.0.0-prerelease.2 → 7.0.0-prerelease.4
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/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +29 -0
- package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.js +70 -0
- package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -0
- package/dist/cjs/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +25 -0
- package/dist/cjs/DataViewCheckboxFilter/index.d.ts +2 -0
- package/dist/cjs/DataViewCheckboxFilter/index.js +23 -0
- package/dist/cjs/DataViewFilters/DataViewFilters.d.ts +25 -0
- package/dist/cjs/DataViewFilters/DataViewFilters.js +85 -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/DataViewTableTree/DataViewTableTree.js +26 -14
- 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 +69 -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 +2 -0
- package/dist/cjs/Hooks/index.js +2 -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/Hooks/sort.d.ts +32 -0
- package/dist/cjs/Hooks/sort.js +47 -0
- package/dist/cjs/Hooks/sort.test.d.ts +1 -0
- package/dist/cjs/Hooks/sort.test.js +68 -0
- package/dist/cjs/InternalContext/InternalContext.d.ts +1 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +7 -1
- package/dist/dynamic/DataViewCheckboxFilter/package.json +1 -0
- 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/DataViewCheckboxFilter/DataViewCheckboxFilter.d.ts +29 -0
- package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.js +62 -0
- package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.d.ts +1 -0
- package/dist/esm/DataViewCheckboxFilter/DataViewCheckboxFilter.test.js +20 -0
- package/dist/esm/DataViewCheckboxFilter/index.d.ts +2 -0
- package/dist/esm/DataViewCheckboxFilter/index.js +2 -0
- package/dist/esm/DataViewFilters/DataViewFilters.d.ts +25 -0
- package/dist/esm/DataViewFilters/DataViewFilters.js +58 -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/DataViewTableTree/DataViewTableTree.js +26 -14
- 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 +65 -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 +2 -0
- package/dist/esm/Hooks/index.js +2 -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/Hooks/sort.d.ts +32 -0
- package/dist/esm/Hooks/sort.js +43 -0
- package/dist/esm/Hooks/sort.test.d.ts +1 -0
- package/dist/esm/Hooks/sort.test.js +66 -0
- package/dist/esm/InternalContext/InternalContext.d.ts +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/patternfly-docs/content/extensions/data-view/examples/Components/Components.md +5 -3
- package/patternfly-docs/content/extensions/data-view/examples/Components/DataViewTableExample.tsx +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsContext.md +1 -0
- package/patternfly-docs/content/extensions/data-view/examples/EventsContext/EventsExample.tsx +28 -6
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx +107 -0
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +67 -2
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx +87 -0
- package/src/DataView/DataView.tsx +3 -2
- package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx +24 -0
- package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx +175 -0
- package/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap +197 -0
- package/src/DataViewCheckboxFilter/index.ts +2 -0
- package/src/DataViewFilters/DataViewFilters.test.tsx +21 -0
- package/src/DataViewFilters/DataViewFilters.tsx +144 -0
- package/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap +194 -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 +40 -18
- package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +24 -0
- package/src/DataViewTextFilter/DataViewTextFilter.tsx +54 -0
- package/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap +203 -0
- package/src/DataViewTextFilter/index.ts +2 -0
- package/src/DataViewToolbar/DataViewToolbar.tsx +47 -28
- package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +44 -0
- package/src/Hooks/filters.test.tsx +62 -0
- package/src/Hooks/filters.ts +97 -0
- package/src/Hooks/index.ts +2 -0
- package/src/Hooks/pagination.ts +1 -0
- package/src/Hooks/selection.ts +3 -2
- package/src/Hooks/sort.test.tsx +84 -0
- package/src/Hooks/sort.ts +87 -0
- package/src/InternalContext/InternalContext.tsx +1 -0
- 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,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;
|