From 43bf16f98b186901ca3371a2ec25f0ff4a5566e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com>
Date: Tue, 7 Nov 2023 08:07:42 +0100
Subject: [PATCH] Dataviews: add filters in columns (#55508)
---
.../src/components/dataviews/view-list.js | 175 +++++++++++++++++-
packages/icons/CHANGELOG.md | 4 +
packages/icons/src/index.js | 1 +
packages/icons/src/library/funnel.js | 12 ++
4 files changed, 191 insertions(+), 1 deletion(-)
create mode 100644 packages/icons/src/library/funnel.js
diff --git a/packages/edit-site/src/components/dataviews/view-list.js b/packages/edit-site/src/components/dataviews/view-list.js
index 0fbdd6ced34c56..cddf316562f346 100644
--- a/packages/edit-site/src/components/dataviews/view-list.js
+++ b/packages/edit-site/src/components/dataviews/view-list.js
@@ -21,6 +21,8 @@ import {
check,
arrowUp,
arrowDown,
+ chevronRightSmall,
+ funnel,
} from '@wordpress/icons';
import {
Button,
@@ -41,6 +43,8 @@ const {
DropdownMenuGroupV2,
DropdownMenuItemV2,
DropdownMenuSeparatorV2,
+ DropdownSubMenuV2,
+ DropdownSubMenuTriggerV2,
} = unlock( componentsPrivateApis );
const EMPTY_OBJECT = {};
@@ -63,6 +67,29 @@ function HeaderMenu( { dataView, header } ) {
return text;
}
const sortedDirection = header.column.getIsSorted();
+
+ let filter;
+ if (
+ header.column.columnDef.filters?.length > 0 &&
+ header.column.columnDef.filters.some(
+ ( f ) =>
+ ( 'string' === typeof f && f === 'enumeration' ) ||
+ ( 'object' === typeof f && f.type === 'enumeration' )
+ )
+ ) {
+ filter = {
+ id: header.column.columnDef.id,
+ elements: [
+ {
+ value: '',
+ label: __( 'All' ),
+ },
+ ...( header.column.columnDef.elements || [] ),
+ ],
+ };
+ }
+ const isFilterable = !! filter;
+
return (
) }
+ { isFilterable && (
+
+ }
+ suffix={
+
+ }
+ >
+ { __( 'Filter by' ) }
+
+ }
+ >
+ { filter.elements.map( ( element ) => {
+ let isActive = false;
+ const columnFilters =
+ dataView.getState().columnFilters;
+ const columnFilter = columnFilters.find(
+ ( f ) =>
+ Object.keys( f )[ 0 ].split(
+ ':'
+ )[ 0 ] === filter.id
+ );
+
+ // Set the empty item as active if the filter is not set.
+ if ( ! columnFilter && element.value === '' ) {
+ isActive = true;
+ }
+
+ if ( columnFilter ) {
+ const value =
+ Object.values( columnFilter )[ 0 ];
+ // Intentionally use loose comparison, so it does type conversion.
+ // This covers the case where a top-level filter for the same field converts a number into a string.
+ isActive = element.value == value; // eslint-disable-line eqeqeq
+ }
+
+ return (
+
+ }
+ onSelect={ () => {
+ const otherFilters =
+ columnFilters?.filter(
+ ( f ) => {
+ const [
+ field,
+ operator,
+ ] =
+ Object.keys(
+ f
+ )[ 0 ].split( ':' );
+ return (
+ field !==
+ filter.id ||
+ operator !== 'in'
+ );
+ }
+ );
+
+ if ( element.value === '' ) {
+ dataView.setColumnFilters(
+ otherFilters
+ );
+ } else {
+ dataView.setColumnFilters( [
+ ...otherFilters,
+ {
+ [ filter.id + ':in' ]:
+ element.value,
+ },
+ ] );
+ }
+ } }
+ >
+ { element.label }
+
+ );
+ } ) }
+
+
+ ) }
);
@@ -186,6 +299,58 @@ function ViewList( {
);
}, [ view.hiddenFields ] );
+ /**
+ * Transform the filters from the view format into the tanstack columns filter format.
+ *
+ * Input:
+ *
+ * view.filters = [
+ * { field: 'date', operator: 'before', value: '2020-01-01' },
+ * { field: 'date', operator: 'after', value: '2020-01-01' },
+ * ]
+ *
+ * Output:
+ *
+ * columnFilters = [
+ * { "date:before": '2020-01-01' },
+ * { "date:after": '2020-01-01' }
+ * ]
+ *
+ * @param {Array} filters The view filters to transform.
+ * @return {Array} The transformed TanStack column filters.
+ */
+ const toTanStackColumnFilters = ( filters ) =>
+ filters.map( ( filter ) => ( {
+ [ filter.field + ':' + filter.operator ]: filter.value,
+ } ) );
+
+ /**
+ * Transform the filters from the view format into the tanstack columns filter format.
+ *
+ * Input:
+ *
+ * columnFilters = [
+ * { "date:before": '2020-01-01'},
+ * { "date:after": '2020-01-01' }
+ * ]
+ *
+ * Output:
+ *
+ * view.filters = [
+ * { field: 'date', operator: 'before', value: '2020-01-01' },
+ * { field: 'date', operator: 'after', value: '2020-01-01' },
+ * ]
+ *
+ * @param {Array} filters The TanStack column filters to transform.
+ * @return {Array} The transformed view filters.
+ */
+ const fromTanStackColumnFilters = ( filters ) =>
+ filters.map( ( filter ) => {
+ const [ key, value ] = Object.entries( filter )[ 0 ];
+ const [ field, operator ] = key.split( ':' );
+ return { field, operator, value };
+ } );
+
const dataView = useReactTable( {
data,
columns,
@@ -203,6 +368,7 @@ function ViewList( {
]
: [],
globalFilter: view.search,
+ columnFilters: toTanStackColumnFilters( view.filters ),
pagination: {
pageIndex: view.page,
pageSize: view.perPage,
@@ -261,7 +427,14 @@ function ViewList( {
} );
},
onGlobalFilterChange: ( value ) => {
- onChangeView( { ...view, search: value, page: 0 } );
+ onChangeView( { ...view, search: value, page: 1 } );
+ },
+ onColumnFiltersChange: ( columnFiltersUpdater ) => {
+ onChangeView( {
+ ...view,
+ filters: fromTanStackColumnFilters( columnFiltersUpdater() ),
+ page: 1,
+ } );
},
onPaginationChange: ( paginationUpdater ) => {
onChangeView( ( currentView ) => {
diff --git a/packages/icons/CHANGELOG.md b/packages/icons/CHANGELOG.md
index 0b7ccc517defaa..d99b95eb4bf371 100644
--- a/packages/icons/CHANGELOG.md
+++ b/packages/icons/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### New features
+
+- Add new `funnel` icon.
+
## 9.36.0 (2023-11-02)
## 9.35.0 (2023-10-18)
diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js
index f135e77e066bed..bb078b348e6043 100644
--- a/packages/icons/src/index.js
+++ b/packages/icons/src/index.js
@@ -95,6 +95,7 @@ export { default as formatStrikethrough } from './library/format-strikethrough';
export { default as formatUnderline } from './library/format-underline';
export { default as formatUppercase } from './library/format-uppercase';
export { default as fullscreen } from './library/fullscreen';
+export { default as funnel } from './library/funnel';
export { default as gallery } from './library/gallery';
export { default as globe } from './library/globe';
export { default as grid } from './library/grid';
diff --git a/packages/icons/src/library/funnel.js b/packages/icons/src/library/funnel.js
new file mode 100644
index 00000000000000..87687dc7608db9
--- /dev/null
+++ b/packages/icons/src/library/funnel.js
@@ -0,0 +1,12 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/primitives';
+
+const funnel = (
+
+);
+
+export default funnel;