@patternfly/react-core 4.247.1 → 4.249.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/CHANGELOG.md +22 -0
- package/dist/esm/components/Icon/Icon.d.ts +25 -0
- package/dist/esm/components/Icon/Icon.d.ts.map +1 -0
- package/dist/esm/components/Icon/Icon.js +14 -0
- package/dist/esm/components/Icon/Icon.js.map +1 -0
- package/dist/esm/components/Icon/index.d.ts +2 -0
- package/dist/esm/components/Icon/index.d.ts.map +1 -0
- package/dist/esm/components/Icon/index.js +2 -0
- package/dist/esm/components/Icon/index.js.map +1 -0
- package/dist/esm/components/Menu/Menu.d.ts.map +1 -1
- package/dist/esm/components/Menu/Menu.js +10 -3
- package/dist/esm/components/Menu/Menu.js.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js +1 -1
- package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/js/components/Icon/Icon.d.ts +25 -0
- package/dist/js/components/Icon/Icon.d.ts.map +1 -0
- package/dist/js/components/Icon/Icon.js +18 -0
- package/dist/js/components/Icon/Icon.js.map +1 -0
- package/dist/js/components/Icon/index.d.ts +2 -0
- package/dist/js/components/Icon/index.d.ts.map +1 -0
- package/dist/js/components/Icon/index.js +5 -0
- package/dist/js/components/Icon/index.js.map +1 -0
- package/dist/js/components/Menu/Menu.d.ts.map +1 -1
- package/dist/js/components/Menu/Menu.js +10 -3
- package/dist/js/components/Menu/Menu.js.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.js +1 -1
- package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
- package/dist/js/components/index.d.ts +1 -0
- package/dist/js/components/index.d.ts.map +1 -1
- package/dist/js/components/index.js +1 -0
- package/dist/js/components/index.js.map +1 -1
- package/dist/umd/react-core.min.css +925 -788
- package/dist/umd/react-core.min.js +1 -1
- package/package.json +2 -2
- package/src/components/Icon/Icon.tsx +66 -0
- package/src/components/Icon/__tests__/Icon.test.tsx +196 -0
- package/src/components/Icon/__tests__/__snapshots__/Icon.test.tsx.snap +82 -0
- package/src/components/Icon/examples/Icon.md +61 -0
- package/src/components/Icon/examples/IconBasic.tsx +23 -0
- package/src/components/Icon/examples/IconContentSizes.tsx +20 -0
- package/src/components/Icon/examples/IconCustomProgress.tsx +24 -0
- package/src/components/Icon/examples/IconInline.tsx +62 -0
- package/src/components/Icon/examples/IconProgress.tsx +24 -0
- package/src/components/Icon/examples/IconSizes.tsx +20 -0
- package/src/components/Icon/examples/IconStatus.tsx +27 -0
- package/src/components/Icon/index.ts +1 -0
- package/src/components/Menu/Menu.tsx +13 -7
- package/src/components/MenuToggle/MenuToggle.tsx +1 -0
- package/src/components/index.ts +1 -0
- package/src/demos/Filters/FilterDemos.md +27 -5
- package/src/demos/Filters/examples/FilterAttributeSearch.tsx +701 -0
- package/src/demos/Filters/examples/FilterCheckboxSelect.tsx +66 -53
- package/src/demos/Filters/examples/FilterFaceted.tsx +512 -0
- package/src/demos/Filters/examples/FilterMixedSelectGroup.tsx +569 -0
- package/src/demos/Filters/examples/FilterSameSelectGroup.tsx +542 -0
- package/src/demos/Filters/examples/FilterSearchInput.tsx +91 -68
- package/src/demos/Filters/examples/FilterSingleSelect.tsx +63 -52
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Toolbar,
|
|
4
|
+
ToolbarContent,
|
|
5
|
+
ToolbarItem,
|
|
6
|
+
Menu,
|
|
7
|
+
MenuContent,
|
|
8
|
+
MenuList,
|
|
9
|
+
MenuItem,
|
|
10
|
+
MenuToggle,
|
|
11
|
+
MenuToggleCheckbox,
|
|
12
|
+
Popper,
|
|
13
|
+
Pagination,
|
|
14
|
+
EmptyState,
|
|
15
|
+
EmptyStateIcon,
|
|
16
|
+
Title,
|
|
17
|
+
EmptyStateBody,
|
|
18
|
+
EmptyStatePrimary,
|
|
19
|
+
Button,
|
|
20
|
+
Bullseye,
|
|
21
|
+
Badge,
|
|
22
|
+
ToolbarFilter,
|
|
23
|
+
ToolbarToggleGroup,
|
|
24
|
+
MenuGroup
|
|
25
|
+
} from '@patternfly/react-core';
|
|
26
|
+
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
|
|
27
|
+
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
|
|
28
|
+
import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';
|
|
29
|
+
|
|
30
|
+
interface Repository {
|
|
31
|
+
name: string;
|
|
32
|
+
threads: string;
|
|
33
|
+
apps: string;
|
|
34
|
+
workspaces: string;
|
|
35
|
+
status: string;
|
|
36
|
+
location: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// In real usage, this data would come from some external source like an API via props.
|
|
40
|
+
const repositories: Repository[] = [
|
|
41
|
+
{ name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },
|
|
42
|
+
{ name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },
|
|
43
|
+
{ name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },
|
|
44
|
+
{ name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },
|
|
45
|
+
{ name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },
|
|
46
|
+
{ name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },
|
|
47
|
+
{ name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },
|
|
48
|
+
{ name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },
|
|
49
|
+
{ name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },
|
|
50
|
+
{ name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const columnNames = {
|
|
54
|
+
name: 'Servers',
|
|
55
|
+
threads: 'Threads',
|
|
56
|
+
apps: 'Applications',
|
|
57
|
+
workspaces: 'Workspaces',
|
|
58
|
+
status: 'Status',
|
|
59
|
+
location: 'Location'
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/* eslint-disable patternfly-react/no-anonymous-functions */
|
|
63
|
+
export const FilterFaceted: React.FunctionComponent = () => {
|
|
64
|
+
// Set up repo filtering
|
|
65
|
+
const [locationSelections, setLocationSelections] = React.useState<string[]>([]);
|
|
66
|
+
const [statusSelections, setStatusSelections] = React.useState<string[]>([]);
|
|
67
|
+
|
|
68
|
+
const onFilter = (repo: Repository) => {
|
|
69
|
+
// Search status with status selection
|
|
70
|
+
const matchesStatusValue = statusSelections.includes(repo.status);
|
|
71
|
+
|
|
72
|
+
// Search location with location selections
|
|
73
|
+
const matchesLocationValue = locationSelections.includes(repo.location);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
(statusSelections.length === 0 || matchesStatusValue) && (locationSelections.length === 0 || matchesLocationValue)
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
const filteredRepos = repositories.filter(onFilter);
|
|
80
|
+
|
|
81
|
+
// Set up table row selection
|
|
82
|
+
// In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.
|
|
83
|
+
// This is to prevent state from being based on row order index in case we later add sorting.
|
|
84
|
+
const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example
|
|
85
|
+
const [selectedRepoNames, setSelectedRepoNames] = React.useState<string[]>([]);
|
|
86
|
+
const setRepoSelected = (repo: Repository, isSelecting = true) =>
|
|
87
|
+
setSelectedRepoNames(prevSelected => {
|
|
88
|
+
const otherSelectedRepoNames = prevSelected.filter(r => r !== repo.name);
|
|
89
|
+
return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;
|
|
90
|
+
});
|
|
91
|
+
const selectAllRepos = (isSelecting = true) =>
|
|
92
|
+
setSelectedRepoNames(isSelecting ? filteredRepos.map(r => r.name) : []); // Selecting all should only select all currently filtered rows
|
|
93
|
+
const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;
|
|
94
|
+
const areSomeReposSelected = selectedRepoNames.length > 0;
|
|
95
|
+
const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);
|
|
96
|
+
|
|
97
|
+
// To allow shift+click to select/deselect multiple rows
|
|
98
|
+
const [recentSelectedRowIndex, setRecentSelectedRowIndex] = React.useState<number | null>(null);
|
|
99
|
+
const [shifting, setShifting] = React.useState(false);
|
|
100
|
+
|
|
101
|
+
const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {
|
|
102
|
+
// If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected
|
|
103
|
+
if (shifting && recentSelectedRowIndex !== null) {
|
|
104
|
+
const numberSelected = rowIndex - recentSelectedRowIndex;
|
|
105
|
+
const intermediateIndexes =
|
|
106
|
+
numberSelected > 0
|
|
107
|
+
? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)
|
|
108
|
+
: Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);
|
|
109
|
+
intermediateIndexes.forEach(index => setRepoSelected(repositories[index], isSelecting));
|
|
110
|
+
} else {
|
|
111
|
+
setRepoSelected(repo, isSelecting);
|
|
112
|
+
}
|
|
113
|
+
setRecentSelectedRowIndex(rowIndex);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
React.useEffect(() => {
|
|
117
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
118
|
+
if (e.key === 'Shift') {
|
|
119
|
+
setShifting(true);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const onKeyUp = (e: KeyboardEvent) => {
|
|
123
|
+
if (e.key === 'Shift') {
|
|
124
|
+
setShifting(false);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
document.addEventListener('keydown', onKeyDown);
|
|
129
|
+
document.addEventListener('keyup', onKeyUp);
|
|
130
|
+
|
|
131
|
+
return () => {
|
|
132
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
133
|
+
document.removeEventListener('keyup', onKeyUp);
|
|
134
|
+
};
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
// Set up bulk selection menu
|
|
138
|
+
const bulkSelectMenuRef = React.useRef<HTMLDivElement>(null);
|
|
139
|
+
const bulkSelectToggleRef = React.useRef<any>(null);
|
|
140
|
+
const bulkSelectContainerRef = React.useRef<HTMLDivElement>(null);
|
|
141
|
+
|
|
142
|
+
const [isBulkSelectOpen, setIsBulkSelectOpen] = React.useState<boolean>(false);
|
|
143
|
+
|
|
144
|
+
const handleBulkSelectClickOutside = (event: MouseEvent) => {
|
|
145
|
+
if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {
|
|
146
|
+
setIsBulkSelectOpen(false);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {
|
|
151
|
+
if (!isBulkSelectOpen) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (
|
|
155
|
+
bulkSelectMenuRef.current?.contains(event.target as Node) ||
|
|
156
|
+
bulkSelectToggleRef.current?.contains(event.target as Node)
|
|
157
|
+
) {
|
|
158
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
159
|
+
setIsBulkSelectOpen(!isBulkSelectOpen);
|
|
160
|
+
bulkSelectToggleRef.current?.querySelector('button').focus();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
React.useEffect(() => {
|
|
166
|
+
window.addEventListener('keydown', handleBulkSelectMenuKeys);
|
|
167
|
+
window.addEventListener('click', handleBulkSelectClickOutside);
|
|
168
|
+
return () => {
|
|
169
|
+
window.removeEventListener('keydown', handleBulkSelectMenuKeys);
|
|
170
|
+
window.removeEventListener('click', handleBulkSelectClickOutside);
|
|
171
|
+
};
|
|
172
|
+
}, [isBulkSelectOpen, bulkSelectMenuRef]);
|
|
173
|
+
|
|
174
|
+
const onBulkSelectToggleClick = (ev: React.MouseEvent) => {
|
|
175
|
+
ev.stopPropagation(); // Stop handleClickOutside from handling
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
if (bulkSelectMenuRef.current) {
|
|
178
|
+
const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');
|
|
179
|
+
firstElement && (firstElement as HTMLElement).focus();
|
|
180
|
+
}
|
|
181
|
+
}, 0);
|
|
182
|
+
setIsBulkSelectOpen(!isBulkSelectOpen);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
let menuToggleCheckmark: boolean | null = false;
|
|
186
|
+
if (areAllReposSelected) {
|
|
187
|
+
menuToggleCheckmark = true;
|
|
188
|
+
} else if (areSomeReposSelected) {
|
|
189
|
+
menuToggleCheckmark = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const bulkSelectToggle = (
|
|
193
|
+
<MenuToggle
|
|
194
|
+
ref={bulkSelectToggleRef}
|
|
195
|
+
onClick={onBulkSelectToggleClick}
|
|
196
|
+
isExpanded={isBulkSelectOpen}
|
|
197
|
+
splitButtonOptions={{
|
|
198
|
+
items: [
|
|
199
|
+
<MenuToggleCheckbox
|
|
200
|
+
id="filter-faceted-input-bulk-select"
|
|
201
|
+
key="filter-faceted-input-bulk-select"
|
|
202
|
+
aria-label="Select all"
|
|
203
|
+
isChecked={menuToggleCheckmark}
|
|
204
|
+
onChange={(checked, _event) => selectAllRepos(checked)}
|
|
205
|
+
/>
|
|
206
|
+
]
|
|
207
|
+
}}
|
|
208
|
+
aria-label="Full table selection checkbox"
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const bulkSelectMenu = (
|
|
213
|
+
<Menu
|
|
214
|
+
id="filter-faceted-input-bulk-select"
|
|
215
|
+
ref={bulkSelectMenuRef}
|
|
216
|
+
onSelect={(_ev, itemId) => {
|
|
217
|
+
selectAllRepos(itemId === 1 || itemId === 2);
|
|
218
|
+
setIsBulkSelectOpen(!isBulkSelectOpen);
|
|
219
|
+
bulkSelectToggleRef.current?.querySelector('button').focus();
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
<MenuContent>
|
|
223
|
+
<MenuList>
|
|
224
|
+
<MenuItem itemId={0}>Select none (0 items)</MenuItem>
|
|
225
|
+
<MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>
|
|
226
|
+
<MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>
|
|
227
|
+
</MenuList>
|
|
228
|
+
</MenuContent>
|
|
229
|
+
</Menu>
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const toolbarBulkSelect = (
|
|
233
|
+
<div ref={bulkSelectContainerRef}>
|
|
234
|
+
<Popper
|
|
235
|
+
trigger={bulkSelectToggle}
|
|
236
|
+
popper={bulkSelectMenu}
|
|
237
|
+
appendTo={bulkSelectContainerRef.current || undefined}
|
|
238
|
+
isVisible={isBulkSelectOpen}
|
|
239
|
+
popperMatchesTriggerWidth={false}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Set up location checkbox select
|
|
245
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
246
|
+
const toggleRef = React.useRef<HTMLButtonElement>(null);
|
|
247
|
+
const menuRef = React.useRef<HTMLDivElement>(null);
|
|
248
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
249
|
+
|
|
250
|
+
const handleKeys = (event: KeyboardEvent) => {
|
|
251
|
+
if (isOpen && menuRef.current?.contains(event.target as Node)) {
|
|
252
|
+
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
253
|
+
setIsOpen(!isOpen);
|
|
254
|
+
toggleRef.current?.focus();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
260
|
+
if (isOpen && !menuRef.current?.contains(event.target as Node)) {
|
|
261
|
+
setIsOpen(false);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
React.useEffect(() => {
|
|
266
|
+
window.addEventListener('keydown', handleKeys);
|
|
267
|
+
window.addEventListener('click', handleClickOutside);
|
|
268
|
+
return () => {
|
|
269
|
+
window.removeEventListener('keydown', handleKeys);
|
|
270
|
+
window.removeEventListener('click', handleClickOutside);
|
|
271
|
+
};
|
|
272
|
+
}, [isOpen, menuRef]);
|
|
273
|
+
|
|
274
|
+
const onToggleClick = (ev: React.MouseEvent) => {
|
|
275
|
+
ev.stopPropagation(); // Stop handleClickOutside from handling
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
if (menuRef.current) {
|
|
278
|
+
const firstElement = menuRef.current.querySelector('li > button:not(:disabled)');
|
|
279
|
+
firstElement && (firstElement as HTMLElement).focus();
|
|
280
|
+
}
|
|
281
|
+
}, 0);
|
|
282
|
+
setIsOpen(!isOpen);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const onSelect = (event: React.MouseEvent | undefined, itemId: string | number | undefined) => {
|
|
286
|
+
if (typeof itemId === 'undefined') {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const itemStr = itemId.toString();
|
|
291
|
+
const category = ['Raleigh', 'Boston', 'Brno', 'Westford', 'Bangalore'].includes(itemStr) ? 'location' : 'status';
|
|
292
|
+
|
|
293
|
+
if (category === 'status') {
|
|
294
|
+
setStatusSelections(
|
|
295
|
+
statusSelections.includes(itemStr)
|
|
296
|
+
? statusSelections.filter(selection => selection !== itemStr)
|
|
297
|
+
: [itemStr, ...statusSelections]
|
|
298
|
+
);
|
|
299
|
+
} else {
|
|
300
|
+
setLocationSelections(
|
|
301
|
+
locationSelections.includes(itemStr)
|
|
302
|
+
? locationSelections.filter(selection => selection !== itemStr)
|
|
303
|
+
: [itemStr, ...locationSelections]
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const onChipDelete = (category: string, chip: string) => {
|
|
309
|
+
if (category === 'status') {
|
|
310
|
+
setStatusSelections(statusSelections.filter(selection => selection !== chip));
|
|
311
|
+
} else {
|
|
312
|
+
setLocationSelections(locationSelections.filter(selection => selection !== chip));
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const areSelectionsPresent = locationSelections.length > 0 || statusSelections.length > 0;
|
|
317
|
+
|
|
318
|
+
const toggle = (
|
|
319
|
+
<MenuToggle
|
|
320
|
+
ref={toggleRef}
|
|
321
|
+
onClick={onToggleClick}
|
|
322
|
+
isExpanded={isOpen}
|
|
323
|
+
{...(areSelectionsPresent && {
|
|
324
|
+
badge: <Badge isRead>{locationSelections.length + statusSelections.length}</Badge>
|
|
325
|
+
})}
|
|
326
|
+
icon={<FilterIcon />}
|
|
327
|
+
style={
|
|
328
|
+
{
|
|
329
|
+
width: '200px'
|
|
330
|
+
} as React.CSSProperties
|
|
331
|
+
}
|
|
332
|
+
>
|
|
333
|
+
Filter
|
|
334
|
+
</MenuToggle>
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const menu = (
|
|
338
|
+
<Menu ref={menuRef} id="filter-faceted-location-menu" onSelect={onSelect} selected={locationSelections}>
|
|
339
|
+
<MenuContent>
|
|
340
|
+
<MenuList>
|
|
341
|
+
<MenuGroup label="Status">
|
|
342
|
+
<MenuItem hasCheck isSelected={statusSelections.includes('Degraded')} itemId="Degraded">
|
|
343
|
+
Degraded
|
|
344
|
+
</MenuItem>
|
|
345
|
+
<MenuItem hasCheck isSelected={statusSelections.includes('Down')} itemId="Down">
|
|
346
|
+
Down
|
|
347
|
+
</MenuItem>
|
|
348
|
+
<MenuItem hasCheck isSelected={statusSelections.includes('Needs Maintenance')} itemId="Needs Maintenance">
|
|
349
|
+
Needs Maintenance
|
|
350
|
+
</MenuItem>
|
|
351
|
+
<MenuItem hasCheck isSelected={statusSelections.includes('Running')} itemId="Running">
|
|
352
|
+
Running
|
|
353
|
+
</MenuItem>
|
|
354
|
+
<MenuItem hasCheck isSelected={statusSelections.includes('Stopped')} itemId="Stopped">
|
|
355
|
+
Stopped
|
|
356
|
+
</MenuItem>
|
|
357
|
+
</MenuGroup>
|
|
358
|
+
<MenuGroup label="Location">
|
|
359
|
+
<MenuItem hasCheck isSelected={locationSelections.includes('Bangalore')} itemId="Bangalore">
|
|
360
|
+
Bangalore
|
|
361
|
+
</MenuItem>
|
|
362
|
+
<MenuItem hasCheck isSelected={locationSelections.includes('Boston')} itemId="Boston">
|
|
363
|
+
Boston
|
|
364
|
+
</MenuItem>
|
|
365
|
+
<MenuItem hasCheck isSelected={locationSelections.includes('Brno')} itemId="Brno">
|
|
366
|
+
Brno
|
|
367
|
+
</MenuItem>
|
|
368
|
+
<MenuItem hasCheck isSelected={locationSelections.includes('Raleigh')} itemId="Raleigh">
|
|
369
|
+
Raleigh
|
|
370
|
+
</MenuItem>
|
|
371
|
+
<MenuItem hasCheck isSelected={locationSelections.includes('Westford')} itemId="Westford">
|
|
372
|
+
Westford
|
|
373
|
+
</MenuItem>
|
|
374
|
+
</MenuGroup>
|
|
375
|
+
</MenuList>
|
|
376
|
+
</MenuContent>
|
|
377
|
+
</Menu>
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
const select = (
|
|
381
|
+
<div ref={containerRef}>
|
|
382
|
+
<Popper trigger={toggle} popper={menu} appendTo={containerRef.current || undefined} isVisible={isOpen} />
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// Set up pagination and toolbar
|
|
387
|
+
const toolbarPagination = (
|
|
388
|
+
<Pagination
|
|
389
|
+
titles={{ paginationTitle: 'Faceted filter group pagination' }}
|
|
390
|
+
perPageComponent="button"
|
|
391
|
+
itemCount={repositories.length}
|
|
392
|
+
perPage={10}
|
|
393
|
+
page={1}
|
|
394
|
+
widgetId="filter-faceted-mock-pagination"
|
|
395
|
+
isCompact
|
|
396
|
+
/>
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const toolbar = (
|
|
400
|
+
<Toolbar
|
|
401
|
+
id="filter-faceted-toolbar"
|
|
402
|
+
clearAllFilters={() => {
|
|
403
|
+
setStatusSelections([]);
|
|
404
|
+
setLocationSelections([]);
|
|
405
|
+
}}
|
|
406
|
+
>
|
|
407
|
+
<ToolbarContent>
|
|
408
|
+
<ToolbarItem>{toolbarBulkSelect}</ToolbarItem>
|
|
409
|
+
<ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint="xl">
|
|
410
|
+
<ToolbarFilter
|
|
411
|
+
chips={statusSelections}
|
|
412
|
+
deleteChip={(category, chip) => onChipDelete(category as string, chip as string)}
|
|
413
|
+
deleteChipGroup={() => setStatusSelections([])}
|
|
414
|
+
categoryName="Status"
|
|
415
|
+
showToolbarItem={false}
|
|
416
|
+
>
|
|
417
|
+
<div />
|
|
418
|
+
</ToolbarFilter>
|
|
419
|
+
<ToolbarFilter
|
|
420
|
+
chips={locationSelections}
|
|
421
|
+
deleteChip={(category, chip) => onChipDelete(category as string, chip as string)}
|
|
422
|
+
deleteChipGroup={() => setLocationSelections([])}
|
|
423
|
+
categoryName="Location"
|
|
424
|
+
>
|
|
425
|
+
{select}
|
|
426
|
+
</ToolbarFilter>
|
|
427
|
+
</ToolbarToggleGroup>
|
|
428
|
+
<ToolbarItem variant="pagination">{toolbarPagination}</ToolbarItem>
|
|
429
|
+
</ToolbarContent>
|
|
430
|
+
</Toolbar>
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const emptyState = (
|
|
434
|
+
<EmptyState>
|
|
435
|
+
<EmptyStateIcon icon={SearchIcon} />
|
|
436
|
+
<Title size="lg" headingLevel="h4">
|
|
437
|
+
No results found
|
|
438
|
+
</Title>
|
|
439
|
+
<EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
|
|
440
|
+
<EmptyStatePrimary>
|
|
441
|
+
<Button
|
|
442
|
+
variant="link"
|
|
443
|
+
onClick={() => {
|
|
444
|
+
setStatusSelections([]);
|
|
445
|
+
setLocationSelections([]);
|
|
446
|
+
}}
|
|
447
|
+
>
|
|
448
|
+
Clear all filters
|
|
449
|
+
</Button>
|
|
450
|
+
</EmptyStatePrimary>
|
|
451
|
+
</EmptyState>
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
return (
|
|
455
|
+
<React.Fragment>
|
|
456
|
+
{toolbar}
|
|
457
|
+
<TableComposable aria-label="Selectable table">
|
|
458
|
+
<Thead>
|
|
459
|
+
<Tr>
|
|
460
|
+
<Th />
|
|
461
|
+
<Th width={20}>{columnNames.name}</Th>
|
|
462
|
+
<Th width={10}>{columnNames.threads}</Th>
|
|
463
|
+
<Th width={10}>{columnNames.apps}</Th>
|
|
464
|
+
<Th width={10}>{columnNames.workspaces}</Th>
|
|
465
|
+
<Th width={20}>{columnNames.status}</Th>
|
|
466
|
+
<Th width={20}>{columnNames.location}</Th>
|
|
467
|
+
</Tr>
|
|
468
|
+
</Thead>
|
|
469
|
+
<Tbody>
|
|
470
|
+
{filteredRepos.length > 0 &&
|
|
471
|
+
filteredRepos.map((repo, rowIndex) => (
|
|
472
|
+
<Tr key={repo.name}>
|
|
473
|
+
<Td
|
|
474
|
+
select={{
|
|
475
|
+
rowIndex,
|
|
476
|
+
onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),
|
|
477
|
+
isSelected: isRepoSelected(repo),
|
|
478
|
+
disable: !isRepoSelectable(repo)
|
|
479
|
+
}}
|
|
480
|
+
/>
|
|
481
|
+
<Td dataLabel={columnNames.name} modifier="truncate">
|
|
482
|
+
{repo.name}
|
|
483
|
+
</Td>
|
|
484
|
+
<Td dataLabel={columnNames.threads} modifier="truncate">
|
|
485
|
+
{repo.threads}
|
|
486
|
+
</Td>
|
|
487
|
+
<Td dataLabel={columnNames.apps} modifier="truncate">
|
|
488
|
+
{repo.apps}
|
|
489
|
+
</Td>
|
|
490
|
+
<Td dataLabel={columnNames.workspaces} modifier="truncate">
|
|
491
|
+
{repo.workspaces}
|
|
492
|
+
</Td>
|
|
493
|
+
<Td dataLabel={columnNames.status} modifier="truncate">
|
|
494
|
+
{repo.status}
|
|
495
|
+
</Td>
|
|
496
|
+
<Td dataLabel={columnNames.location} modifier="truncate">
|
|
497
|
+
{repo.location}
|
|
498
|
+
</Td>
|
|
499
|
+
</Tr>
|
|
500
|
+
))}
|
|
501
|
+
{filteredRepos.length === 0 && (
|
|
502
|
+
<Tr>
|
|
503
|
+
<Td colSpan={8}>
|
|
504
|
+
<Bullseye>{emptyState}</Bullseye>
|
|
505
|
+
</Td>
|
|
506
|
+
</Tr>
|
|
507
|
+
)}
|
|
508
|
+
</Tbody>
|
|
509
|
+
</TableComposable>
|
|
510
|
+
</React.Fragment>
|
|
511
|
+
);
|
|
512
|
+
};
|