@patternfly/react-data-view 5.5.1 → 5.7.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/Hooks/index.d.ts +1 -0
- package/dist/cjs/Hooks/index.js +1 -0
- 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/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/Hooks/index.d.ts +1 -0
- package/dist/esm/Hooks/index.js +1 -0
- 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/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 +36 -4
- package/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx +87 -0
- 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/Hooks/index.ts +1 -0
- package/src/Hooks/sort.test.tsx +84 -0
- package/src/Hooks/sort.ts +87 -0
- package/src/index.ts +3 -0
|
@@ -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/Hooks/index.js
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ISortBy } from "@patternfly/react-table";
|
|
2
|
+
export declare enum DataViewSortParams {
|
|
3
|
+
SORT_BY = "sortBy",
|
|
4
|
+
DIRECTION = "direction"
|
|
5
|
+
}
|
|
6
|
+
export interface DataViewSortConfig {
|
|
7
|
+
/** Attribute to sort the entries by */
|
|
8
|
+
sortBy: string | undefined;
|
|
9
|
+
/** Sort direction */
|
|
10
|
+
direction: ISortBy['direction'];
|
|
11
|
+
}
|
|
12
|
+
export interface UseDataViewSortProps {
|
|
13
|
+
/** Initial sort config */
|
|
14
|
+
initialSort?: DataViewSortConfig;
|
|
15
|
+
/** Current search parameters as a string */
|
|
16
|
+
searchParams?: URLSearchParams;
|
|
17
|
+
/** Function to set search parameters */
|
|
18
|
+
setSearchParams?: (params: URLSearchParams) => void;
|
|
19
|
+
/** Default direction */
|
|
20
|
+
defaultDirection?: ISortBy['direction'];
|
|
21
|
+
/** Sort by URL param name */
|
|
22
|
+
sortByParam?: string;
|
|
23
|
+
/** Direction URL param name */
|
|
24
|
+
directionParam?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const useDataViewSort: (props?: UseDataViewSortProps) => {
|
|
27
|
+
onSort: (_event: React.MouseEvent | React.KeyboardEvent | MouseEvent | undefined, newSortBy: string, newSortDirection: ISortBy['direction']) => void;
|
|
28
|
+
/** Attribute to sort the entries by */
|
|
29
|
+
sortBy: string | undefined;
|
|
30
|
+
/** Sort direction */
|
|
31
|
+
direction: ISortBy['direction'];
|
|
32
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from "react";
|
|
2
|
+
export var DataViewSortParams;
|
|
3
|
+
(function (DataViewSortParams) {
|
|
4
|
+
DataViewSortParams["SORT_BY"] = "sortBy";
|
|
5
|
+
DataViewSortParams["DIRECTION"] = "direction";
|
|
6
|
+
})(DataViewSortParams || (DataViewSortParams = {}));
|
|
7
|
+
;
|
|
8
|
+
const validateDirection = (direction, defaultDirection) => (direction === 'asc' || direction === 'desc' ? direction : defaultDirection);
|
|
9
|
+
;
|
|
10
|
+
;
|
|
11
|
+
export const useDataViewSort = (props) => {
|
|
12
|
+
var _a;
|
|
13
|
+
const { initialSort, searchParams, setSearchParams, defaultDirection = 'asc', sortByParam = DataViewSortParams.SORT_BY, directionParam = DataViewSortParams.DIRECTION } = props !== null && props !== void 0 ? props : {};
|
|
14
|
+
const isUrlSyncEnabled = useMemo(() => searchParams && !!setSearchParams, [searchParams, setSearchParams]);
|
|
15
|
+
const [state, setState] = useState({
|
|
16
|
+
sortBy: (_a = searchParams === null || searchParams === void 0 ? void 0 : searchParams.get(sortByParam)) !== null && _a !== void 0 ? _a : initialSort === null || initialSort === void 0 ? void 0 : initialSort.sortBy,
|
|
17
|
+
direction: validateDirection(searchParams === null || searchParams === void 0 ? void 0 : searchParams.get(directionParam), initialSort === null || initialSort === void 0 ? void 0 : initialSort.direction),
|
|
18
|
+
});
|
|
19
|
+
const updateSearchParams = (sortBy, direction) => {
|
|
20
|
+
if (isUrlSyncEnabled && sortBy) {
|
|
21
|
+
const params = new URLSearchParams(searchParams);
|
|
22
|
+
params.set(sortByParam, `${sortBy}`);
|
|
23
|
+
params.set(directionParam, `${direction}`);
|
|
24
|
+
setSearchParams === null || setSearchParams === void 0 ? void 0 : setSearchParams(params);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
state.sortBy && state.direction && updateSearchParams(state.sortBy, state.direction);
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, []);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const currentSortBy = (searchParams === null || searchParams === void 0 ? void 0 : searchParams.get(sortByParam)) || state.sortBy;
|
|
33
|
+
const currentDirection = (searchParams === null || searchParams === void 0 ? void 0 : searchParams.get(directionParam)) || state.direction;
|
|
34
|
+
const validDirection = validateDirection(currentDirection, defaultDirection);
|
|
35
|
+
currentSortBy !== state.sortBy || validDirection !== state.direction && setState({ sortBy: currentSortBy, direction: validDirection });
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, [searchParams === null || searchParams === void 0 ? void 0 : searchParams.toString()]);
|
|
38
|
+
const onSort = (_event, newSortBy, newSortDirection) => {
|
|
39
|
+
setState({ sortBy: newSortBy, direction: newSortDirection });
|
|
40
|
+
updateSearchParams(newSortBy, newSortDirection);
|
|
41
|
+
};
|
|
42
|
+
return Object.assign(Object.assign({}, state), { onSort });
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { renderHook, act } from '@testing-library/react';
|
|
3
|
+
import { useDataViewSort, DataViewSortParams } from './sort';
|
|
4
|
+
describe('useDataViewSort', () => {
|
|
5
|
+
const initialSort = { sortBy: 'name', direction: 'asc' };
|
|
6
|
+
it('should initialize with provided initial sort config', () => {
|
|
7
|
+
const { result } = renderHook(() => useDataViewSort({ initialSort }));
|
|
8
|
+
expect(result.current).toEqual(expect.objectContaining(initialSort));
|
|
9
|
+
});
|
|
10
|
+
it('should initialize with empty sort config if no initialSort is provided', () => {
|
|
11
|
+
const { result } = renderHook(() => useDataViewSort());
|
|
12
|
+
expect(result.current).toEqual(expect.objectContaining({ sortBy: undefined, direction: 'asc' }));
|
|
13
|
+
});
|
|
14
|
+
it('should update sort state when onSort is called', () => {
|
|
15
|
+
const { result } = renderHook(() => useDataViewSort({ initialSort }));
|
|
16
|
+
act(() => {
|
|
17
|
+
result.current.onSort(undefined, 'age', 'desc');
|
|
18
|
+
});
|
|
19
|
+
expect(result.current).toEqual(expect.objectContaining({ sortBy: 'age', direction: 'desc' }));
|
|
20
|
+
});
|
|
21
|
+
it('should sync with URL search params if isUrlSyncEnabled', () => {
|
|
22
|
+
const searchParams = new URLSearchParams();
|
|
23
|
+
const setSearchParams = jest.fn();
|
|
24
|
+
const props = {
|
|
25
|
+
initialSort,
|
|
26
|
+
searchParams,
|
|
27
|
+
setSearchParams,
|
|
28
|
+
};
|
|
29
|
+
const { result } = renderHook(() => useDataViewSort(props));
|
|
30
|
+
expect(setSearchParams).toHaveBeenCalledTimes(1);
|
|
31
|
+
expect(result.current).toEqual(expect.objectContaining(initialSort));
|
|
32
|
+
});
|
|
33
|
+
it('should validate direction and fallback to default direction if invalid direction is provided', () => {
|
|
34
|
+
const searchParams = new URLSearchParams();
|
|
35
|
+
searchParams.set(DataViewSortParams.SORT_BY, 'name');
|
|
36
|
+
searchParams.set(DataViewSortParams.DIRECTION, 'invalid-direction');
|
|
37
|
+
const { result } = renderHook(() => useDataViewSort({ searchParams, defaultDirection: 'desc' }));
|
|
38
|
+
expect(result.current).toEqual(expect.objectContaining({ sortBy: 'name', direction: 'desc' }));
|
|
39
|
+
});
|
|
40
|
+
it('should update search params when URL sync is enabled and sort changes', () => {
|
|
41
|
+
const searchParams = new URLSearchParams();
|
|
42
|
+
const setSearchParams = jest.fn();
|
|
43
|
+
const props = {
|
|
44
|
+
initialSort,
|
|
45
|
+
searchParams,
|
|
46
|
+
setSearchParams,
|
|
47
|
+
};
|
|
48
|
+
const { result } = renderHook(() => useDataViewSort(props));
|
|
49
|
+
act(() => {
|
|
50
|
+
expect(setSearchParams).toHaveBeenCalledTimes(1);
|
|
51
|
+
result.current.onSort(undefined, 'priority', 'desc');
|
|
52
|
+
});
|
|
53
|
+
expect(setSearchParams).toHaveBeenCalledTimes(2);
|
|
54
|
+
expect(result.current).toEqual(expect.objectContaining({ sortBy: 'priority', direction: 'desc' }));
|
|
55
|
+
});
|
|
56
|
+
it('should prioritize searchParams values', () => {
|
|
57
|
+
const searchParams = new URLSearchParams();
|
|
58
|
+
searchParams.set(DataViewSortParams.SORT_BY, 'category');
|
|
59
|
+
searchParams.set(DataViewSortParams.DIRECTION, 'desc');
|
|
60
|
+
const { result } = renderHook((props) => useDataViewSort(props), { initialProps: { initialSort, searchParams } });
|
|
61
|
+
expect(result.current).toEqual(expect.objectContaining({
|
|
62
|
+
sortBy: 'category',
|
|
63
|
+
direction: 'desc',
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
});
|
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,18 +11,19 @@ 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';
|
|
18
18
|
import { BrowserRouter, useSearchParams } from 'react-router-dom';
|
|
19
|
-
import { useDataViewPagination, useDataViewSelection, useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
19
|
+
import { useDataViewPagination, useDataViewSelection, useDataViewFilters, useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
20
20
|
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
|
|
21
21
|
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
|
|
22
22
|
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
|
|
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
|
|
|
@@ -118,3 +119,34 @@ This example demonstrates the setup and usage of filters within the data view. I
|
|
|
118
119
|
```js file="./FiltersExample.tsx"
|
|
119
120
|
|
|
120
121
|
```
|
|
122
|
+
|
|
123
|
+
### Sort state
|
|
124
|
+
|
|
125
|
+
The `useDataViewSort` hook manages the sorting state of a data view. It provides an easy way to handle sorting logic, including synchronization with URL parameters and defining default sorting behavior.
|
|
126
|
+
|
|
127
|
+
**Initial values:**
|
|
128
|
+
- `initialSort` object to set default `sortBy` and `direction` values:
|
|
129
|
+
- `sortBy`: key of the initial column to sort.
|
|
130
|
+
- `direction`: default sorting direction (`asc` or `desc`).
|
|
131
|
+
- Optional `searchParams` object to manage URL-based synchronization of sort state.
|
|
132
|
+
- Optional `setSearchParams` function to update the URL parameters when sorting changes.
|
|
133
|
+
- `defaultDirection` to set the default direction when no direction is specified.
|
|
134
|
+
- Customizable parameter names for the URL:
|
|
135
|
+
- `sortByParam`: name of the URL parameter for the column key.
|
|
136
|
+
- `directionParam`: name of the URL parameter for the sorting direction.
|
|
137
|
+
|
|
138
|
+
The `useDataViewSort` hook integrates seamlessly with React Router to manage sort state via URL parameters. Alternatively, you can use `URLSearchParams` and `window.history.pushState` APIs, or other routing libraries. If URL synchronization is not configured, the sort state is managed internally within the component.
|
|
139
|
+
|
|
140
|
+
**Return values:**
|
|
141
|
+
- `sortBy`: key of the column currently being sorted.
|
|
142
|
+
- `direction`: current sorting direction (`asc` or `desc`).
|
|
143
|
+
- `onSort`: function to handle sorting changes programmatically or via user interaction.
|
|
144
|
+
|
|
145
|
+
### Sorting example
|
|
146
|
+
|
|
147
|
+
This example demonstrates how to set up and use sorting functionality within a data view. The implementation includes dynamic sorting by column with persistence of sort state in the URL using React Router.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
```js file="./SortingExample.tsx"
|
|
151
|
+
|
|
152
|
+
```
|
package/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import { useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks';
|
|
4
|
+
import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
5
|
+
import { ThProps } from '@patternfly/react-table';
|
|
6
|
+
import { BrowserRouter, useSearchParams } from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
interface Repository {
|
|
9
|
+
name: string;
|
|
10
|
+
branches: string;
|
|
11
|
+
prs: string;
|
|
12
|
+
workspaces: string;
|
|
13
|
+
lastCommit: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const COLUMNS = [
|
|
17
|
+
{ label: 'Repository', key: 'name', index: 0 },
|
|
18
|
+
{ label: 'Branch', key: 'branches', index: 1 },
|
|
19
|
+
{ label: 'Pull request', key: 'prs', index: 2 },
|
|
20
|
+
{ label: 'Workspace', key: 'workspaces', index: 3 },
|
|
21
|
+
{ label: 'Last commit', key: 'lastCommit', index: 4 }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const repositories: Repository[] = [
|
|
25
|
+
{ name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
26
|
+
{ name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
27
|
+
{ name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
28
|
+
{ name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
29
|
+
{ name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
30
|
+
{ name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const sortData = (data: Repository[], sortBy: string | undefined, direction: 'asc' | 'desc' | undefined) =>
|
|
34
|
+
sortBy && direction
|
|
35
|
+
? [ ...data ].sort((a, b) =>
|
|
36
|
+
direction === 'asc'
|
|
37
|
+
? a[sortBy] < b[sortBy] ? -1 : a[sortBy] > b[sortBy] ? 1 : 0
|
|
38
|
+
: a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0
|
|
39
|
+
)
|
|
40
|
+
: data;
|
|
41
|
+
|
|
42
|
+
const ouiaId = 'TableExample';
|
|
43
|
+
|
|
44
|
+
export const MyTable: React.FunctionComponent = () => {
|
|
45
|
+
const [ searchParams, setSearchParams ] = useSearchParams();
|
|
46
|
+
const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams });
|
|
47
|
+
const sortByIndex = useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]);
|
|
48
|
+
|
|
49
|
+
const getSortParams = (columnIndex: number): ThProps['sort'] => ({
|
|
50
|
+
sortBy: {
|
|
51
|
+
index: sortByIndex,
|
|
52
|
+
direction,
|
|
53
|
+
defaultDirection: 'asc'
|
|
54
|
+
},
|
|
55
|
+
onSort: (_event, index, direction) => onSort(_event, COLUMNS[index].key, direction),
|
|
56
|
+
columnIndex
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const columns: DataViewTh[] = COLUMNS.map((column, index) => ({
|
|
60
|
+
cell: column.label,
|
|
61
|
+
props: { sort: getSortParams(index) }
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
const rows: DataViewTr[] = useMemo(() => sortData(repositories, sortBy, direction).map(({ name, branches, prs, workspaces, lastCommit }) => [
|
|
65
|
+
name,
|
|
66
|
+
branches,
|
|
67
|
+
prs,
|
|
68
|
+
workspaces,
|
|
69
|
+
lastCommit,
|
|
70
|
+
]), [ sortBy, direction ]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<DataViewTable
|
|
74
|
+
aria-label="Repositories table"
|
|
75
|
+
ouiaId={ouiaId}
|
|
76
|
+
columns={columns}
|
|
77
|
+
rows={rows}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const BasicExample: React.FunctionComponent = () => (
|
|
83
|
+
<BrowserRouter>
|
|
84
|
+
<MyTable/>
|
|
85
|
+
</BrowserRouter>
|
|
86
|
+
)
|
|
87
|
+
|
|
@@ -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
|
+
});
|