@patternfly/react-data-view 5.5.1 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/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 +7 -1
- package/dist/cjs/DataViewFilters/DataViewFilters.js +16 -1
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +1 -1
- package/dist/cjs/Hooks/filters.js +13 -14
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -1
- package/dist/dynamic/DataViewCheckboxFilter/package.json +1 -0
- 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 +7 -1
- package/dist/esm/DataViewFilters/DataViewFilters.js +16 -1
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +1 -1
- package/dist/esm/Hooks/filters.js +13 -14
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx +31 -16
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md +4 -3
- package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx +24 -0
- package/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx +175 -0
- package/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap +194 -0
- package/src/DataViewCheckboxFilter/index.ts +2 -0
- package/src/DataViewFilters/DataViewFilters.tsx +26 -7
- package/src/DataViewTextFilter/DataViewTextFilter.tsx +1 -0
- package/src/Hooks/filters.ts +14 -13
- package/src/index.ts +3 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MenuProps } from '@patternfly/react-core';
|
|
3
|
+
import { DataViewFilterOption } from '../DataViewFilters';
|
|
4
|
+
export declare const isDataViewFilterOption: (obj: unknown) => obj is DataViewFilterOption;
|
|
5
|
+
/** extends MenuProps */
|
|
6
|
+
export interface DataViewCheckboxFilterProps extends Omit<MenuProps, 'onSelect' | 'onChange'> {
|
|
7
|
+
/** Unique key for the filter attribute */
|
|
8
|
+
filterId: string;
|
|
9
|
+
/** Array of current filter values */
|
|
10
|
+
value?: string[];
|
|
11
|
+
/** Filter title displayed in the toolbar */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Placeholder text of the menu */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
/** Filter options displayed */
|
|
16
|
+
options: (DataViewFilterOption | string)[];
|
|
17
|
+
/** Callback for updating when item selection changes. */
|
|
18
|
+
onChange?: (event?: React.MouseEvent, values?: string[]) => void;
|
|
19
|
+
/** Controls visibility of the filter in the toolbar */
|
|
20
|
+
showToolbarItem?: boolean;
|
|
21
|
+
/** Controls visibility of the filter icon */
|
|
22
|
+
showIcon?: boolean;
|
|
23
|
+
/** Controls visibility of the selected items badge */
|
|
24
|
+
showBadge?: boolean;
|
|
25
|
+
/** Custom OUIA ID */
|
|
26
|
+
ouiaId?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const DataViewCheckboxFilter: React.FC<DataViewCheckboxFilterProps>;
|
|
29
|
+
export default DataViewCheckboxFilter;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DataViewCheckboxFilter = exports.isDataViewFilterOption = void 0;
|
|
18
|
+
const react_1 = __importDefault(require("react"));
|
|
19
|
+
const react_core_1 = require("@patternfly/react-core");
|
|
20
|
+
const react_icons_1 = require("@patternfly/react-icons");
|
|
21
|
+
const isToolbarChip = (chip) => typeof chip === 'object' && 'key' in chip;
|
|
22
|
+
const isDataViewFilterOption = (obj) => !!obj &&
|
|
23
|
+
typeof obj === 'object' &&
|
|
24
|
+
'label' in obj &&
|
|
25
|
+
'value' in obj &&
|
|
26
|
+
typeof obj.value === 'string';
|
|
27
|
+
exports.isDataViewFilterOption = isDataViewFilterOption;
|
|
28
|
+
const DataViewCheckboxFilter = (_a) => {
|
|
29
|
+
var { filterId, title, value = [], onChange, placeholder, options = [], showToolbarItem, showIcon = !placeholder, showBadge = !placeholder, ouiaId = 'DataViewCheckboxFilter' } = _a, props = __rest(_a, ["filterId", "title", "value", "onChange", "placeholder", "options", "showToolbarItem", "showIcon", "showBadge", "ouiaId"]);
|
|
30
|
+
const [isOpen, setIsOpen] = react_1.default.useState(false);
|
|
31
|
+
const toggleRef = react_1.default.useRef(null);
|
|
32
|
+
const menuRef = react_1.default.useRef(null);
|
|
33
|
+
const containerRef = react_1.default.useRef(null);
|
|
34
|
+
const normalizeOptions = react_1.default.useMemo(() => options.map(option => typeof option === 'string'
|
|
35
|
+
? { label: option, value: option }
|
|
36
|
+
: option), [options]);
|
|
37
|
+
const handleToggleClick = (event) => {
|
|
38
|
+
event.stopPropagation();
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
var _a;
|
|
41
|
+
const firstElement = (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('li > button:not(:disabled)');
|
|
42
|
+
firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus();
|
|
43
|
+
}, 0);
|
|
44
|
+
setIsOpen(prev => !prev);
|
|
45
|
+
};
|
|
46
|
+
const handleSelect = (event, itemId) => {
|
|
47
|
+
const activeItem = String(itemId);
|
|
48
|
+
const isSelected = value.includes(activeItem);
|
|
49
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event, isSelected ? value.filter(item => item !== activeItem) : [activeItem, ...value]);
|
|
50
|
+
};
|
|
51
|
+
const handleClickOutside = (event) => isOpen &&
|
|
52
|
+
menuRef.current && toggleRef.current &&
|
|
53
|
+
!menuRef.current.contains(event.target) && !toggleRef.current.contains(event.target)
|
|
54
|
+
&& setIsOpen(false);
|
|
55
|
+
react_1.default.useEffect(() => {
|
|
56
|
+
window.addEventListener('click', handleClickOutside);
|
|
57
|
+
return () => {
|
|
58
|
+
window.removeEventListener('click', handleClickOutside);
|
|
59
|
+
};
|
|
60
|
+
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
61
|
+
return (react_1.default.createElement(react_core_1.ToolbarFilter, { key: ouiaId, "data-ouia-component-id": ouiaId, chips: value.map(item => {
|
|
62
|
+
const activeOption = normalizeOptions.find(option => option.value === item);
|
|
63
|
+
return ({ key: activeOption === null || activeOption === void 0 ? void 0 : activeOption.value, node: activeOption === null || activeOption === void 0 ? void 0 : activeOption.label });
|
|
64
|
+
}), deleteChip: (_, chip) => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, value.filter(item => item !== (isToolbarChip(chip) ? chip.key : chip))), categoryName: title, showToolbarItem: showToolbarItem },
|
|
65
|
+
react_1.default.createElement(react_core_1.Popper, { trigger: react_1.default.createElement(react_core_1.MenuToggle, { ouiaId: `${ouiaId}-toggle`, ref: toggleRef, onClick: handleToggleClick, isExpanded: isOpen, icon: showIcon ? react_1.default.createElement(react_icons_1.FilterIcon, null) : undefined, badge: value.length > 0 && showBadge ? react_1.default.createElement(react_core_1.Badge, { "data-ouia-component-id": `${ouiaId}-badge`, isRead: true }, value.length) : undefined, style: { width: '200px' } }, placeholder !== null && placeholder !== void 0 ? placeholder : title), triggerRef: toggleRef, popper: react_1.default.createElement(react_core_1.Menu, Object.assign({ ref: menuRef, ouiaId: `${ouiaId}-menu`, onSelect: handleSelect, selected: value }, props),
|
|
66
|
+
react_1.default.createElement(react_core_1.MenuContent, null,
|
|
67
|
+
react_1.default.createElement(react_core_1.MenuList, null, normalizeOptions.map(option => (react_1.default.createElement(react_core_1.MenuItem, { "data-ouia-component-id": `${ouiaId}-filter-item-${option.value}`, key: option.value, itemId: option.value, isSelected: value.includes(option.value), hasCheckbox: true }, option.label)))))), popperRef: menuRef, appendTo: containerRef.current || undefined, "aria-label": `${title !== null && title !== void 0 ? title : filterId} filter`, isVisible: isOpen })));
|
|
68
|
+
};
|
|
69
|
+
exports.DataViewCheckboxFilter = DataViewCheckboxFilter;
|
|
70
|
+
exports.default = exports.DataViewCheckboxFilter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
const react_2 = require("@testing-library/react");
|
|
8
|
+
const DataViewCheckboxFilter_1 = __importDefault(require("./DataViewCheckboxFilter"));
|
|
9
|
+
const DataViewToolbar_1 = __importDefault(require("../DataViewToolbar"));
|
|
10
|
+
describe('DataViewCheckboxFilter component', () => {
|
|
11
|
+
const defaultProps = {
|
|
12
|
+
filterId: 'test-checkbox-filter',
|
|
13
|
+
title: 'Test Checkbox Filter',
|
|
14
|
+
value: ['workspace-one'],
|
|
15
|
+
options: [
|
|
16
|
+
{ label: 'Workspace one', value: 'workspace-one' },
|
|
17
|
+
{ label: 'Workspace two', value: 'workspace-two' },
|
|
18
|
+
{ label: 'Workspace three', value: 'workspace-three' },
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
it('should render correctly', () => {
|
|
22
|
+
const { container } = (0, react_2.render)(react_1.default.createElement(DataViewToolbar_1.default, { filters: react_1.default.createElement(DataViewCheckboxFilter_1.default, Object.assign({}, defaultProps)) }));
|
|
23
|
+
expect(container).toMatchSnapshot();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.default = void 0;
|
|
21
|
+
var DataViewCheckboxFilter_1 = require("./DataViewCheckboxFilter");
|
|
22
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(DataViewCheckboxFilter_1).default; } });
|
|
23
|
+
__exportStar(require("./DataViewCheckboxFilter"), exports);
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
2
|
import { ToolbarToggleGroupProps } from '@patternfly/react-core';
|
|
3
|
+
export interface DataViewFilterOption {
|
|
4
|
+
/** Filter option label */
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
/** Filter option value */
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
3
9
|
/** extends ToolbarToggleGroupProps */
|
|
4
10
|
export interface DataViewFiltersProps<T extends object> extends Omit<ToolbarToggleGroupProps, 'toggleIcon' | 'breakpoint' | 'onChange'> {
|
|
5
11
|
/** Content rendered inside the data view */
|
|
@@ -52,6 +52,19 @@ const DataViewFilters = (_a) => {
|
|
|
52
52
|
(0, react_1.useEffect)(() => {
|
|
53
53
|
filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title);
|
|
54
54
|
}, [filterItems]);
|
|
55
|
+
const handleClickOutside = (event) => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
return isAttributeMenuOpen &&
|
|
58
|
+
!((_a = attributeMenuRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target)) &&
|
|
59
|
+
!((_b = attributeToggleRef.current) === null || _b === void 0 ? void 0 : _b.contains(event.target))
|
|
60
|
+
&& setIsAttributeMenuOpen(false);
|
|
61
|
+
};
|
|
62
|
+
(0, react_1.useEffect)(() => {
|
|
63
|
+
window.addEventListener('click', handleClickOutside);
|
|
64
|
+
return () => {
|
|
65
|
+
window.removeEventListener('click', handleClickOutside);
|
|
66
|
+
};
|
|
67
|
+
}, [isAttributeMenuOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
55
68
|
const attributeToggle = (react_1.default.createElement(react_core_1.MenuToggle, { ref: attributeToggleRef, onClick: () => setIsAttributeMenuOpen(!isAttributeMenuOpen), isExpanded: isAttributeMenuOpen, icon: toggleIcon }, activeAttributeMenu));
|
|
56
69
|
const attributeMenu = (react_1.default.createElement(react_core_1.Menu, { ref: attributeMenuRef, onSelect: (_ev, itemId) => {
|
|
57
70
|
const selectedItem = filterItems.find(item => item.filterId === itemId);
|
|
@@ -64,7 +77,9 @@ const DataViewFilters = (_a) => {
|
|
|
64
77
|
react_1.default.createElement(react_core_1.ToolbarGroup, { variant: "filter-group" },
|
|
65
78
|
react_1.default.createElement("div", { ref: attributeContainerRef },
|
|
66
79
|
react_1.default.createElement(react_core_1.Popper, { trigger: attributeToggle, triggerRef: attributeToggleRef, popper: attributeMenu, popperRef: attributeMenuRef, appendTo: attributeContainerRef.current || undefined, isVisible: isAttributeMenuOpen })),
|
|
67
|
-
react_1.default.Children.map(children, (child) =>
|
|
80
|
+
react_1.default.Children.map(children, (child) => react_1.default.isValidElement(child)
|
|
81
|
+
? react_1.default.cloneElement(child, Object.assign({ showToolbarItem: activeAttributeMenu === child.props.title, onChange: (event, value) => onChange === null || onChange === void 0 ? void 0 : onChange(event, { [child.props.filterId]: value }), value: values === null || values === void 0 ? void 0 : values[child.props.filterId] }, child.props))
|
|
82
|
+
: child))));
|
|
68
83
|
};
|
|
69
84
|
exports.DataViewFilters = DataViewFilters;
|
|
70
85
|
exports.default = exports.DataViewFilters;
|
|
@@ -19,7 +19,7 @@ const react_1 = __importDefault(require("react"));
|
|
|
19
19
|
const react_core_1 = require("@patternfly/react-core");
|
|
20
20
|
const DataViewTextFilter = (_a) => {
|
|
21
21
|
var { filterId, title, value = '', onChange, onClear = () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), showToolbarItem, trimValue = true, ouiaId = 'DataViewTextFilter' } = _a, props = __rest(_a, ["filterId", "title", "value", "onChange", "onClear", "showToolbarItem", "trimValue", "ouiaId"]);
|
|
22
|
-
return (react_1.default.createElement(react_core_1.ToolbarFilter, { "data-ouia-component-id": ouiaId, chips: value.length > 0 ? [{ key: title, node: value }] : [], deleteChip: () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), categoryName: title, showToolbarItem: showToolbarItem },
|
|
22
|
+
return (react_1.default.createElement(react_core_1.ToolbarFilter, { key: ouiaId, "data-ouia-component-id": ouiaId, chips: value.length > 0 ? [{ key: title, node: value }] : [], deleteChip: () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), categoryName: title, showToolbarItem: showToolbarItem },
|
|
23
23
|
react_1.default.createElement(react_core_1.SearchInput, Object.assign({ searchInputId: filterId, value: value, onChange: (e, inputValue) => onChange === null || onChange === void 0 ? void 0 : onChange(e, trimValue ? inputValue.trim() : inputValue), onClear: onClear, placeholder: `Filter by ${title}`, "aria-label": `${title !== null && title !== void 0 ? title : filterId} filter`, "data-ouia-component-id": `${ouiaId}-input` }, props))));
|
|
24
24
|
};
|
|
25
25
|
exports.DataViewTextFilter = DataViewTextFilter;
|
|
@@ -5,25 +5,24 @@ const react_1 = require("react");
|
|
|
5
5
|
;
|
|
6
6
|
const useDataViewFilters = ({ initialFilters = {}, searchParams, setSearchParams, }) => {
|
|
7
7
|
const isUrlSyncEnabled = (0, react_1.useMemo)(() => searchParams && !!setSearchParams, [searchParams, setSearchParams]);
|
|
8
|
-
const getInitialFilters = (0, react_1.useCallback)(() => isUrlSyncEnabled
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const getInitialFilters = (0, react_1.useCallback)(() => isUrlSyncEnabled
|
|
9
|
+
? Object.keys(initialFilters).reduce((loadedFilters, key) => {
|
|
10
|
+
const urlValue = searchParams === null || searchParams === void 0 ? void 0 : searchParams.get(key);
|
|
11
|
+
const isArrayFilter = Array.isArray(initialFilters[key]);
|
|
12
|
+
// eslint-disable-next-line no-nested-ternary
|
|
13
|
+
loadedFilters[key] = urlValue
|
|
14
|
+
? (isArrayFilter && !Array.isArray(urlValue) ? [urlValue] : urlValue)
|
|
15
|
+
: initialFilters[key];
|
|
16
|
+
return loadedFilters;
|
|
17
|
+
}, Object.assign({}, initialFilters))
|
|
18
|
+
: initialFilters, [isUrlSyncEnabled, initialFilters, searchParams]);
|
|
16
19
|
const [filters, setFilters] = (0, react_1.useState)(getInitialFilters());
|
|
17
20
|
const updateSearchParams = (0, react_1.useCallback)((newFilters) => {
|
|
18
21
|
if (isUrlSyncEnabled) {
|
|
19
22
|
const params = new URLSearchParams(searchParams);
|
|
20
23
|
Object.entries(newFilters).forEach(([key, value]) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
params.delete(key);
|
|
26
|
-
}
|
|
24
|
+
params.delete(key);
|
|
25
|
+
(Array.isArray(value) ? value : [value]).forEach((val) => value && params.append(key, val));
|
|
27
26
|
});
|
|
28
27
|
setSearchParams === null || setSearchParams === void 0 ? void 0 : setSearchParams(params);
|
|
29
28
|
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -15,5 +15,7 @@ export { default as DataViewTable } from './DataViewTable';
|
|
|
15
15
|
export * from './DataViewTable';
|
|
16
16
|
export { default as DataViewEventsContext } from './DataViewEventsContext';
|
|
17
17
|
export * from './DataViewEventsContext';
|
|
18
|
+
export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter';
|
|
19
|
+
export * from './DataViewCheckboxFilter';
|
|
18
20
|
export { default as DataView } from './DataView';
|
|
19
21
|
export * from './DataView';
|
package/dist/cjs/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.DataView = exports.DataViewEventsContext = exports.DataViewTable = exports.DataViewTableBasic = exports.DataViewTableHead = exports.DataViewTableTree = exports.DataViewTextFilter = exports.DataViewToolbar = exports.InternalContext = void 0;
|
|
21
|
+
exports.DataView = exports.DataViewCheckboxFilter = exports.DataViewEventsContext = exports.DataViewTable = exports.DataViewTableBasic = exports.DataViewTableHead = exports.DataViewTableTree = exports.DataViewTextFilter = exports.DataViewToolbar = exports.InternalContext = void 0;
|
|
22
22
|
var InternalContext_1 = require("./InternalContext");
|
|
23
23
|
Object.defineProperty(exports, "InternalContext", { enumerable: true, get: function () { return __importDefault(InternalContext_1).default; } });
|
|
24
24
|
__exportStar(require("./InternalContext"), exports);
|
|
@@ -44,6 +44,9 @@ __exportStar(require("./DataViewTable"), exports);
|
|
|
44
44
|
var DataViewEventsContext_1 = require("./DataViewEventsContext");
|
|
45
45
|
Object.defineProperty(exports, "DataViewEventsContext", { enumerable: true, get: function () { return __importDefault(DataViewEventsContext_1).default; } });
|
|
46
46
|
__exportStar(require("./DataViewEventsContext"), exports);
|
|
47
|
+
var DataViewCheckboxFilter_1 = require("./DataViewCheckboxFilter");
|
|
48
|
+
Object.defineProperty(exports, "DataViewCheckboxFilter", { enumerable: true, get: function () { return __importDefault(DataViewCheckboxFilter_1).default; } });
|
|
49
|
+
__exportStar(require("./DataViewCheckboxFilter"), exports);
|
|
47
50
|
var DataView_1 = require("./DataView");
|
|
48
51
|
Object.defineProperty(exports, "DataView", { enumerable: true, get: function () { return __importDefault(DataView_1).default; } });
|
|
49
52
|
__exportStar(require("./DataView"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"main":"../../cjs/DataViewCheckboxFilter/index.js","module":"../../esm/DataViewCheckboxFilter/index.js","typings":"../../esm/DataViewCheckboxFilter/index.d.ts"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MenuProps } from '@patternfly/react-core';
|
|
3
|
+
import { DataViewFilterOption } from '../DataViewFilters';
|
|
4
|
+
export declare const isDataViewFilterOption: (obj: unknown) => obj is DataViewFilterOption;
|
|
5
|
+
/** extends MenuProps */
|
|
6
|
+
export interface DataViewCheckboxFilterProps extends Omit<MenuProps, 'onSelect' | 'onChange'> {
|
|
7
|
+
/** Unique key for the filter attribute */
|
|
8
|
+
filterId: string;
|
|
9
|
+
/** Array of current filter values */
|
|
10
|
+
value?: string[];
|
|
11
|
+
/** Filter title displayed in the toolbar */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Placeholder text of the menu */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
/** Filter options displayed */
|
|
16
|
+
options: (DataViewFilterOption | string)[];
|
|
17
|
+
/** Callback for updating when item selection changes. */
|
|
18
|
+
onChange?: (event?: React.MouseEvent, values?: string[]) => void;
|
|
19
|
+
/** Controls visibility of the filter in the toolbar */
|
|
20
|
+
showToolbarItem?: boolean;
|
|
21
|
+
/** Controls visibility of the filter icon */
|
|
22
|
+
showIcon?: boolean;
|
|
23
|
+
/** Controls visibility of the selected items badge */
|
|
24
|
+
showBadge?: boolean;
|
|
25
|
+
/** Custom OUIA ID */
|
|
26
|
+
ouiaId?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const DataViewCheckboxFilter: React.FC<DataViewCheckboxFilterProps>;
|
|
29
|
+
export default DataViewCheckboxFilter;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { Badge, Menu, MenuContent, MenuItem, MenuList, MenuToggle, Popper, ToolbarFilter, } from '@patternfly/react-core';
|
|
14
|
+
import { FilterIcon } from '@patternfly/react-icons';
|
|
15
|
+
const isToolbarChip = (chip) => typeof chip === 'object' && 'key' in chip;
|
|
16
|
+
export const isDataViewFilterOption = (obj) => !!obj &&
|
|
17
|
+
typeof obj === 'object' &&
|
|
18
|
+
'label' in obj &&
|
|
19
|
+
'value' in obj &&
|
|
20
|
+
typeof obj.value === 'string';
|
|
21
|
+
export const DataViewCheckboxFilter = (_a) => {
|
|
22
|
+
var { filterId, title, value = [], onChange, placeholder, options = [], showToolbarItem, showIcon = !placeholder, showBadge = !placeholder, ouiaId = 'DataViewCheckboxFilter' } = _a, props = __rest(_a, ["filterId", "title", "value", "onChange", "placeholder", "options", "showToolbarItem", "showIcon", "showBadge", "ouiaId"]);
|
|
23
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
24
|
+
const toggleRef = React.useRef(null);
|
|
25
|
+
const menuRef = React.useRef(null);
|
|
26
|
+
const containerRef = React.useRef(null);
|
|
27
|
+
const normalizeOptions = React.useMemo(() => options.map(option => typeof option === 'string'
|
|
28
|
+
? { label: option, value: option }
|
|
29
|
+
: option), [options]);
|
|
30
|
+
const handleToggleClick = (event) => {
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
var _a;
|
|
34
|
+
const firstElement = (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('li > button:not(:disabled)');
|
|
35
|
+
firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus();
|
|
36
|
+
}, 0);
|
|
37
|
+
setIsOpen(prev => !prev);
|
|
38
|
+
};
|
|
39
|
+
const handleSelect = (event, itemId) => {
|
|
40
|
+
const activeItem = String(itemId);
|
|
41
|
+
const isSelected = value.includes(activeItem);
|
|
42
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event, isSelected ? value.filter(item => item !== activeItem) : [activeItem, ...value]);
|
|
43
|
+
};
|
|
44
|
+
const handleClickOutside = (event) => isOpen &&
|
|
45
|
+
menuRef.current && toggleRef.current &&
|
|
46
|
+
!menuRef.current.contains(event.target) && !toggleRef.current.contains(event.target)
|
|
47
|
+
&& setIsOpen(false);
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
window.addEventListener('click', handleClickOutside);
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener('click', handleClickOutside);
|
|
52
|
+
};
|
|
53
|
+
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
54
|
+
return (React.createElement(ToolbarFilter, { key: ouiaId, "data-ouia-component-id": ouiaId, chips: value.map(item => {
|
|
55
|
+
const activeOption = normalizeOptions.find(option => option.value === item);
|
|
56
|
+
return ({ key: activeOption === null || activeOption === void 0 ? void 0 : activeOption.value, node: activeOption === null || activeOption === void 0 ? void 0 : activeOption.label });
|
|
57
|
+
}), deleteChip: (_, chip) => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, value.filter(item => item !== (isToolbarChip(chip) ? chip.key : chip))), categoryName: title, showToolbarItem: showToolbarItem },
|
|
58
|
+
React.createElement(Popper, { trigger: React.createElement(MenuToggle, { ouiaId: `${ouiaId}-toggle`, ref: toggleRef, onClick: handleToggleClick, isExpanded: isOpen, icon: showIcon ? React.createElement(FilterIcon, null) : undefined, badge: value.length > 0 && showBadge ? React.createElement(Badge, { "data-ouia-component-id": `${ouiaId}-badge`, isRead: true }, value.length) : undefined, style: { width: '200px' } }, placeholder !== null && placeholder !== void 0 ? placeholder : title), triggerRef: toggleRef, popper: React.createElement(Menu, Object.assign({ ref: menuRef, ouiaId: `${ouiaId}-menu`, onSelect: handleSelect, selected: value }, props),
|
|
59
|
+
React.createElement(MenuContent, null,
|
|
60
|
+
React.createElement(MenuList, null, normalizeOptions.map(option => (React.createElement(MenuItem, { "data-ouia-component-id": `${ouiaId}-filter-item-${option.value}`, key: option.value, itemId: option.value, isSelected: value.includes(option.value), hasCheckbox: true }, option.label)))))), popperRef: menuRef, appendTo: containerRef.current || undefined, "aria-label": `${title !== null && title !== void 0 ? title : filterId} filter`, isVisible: isOpen })));
|
|
61
|
+
};
|
|
62
|
+
export default DataViewCheckboxFilter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import DataViewCheckboxFilter from './DataViewCheckboxFilter';
|
|
4
|
+
import DataViewToolbar from '../DataViewToolbar';
|
|
5
|
+
describe('DataViewCheckboxFilter component', () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
filterId: 'test-checkbox-filter',
|
|
8
|
+
title: 'Test Checkbox Filter',
|
|
9
|
+
value: ['workspace-one'],
|
|
10
|
+
options: [
|
|
11
|
+
{ label: 'Workspace one', value: 'workspace-one' },
|
|
12
|
+
{ label: 'Workspace two', value: 'workspace-two' },
|
|
13
|
+
{ label: 'Workspace three', value: 'workspace-three' },
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
it('should render correctly', () => {
|
|
17
|
+
const { container } = render(React.createElement(DataViewToolbar, { filters: React.createElement(DataViewCheckboxFilter, Object.assign({}, defaultProps)) }));
|
|
18
|
+
expect(container).toMatchSnapshot();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
2
|
import { ToolbarToggleGroupProps } from '@patternfly/react-core';
|
|
3
|
+
export interface DataViewFilterOption {
|
|
4
|
+
/** Filter option label */
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
/** Filter option value */
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
3
9
|
/** extends ToolbarToggleGroupProps */
|
|
4
10
|
export interface DataViewFiltersProps<T extends object> extends Omit<ToolbarToggleGroupProps, 'toggleIcon' | 'breakpoint' | 'onChange'> {
|
|
5
11
|
/** Content rendered inside the data view */
|
|
@@ -26,6 +26,19 @@ export const DataViewFilters = (_a) => {
|
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title);
|
|
28
28
|
}, [filterItems]);
|
|
29
|
+
const handleClickOutside = (event) => {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
return isAttributeMenuOpen &&
|
|
32
|
+
!((_a = attributeMenuRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target)) &&
|
|
33
|
+
!((_b = attributeToggleRef.current) === null || _b === void 0 ? void 0 : _b.contains(event.target))
|
|
34
|
+
&& setIsAttributeMenuOpen(false);
|
|
35
|
+
};
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
window.addEventListener('click', handleClickOutside);
|
|
38
|
+
return () => {
|
|
39
|
+
window.removeEventListener('click', handleClickOutside);
|
|
40
|
+
};
|
|
41
|
+
}, [isAttributeMenuOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
29
42
|
const attributeToggle = (React.createElement(MenuToggle, { ref: attributeToggleRef, onClick: () => setIsAttributeMenuOpen(!isAttributeMenuOpen), isExpanded: isAttributeMenuOpen, icon: toggleIcon }, activeAttributeMenu));
|
|
30
43
|
const attributeMenu = (React.createElement(Menu, { ref: attributeMenuRef, onSelect: (_ev, itemId) => {
|
|
31
44
|
const selectedItem = filterItems.find(item => item.filterId === itemId);
|
|
@@ -38,6 +51,8 @@ export const DataViewFilters = (_a) => {
|
|
|
38
51
|
React.createElement(ToolbarGroup, { variant: "filter-group" },
|
|
39
52
|
React.createElement("div", { ref: attributeContainerRef },
|
|
40
53
|
React.createElement(Popper, { trigger: attributeToggle, triggerRef: attributeToggleRef, popper: attributeMenu, popperRef: attributeMenuRef, appendTo: attributeContainerRef.current || undefined, isVisible: isAttributeMenuOpen })),
|
|
41
|
-
React.Children.map(children, (child) =>
|
|
54
|
+
React.Children.map(children, (child) => React.isValidElement(child)
|
|
55
|
+
? React.cloneElement(child, Object.assign({ showToolbarItem: activeAttributeMenu === child.props.title, onChange: (event, value) => onChange === null || onChange === void 0 ? void 0 : onChange(event, { [child.props.filterId]: value }), value: values === null || values === void 0 ? void 0 : values[child.props.filterId] }, child.props))
|
|
56
|
+
: child))));
|
|
42
57
|
};
|
|
43
58
|
export default DataViewFilters;
|
|
@@ -13,7 +13,7 @@ import React from 'react';
|
|
|
13
13
|
import { SearchInput, ToolbarFilter } from '@patternfly/react-core';
|
|
14
14
|
export const DataViewTextFilter = (_a) => {
|
|
15
15
|
var { filterId, title, value = '', onChange, onClear = () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), showToolbarItem, trimValue = true, ouiaId = 'DataViewTextFilter' } = _a, props = __rest(_a, ["filterId", "title", "value", "onChange", "onClear", "showToolbarItem", "trimValue", "ouiaId"]);
|
|
16
|
-
return (React.createElement(ToolbarFilter, { "data-ouia-component-id": ouiaId, chips: value.length > 0 ? [{ key: title, node: value }] : [], deleteChip: () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), categoryName: title, showToolbarItem: showToolbarItem },
|
|
16
|
+
return (React.createElement(ToolbarFilter, { key: ouiaId, "data-ouia-component-id": ouiaId, chips: value.length > 0 ? [{ key: title, node: value }] : [], deleteChip: () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), categoryName: title, showToolbarItem: showToolbarItem },
|
|
17
17
|
React.createElement(SearchInput, Object.assign({ searchInputId: filterId, value: value, onChange: (e, inputValue) => onChange === null || onChange === void 0 ? void 0 : onChange(e, trimValue ? inputValue.trim() : inputValue), onClear: onClear, placeholder: `Filter by ${title}`, "aria-label": `${title !== null && title !== void 0 ? title : filterId} filter`, "data-ouia-component-id": `${ouiaId}-input` }, props))));
|
|
18
18
|
};
|
|
19
19
|
export default DataViewTextFilter;
|
|
@@ -2,25 +2,24 @@ import { useState, useCallback, useEffect, useMemo } from "react";
|
|
|
2
2
|
;
|
|
3
3
|
export const useDataViewFilters = ({ initialFilters = {}, searchParams, setSearchParams, }) => {
|
|
4
4
|
const isUrlSyncEnabled = useMemo(() => searchParams && !!setSearchParams, [searchParams, setSearchParams]);
|
|
5
|
-
const getInitialFilters = useCallback(() => isUrlSyncEnabled
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const getInitialFilters = useCallback(() => isUrlSyncEnabled
|
|
6
|
+
? Object.keys(initialFilters).reduce((loadedFilters, key) => {
|
|
7
|
+
const urlValue = searchParams === null || searchParams === void 0 ? void 0 : searchParams.get(key);
|
|
8
|
+
const isArrayFilter = Array.isArray(initialFilters[key]);
|
|
9
|
+
// eslint-disable-next-line no-nested-ternary
|
|
10
|
+
loadedFilters[key] = urlValue
|
|
11
|
+
? (isArrayFilter && !Array.isArray(urlValue) ? [urlValue] : urlValue)
|
|
12
|
+
: initialFilters[key];
|
|
13
|
+
return loadedFilters;
|
|
14
|
+
}, Object.assign({}, initialFilters))
|
|
15
|
+
: initialFilters, [isUrlSyncEnabled, initialFilters, searchParams]);
|
|
13
16
|
const [filters, setFilters] = useState(getInitialFilters());
|
|
14
17
|
const updateSearchParams = useCallback((newFilters) => {
|
|
15
18
|
if (isUrlSyncEnabled) {
|
|
16
19
|
const params = new URLSearchParams(searchParams);
|
|
17
20
|
Object.entries(newFilters).forEach(([key, value]) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
params.delete(key);
|
|
23
|
-
}
|
|
21
|
+
params.delete(key);
|
|
22
|
+
(Array.isArray(value) ? value : [value]).forEach((val) => value && params.append(key, val));
|
|
24
23
|
});
|
|
25
24
|
setSearchParams === null || setSearchParams === void 0 ? void 0 : setSearchParams(params);
|
|
26
25
|
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -15,5 +15,7 @@ export { default as DataViewTable } from './DataViewTable';
|
|
|
15
15
|
export * from './DataViewTable';
|
|
16
16
|
export { default as DataViewEventsContext } from './DataViewEventsContext';
|
|
17
17
|
export * from './DataViewEventsContext';
|
|
18
|
+
export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter';
|
|
19
|
+
export * from './DataViewCheckboxFilter';
|
|
18
20
|
export { default as DataView } from './DataView';
|
|
19
21
|
export * from './DataView';
|
package/dist/esm/index.js
CHANGED
|
@@ -16,5 +16,7 @@ export { default as DataViewTable } from './DataViewTable';
|
|
|
16
16
|
export * from './DataViewTable';
|
|
17
17
|
export { default as DataViewEventsContext } from './DataViewEventsContext';
|
|
18
18
|
export * from './DataViewEventsContext';
|
|
19
|
+
export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter';
|
|
20
|
+
export * from './DataViewCheckboxFilter';
|
|
19
21
|
export { default as DataView } from './DataView';
|
|
20
22
|
export * from './DataView';
|
package/package.json
CHANGED
package/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx
CHANGED
|
@@ -5,8 +5,9 @@ import { useDataViewFilters, useDataViewPagination } from '@patternfly/react-dat
|
|
|
5
5
|
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
|
|
6
6
|
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
7
7
|
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
|
|
8
|
-
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
|
|
8
|
+
import { DataViewFilterOption, DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
|
|
9
9
|
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
|
|
10
|
+
import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
|
|
10
11
|
|
|
11
12
|
const perPageOptions = [
|
|
12
13
|
{ title: '5', value: 5 },
|
|
@@ -17,38 +18,51 @@ interface Repository {
|
|
|
17
18
|
name: string;
|
|
18
19
|
branch: string | null;
|
|
19
20
|
prs: string | null;
|
|
20
|
-
|
|
21
|
+
workspace: string;
|
|
21
22
|
lastCommit: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
interface RepositoryFilters {
|
|
25
26
|
name: string,
|
|
26
|
-
branch: string
|
|
27
|
+
branch: string,
|
|
28
|
+
workspace: string[]
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const repositories: Repository[] = [
|
|
30
|
-
{ name: 'Repository one', branch: 'Branch one', prs: 'Pull request one',
|
|
31
|
-
{ name: 'Repository two', branch: 'Branch two', prs: 'Pull request two',
|
|
32
|
-
{ name: 'Repository three', branch: 'Branch three', prs: 'Pull request three',
|
|
33
|
-
{ name: 'Repository four', branch: 'Branch four', prs: 'Pull request four',
|
|
34
|
-
{ name: 'Repository five', branch: 'Branch five', prs: 'Pull request five',
|
|
35
|
-
{ name: 'Repository six', branch: 'Branch six', prs: 'Pull request six',
|
|
32
|
+
{ name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspace: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
33
|
+
{ name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspace: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
34
|
+
{ name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspace: 'Workspace one', lastCommit: 'Timestamp three' },
|
|
35
|
+
{ name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspace: 'Workspace one', lastCommit: 'Timestamp four' },
|
|
36
|
+
{ name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspace: 'Workspace two', lastCommit: 'Timestamp five' },
|
|
37
|
+
{ name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspace: 'Workspace three', lastCommit: 'Timestamp six' }
|
|
36
38
|
];
|
|
37
39
|
|
|
38
|
-
const
|
|
40
|
+
const filterOptions: DataViewFilterOption[] = [
|
|
41
|
+
{ label: 'Workspace one', value: 'workspace-one' },
|
|
42
|
+
{ label: 'Workspace two', value: 'workspace-two' },
|
|
43
|
+
{ label: 'Workspace three', value: 'workspace-three' }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const columns = [ 'Name', 'Branch', 'Pull requests', 'Workspace', 'Last commit' ];
|
|
39
47
|
|
|
40
48
|
const ouiaId = 'LayoutExample';
|
|
41
49
|
|
|
42
50
|
const MyTable: React.FunctionComponent = () => {
|
|
43
51
|
const [ searchParams, setSearchParams ] = useSearchParams();
|
|
52
|
+
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<RepositoryFilters>({ initialFilters: { name: '', branch: '', workspace: [] }, searchParams, setSearchParams });
|
|
44
53
|
const pagination = useDataViewPagination({ perPage: 5 });
|
|
45
54
|
const { page, perPage } = pagination;
|
|
46
|
-
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<RepositoryFilters>({ initialFilters: { name: '', branch: '' }, searchParams, setSearchParams });
|
|
47
55
|
|
|
48
|
-
const
|
|
49
|
-
|
|
56
|
+
const filteredData = useMemo(() => repositories.filter(item =>
|
|
57
|
+
(!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) &&
|
|
58
|
+
(!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase())) &&
|
|
59
|
+
(!filters.workspace || filters.workspace.length === 0 || filters.workspace.includes(String(filterOptions.find(option => option.label === item.workspace)?.value)))
|
|
60
|
+
), [ filters ]);
|
|
61
|
+
|
|
62
|
+
const pageRows = useMemo(() => filteredData
|
|
50
63
|
.slice((page - 1) * perPage, ((page - 1) * perPage) + perPage)
|
|
51
|
-
.map(item => Object.values(item)),
|
|
64
|
+
.map(item => Object.values(item)),
|
|
65
|
+
[ page, perPage, filteredData ]);
|
|
52
66
|
|
|
53
67
|
return (
|
|
54
68
|
<DataView>
|
|
@@ -58,7 +72,7 @@ const MyTable: React.FunctionComponent = () => {
|
|
|
58
72
|
pagination={
|
|
59
73
|
<Pagination
|
|
60
74
|
perPageOptions={perPageOptions}
|
|
61
|
-
itemCount={
|
|
75
|
+
itemCount={filteredData.length}
|
|
62
76
|
{...pagination}
|
|
63
77
|
/>
|
|
64
78
|
}
|
|
@@ -66,6 +80,7 @@ const MyTable: React.FunctionComponent = () => {
|
|
|
66
80
|
<DataViewFilters onChange={(_e, values) => onSetFilters(values)} values={filters}>
|
|
67
81
|
<DataViewTextFilter filterId="name" title='Name' placeholder='Filter by name' />
|
|
68
82
|
<DataViewTextFilter filterId="branch" title='Branch' placeholder='Filter by branch' />
|
|
83
|
+
<DataViewCheckboxFilter filterId="workspace" title='Workspace' placeholder='Filter by workspace' options={filterOptions} />
|
|
69
84
|
</DataViewFilters>
|
|
70
85
|
}
|
|
71
86
|
/>
|
|
@@ -76,7 +91,7 @@ const MyTable: React.FunctionComponent = () => {
|
|
|
76
91
|
<Pagination
|
|
77
92
|
isCompact
|
|
78
93
|
perPageOptions={perPageOptions}
|
|
79
|
-
itemCount={
|
|
94
|
+
itemCount={filteredData.length}
|
|
80
95
|
{...pagination}
|
|
81
96
|
/>
|
|
82
97
|
}
|
package/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
CHANGED
|
@@ -11,7 +11,7 @@ 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
|
+
propComponents: ['DataViewFilters', 'DataViewTextFilter', 'DataViewCheckboxFilter']
|
|
15
15
|
sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
|
|
16
16
|
---
|
|
17
17
|
import { useMemo } from 'react';
|
|
@@ -23,6 +23,7 @@ import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataVi
|
|
|
23
23
|
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
24
24
|
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
|
|
25
25
|
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
|
|
26
|
+
import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
|
|
26
27
|
|
|
27
28
|
This is a list of functionality you can use to manage data displayed in the **data view**.
|
|
28
29
|
|
|
@@ -92,7 +93,7 @@ The `useDataViewSelection` hook manages the selection state of the data view.
|
|
|
92
93
|
Enables filtering of data records in the data view and displays the applied filter chips.
|
|
93
94
|
|
|
94
95
|
### 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 `
|
|
96
|
+
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`, `DataViewTextFilter` and `DataViewCheckboxFilter` 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`, `DataViewCheckboxFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page.
|
|
96
97
|
|
|
97
98
|
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
|
|
|
@@ -101,7 +102,7 @@ You can decide between passing `value` and `onChange` event to every filter sepa
|
|
|
101
102
|
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
|
|
|
103
104
|
**Initial values:**
|
|
104
|
-
- `initialFilters` object with default filter values
|
|
105
|
+
- `initialFilters` object with default filter values (if the filter param allows multiple values, pass an array)
|
|
105
106
|
- optional `searchParams` object for managing URL-based filter state
|
|
106
107
|
- optional `setSearchParams` function to update the URL when filters are modified
|
|
107
108
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import DataViewCheckboxFilter, { DataViewCheckboxFilterProps } from './DataViewCheckboxFilter';
|
|
4
|
+
import DataViewToolbar from '../DataViewToolbar';
|
|
5
|
+
|
|
6
|
+
describe('DataViewCheckboxFilter component', () => {
|
|
7
|
+
const defaultProps: DataViewCheckboxFilterProps = {
|
|
8
|
+
filterId: 'test-checkbox-filter',
|
|
9
|
+
title: 'Test Checkbox Filter',
|
|
10
|
+
value: [ 'workspace-one' ],
|
|
11
|
+
options: [
|
|
12
|
+
{ label: 'Workspace one', value: 'workspace-one' },
|
|
13
|
+
{ label: 'Workspace two', value: 'workspace-two' },
|
|
14
|
+
{ label: 'Workspace three', value: 'workspace-three' },
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
it('should render correctly', () => {
|
|
19
|
+
const { container } = render(
|
|
20
|
+
<DataViewToolbar filters={<DataViewCheckboxFilter {...defaultProps} />} />
|
|
21
|
+
);
|
|
22
|
+
expect(container).toMatchSnapshot();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -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
|
+
ToolbarChip,
|
|
12
|
+
ToolbarFilter,
|
|
13
|
+
} from '@patternfly/react-core';
|
|
14
|
+
import { FilterIcon } from '@patternfly/react-icons';
|
|
15
|
+
import { DataViewFilterOption } from '../DataViewFilters';
|
|
16
|
+
|
|
17
|
+
const isToolbarChip = (chip: string | ToolbarChip): chip is ToolbarChip =>
|
|
18
|
+
typeof chip === 'object' && 'key' in chip;
|
|
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
|
+
chips={value.map(item => {
|
|
117
|
+
const activeOption = normalizeOptions.find(option => option.value === item);
|
|
118
|
+
return ({ key: activeOption?.value as string, node: activeOption?.label })
|
|
119
|
+
})}
|
|
120
|
+
deleteChip={(_, chip) =>
|
|
121
|
+
onChange?.(undefined, value.filter(item => item !== (isToolbarChip(chip) ? chip.key : chip)))
|
|
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,194 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`DataViewCheckboxFilter 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__item"
|
|
23
|
+
data-ouia-component-id="DataViewCheckboxFilter"
|
|
24
|
+
>
|
|
25
|
+
<button
|
|
26
|
+
aria-expanded="false"
|
|
27
|
+
class="pf-v5-c-menu-toggle"
|
|
28
|
+
data-ouia-component-id="DataViewCheckboxFilter-toggle"
|
|
29
|
+
data-ouia-component-type="PF5/MenuToggle"
|
|
30
|
+
data-ouia-safe="true"
|
|
31
|
+
style="width: 200px;"
|
|
32
|
+
type="button"
|
|
33
|
+
>
|
|
34
|
+
<span
|
|
35
|
+
class="pf-v5-c-menu-toggle__icon"
|
|
36
|
+
>
|
|
37
|
+
<svg
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
class="pf-v5-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-v5-c-menu-toggle__text"
|
|
53
|
+
>
|
|
54
|
+
Test Checkbox Filter
|
|
55
|
+
</span>
|
|
56
|
+
<span
|
|
57
|
+
class="pf-v5-c-menu-toggle__count"
|
|
58
|
+
>
|
|
59
|
+
<span
|
|
60
|
+
class="pf-v5-c-badge pf-m-read"
|
|
61
|
+
data-ouia-component-id="DataViewCheckboxFilter-badge"
|
|
62
|
+
>
|
|
63
|
+
1
|
|
64
|
+
</span>
|
|
65
|
+
</span>
|
|
66
|
+
<span
|
|
67
|
+
class="pf-v5-c-menu-toggle__controls"
|
|
68
|
+
>
|
|
69
|
+
<span
|
|
70
|
+
class="pf-v5-c-menu-toggle__toggle-icon"
|
|
71
|
+
>
|
|
72
|
+
<svg
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
class="pf-v5-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-v5-c-toolbar__content pf-m-chip-container"
|
|
94
|
+
>
|
|
95
|
+
<div
|
|
96
|
+
class="pf-v5-c-toolbar__group"
|
|
97
|
+
>
|
|
98
|
+
<div
|
|
99
|
+
class="pf-v5-c-toolbar__item pf-m-chip-group"
|
|
100
|
+
>
|
|
101
|
+
<div
|
|
102
|
+
aria-labelledby="pf-random-id-1"
|
|
103
|
+
class="pf-v5-c-chip-group pf-m-category"
|
|
104
|
+
data-ouia-component-type="PF5/ChipGroup"
|
|
105
|
+
data-ouia-safe="true"
|
|
106
|
+
role="group"
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
class="pf-v5-c-chip-group__main"
|
|
110
|
+
>
|
|
111
|
+
<span
|
|
112
|
+
class="pf-v5-c-chip-group__label"
|
|
113
|
+
id="pf-random-id-1"
|
|
114
|
+
>
|
|
115
|
+
Test Checkbox Filter
|
|
116
|
+
</span>
|
|
117
|
+
<ul
|
|
118
|
+
aria-labelledby="pf-random-id-1"
|
|
119
|
+
class="pf-v5-c-chip-group__list"
|
|
120
|
+
role="list"
|
|
121
|
+
>
|
|
122
|
+
<li
|
|
123
|
+
class="pf-v5-c-chip-group__list-item"
|
|
124
|
+
>
|
|
125
|
+
<div
|
|
126
|
+
class="pf-v5-c-chip"
|
|
127
|
+
data-ouia-component-id="OUIA-Generated-Chip-1"
|
|
128
|
+
data-ouia-component-type="PF5/Chip"
|
|
129
|
+
data-ouia-safe="true"
|
|
130
|
+
>
|
|
131
|
+
<span
|
|
132
|
+
class="pf-v5-c-chip__content"
|
|
133
|
+
>
|
|
134
|
+
<span
|
|
135
|
+
class="pf-v5-c-chip__text"
|
|
136
|
+
id="pf-random-id-2"
|
|
137
|
+
>
|
|
138
|
+
Workspace one
|
|
139
|
+
</span>
|
|
140
|
+
</span>
|
|
141
|
+
<span
|
|
142
|
+
class="pf-v5-c-chip__actions"
|
|
143
|
+
>
|
|
144
|
+
<button
|
|
145
|
+
aria-disabled="false"
|
|
146
|
+
aria-label="close"
|
|
147
|
+
aria-labelledby="remove_pf-random-id-2 pf-random-id-2"
|
|
148
|
+
class="pf-v5-c-button pf-m-plain"
|
|
149
|
+
data-ouia-component-id="close"
|
|
150
|
+
data-ouia-component-type="PF5/Button"
|
|
151
|
+
data-ouia-safe="true"
|
|
152
|
+
id="remove_pf-random-id-2"
|
|
153
|
+
type="button"
|
|
154
|
+
>
|
|
155
|
+
<svg
|
|
156
|
+
aria-hidden="true"
|
|
157
|
+
class="pf-v5-svg"
|
|
158
|
+
fill="currentColor"
|
|
159
|
+
height="1em"
|
|
160
|
+
role="img"
|
|
161
|
+
viewBox="0 0 352 512"
|
|
162
|
+
width="1em"
|
|
163
|
+
>
|
|
164
|
+
<path
|
|
165
|
+
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"
|
|
166
|
+
/>
|
|
167
|
+
</svg>
|
|
168
|
+
</button>
|
|
169
|
+
</span>
|
|
170
|
+
</div>
|
|
171
|
+
</li>
|
|
172
|
+
</ul>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
<div
|
|
178
|
+
class="pf-v5-c-toolbar__item"
|
|
179
|
+
>
|
|
180
|
+
<button
|
|
181
|
+
aria-disabled="false"
|
|
182
|
+
class="pf-v5-c-button pf-m-link pf-m-inline"
|
|
183
|
+
data-ouia-component-id="DataViewToolbar-clear-all-filters"
|
|
184
|
+
data-ouia-component-type="PF5/Button"
|
|
185
|
+
data-ouia-safe="true"
|
|
186
|
+
type="button"
|
|
187
|
+
>
|
|
188
|
+
Clear filters
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
`;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import React, { useMemo, useState, useRef, useEffect, ReactElement } from 'react';
|
|
1
|
+
import React, { useMemo, useState, useRef, useEffect, ReactElement, ReactNode } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Menu, MenuContent, MenuItem, MenuList, MenuToggle, Popper, ToolbarGroup, ToolbarToggleGroup, ToolbarToggleGroupProps,
|
|
4
4
|
} from '@patternfly/react-core';
|
|
5
5
|
import { FilterIcon } from '@patternfly/react-icons';
|
|
6
6
|
|
|
7
|
+
export interface DataViewFilterOption {
|
|
8
|
+
/** Filter option label */
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
/** Filter option value */
|
|
11
|
+
value: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
// helper interface to generate attribute menu
|
|
8
15
|
interface DataViewFilterIdentifiers {
|
|
9
16
|
filterId: string;
|
|
@@ -57,6 +64,19 @@ export const DataViewFilters = <T extends object>({
|
|
|
57
64
|
filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title);
|
|
58
65
|
}, [ filterItems ]);
|
|
59
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
|
+
|
|
60
80
|
const attributeToggle = (
|
|
61
81
|
<MenuToggle
|
|
62
82
|
ref={attributeToggleRef}
|
|
@@ -102,9 +122,9 @@ export const DataViewFilters = <T extends object>({
|
|
|
102
122
|
isVisible={isAttributeMenuOpen}
|
|
103
123
|
/>
|
|
104
124
|
</div>
|
|
105
|
-
{React.Children.map(children, (child) =>
|
|
106
|
-
React.isValidElement(child)
|
|
107
|
-
React.cloneElement(child as ReactElement<{
|
|
125
|
+
{React.Children.map(children, (child) =>
|
|
126
|
+
React.isValidElement(child)
|
|
127
|
+
? React.cloneElement(child as ReactElement<{
|
|
108
128
|
showToolbarItem: boolean;
|
|
109
129
|
onChange: (_e: unknown, values: unknown) => void;
|
|
110
130
|
value: unknown;
|
|
@@ -114,9 +134,8 @@ export const DataViewFilters = <T extends object>({
|
|
|
114
134
|
value: values?.[child.props.filterId],
|
|
115
135
|
...child.props
|
|
116
136
|
})
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
|
|
137
|
+
: child
|
|
138
|
+
)}
|
|
120
139
|
</ToolbarGroup>
|
|
121
140
|
</ToolbarToggleGroup>
|
|
122
141
|
);
|
|
@@ -31,6 +31,7 @@ export const DataViewTextFilter: React.FC<DataViewTextFilterProps> = ({
|
|
|
31
31
|
...props
|
|
32
32
|
}: DataViewTextFilterProps) => (
|
|
33
33
|
<ToolbarFilter
|
|
34
|
+
key={ouiaId}
|
|
34
35
|
data-ouia-component-id={ouiaId}
|
|
35
36
|
chips={value.length > 0 ? [ { key: title, node: value } ] : []}
|
|
36
37
|
deleteChip={() => onChange?.(undefined, '')}
|
package/src/Hooks/filters.ts
CHANGED
|
@@ -16,15 +16,19 @@ export const useDataViewFilters = <T extends object>({
|
|
|
16
16
|
}: UseDataViewFiltersProps<T>) => {
|
|
17
17
|
const isUrlSyncEnabled = useMemo(() => searchParams && !!setSearchParams, [ searchParams, setSearchParams ]);
|
|
18
18
|
|
|
19
|
-
const getInitialFilters = useCallback((): T => isUrlSyncEnabled
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
: initialFilters[key as keyof T];
|
|
24
|
-
return loadedFilters;
|
|
25
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
-
}, { ...initialFilters }) : initialFilters, [ isUrlSyncEnabled, JSON.stringify(initialFilters), searchParams?.toString() ]);
|
|
19
|
+
const getInitialFilters = useCallback((): T => isUrlSyncEnabled
|
|
20
|
+
? Object.keys(initialFilters).reduce((loadedFilters, key) => {
|
|
21
|
+
const urlValue = searchParams?.get(key);
|
|
22
|
+
const isArrayFilter = Array.isArray(initialFilters[key]);
|
|
27
23
|
|
|
24
|
+
// eslint-disable-next-line no-nested-ternary
|
|
25
|
+
loadedFilters[key] = urlValue
|
|
26
|
+
? (isArrayFilter && !Array.isArray(urlValue) ? [ urlValue ] : urlValue)
|
|
27
|
+
: initialFilters[key];
|
|
28
|
+
|
|
29
|
+
return loadedFilters;
|
|
30
|
+
}, { ...initialFilters })
|
|
31
|
+
: initialFilters, [ isUrlSyncEnabled, initialFilters, searchParams ]);
|
|
28
32
|
const [ filters, setFilters ] = useState<T>(getInitialFilters());
|
|
29
33
|
|
|
30
34
|
const updateSearchParams = useCallback(
|
|
@@ -32,11 +36,8 @@ export const useDataViewFilters = <T extends object>({
|
|
|
32
36
|
if (isUrlSyncEnabled) {
|
|
33
37
|
const params = new URLSearchParams(searchParams);
|
|
34
38
|
Object.entries(newFilters).forEach(([ key, value ]) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} else {
|
|
38
|
-
params.delete(key);
|
|
39
|
-
}
|
|
39
|
+
params.delete(key);
|
|
40
|
+
(Array.isArray(value) ? value : [ value ]).forEach((val) => value && params.append(key, val));
|
|
40
41
|
});
|
|
41
42
|
setSearchParams?.(params);
|
|
42
43
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,5 +25,8 @@ export * from './DataViewTable';
|
|
|
25
25
|
export { default as DataViewEventsContext } from './DataViewEventsContext';
|
|
26
26
|
export * from './DataViewEventsContext';
|
|
27
27
|
|
|
28
|
+
export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter';
|
|
29
|
+
export * from './DataViewCheckboxFilter';
|
|
30
|
+
|
|
28
31
|
export { default as DataView } from './DataView';
|
|
29
32
|
export * from './DataView';
|