diff --git a/package-lock.json b/package-lock.json
index 3768fe12dcc02..d12068330d7d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3986,9 +3986,9 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
},
"node_modules/@babel/runtime": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
- "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "version": "7.23.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
+ "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -54311,6 +54311,7 @@
"version": "0.4.1",
"license": "GPL-2.0-or-later",
"dependencies": {
+ "@ariakit/react": "^0.3.12",
"@babel/runtime": "^7.16.0",
"@wordpress/a11y": "file:../a11y",
"@wordpress/components": "file:../components",
@@ -54331,6 +54332,41 @@
"react": "^18.0.0"
}
},
+ "packages/dataviews/node_modules/@ariakit/core": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.1.tgz",
+ "integrity": "sha512-Rdhw0/K0x+50gFvzuMW9wp+WJxpkrgiMgegRTOZSU92bv1K+6XfQWnlieIkLt2FC7pZGrDpGlS4C7ztEVF+JRg=="
+ },
+ "packages/dataviews/node_modules/@ariakit/react": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.1.tgz",
+ "integrity": "sha512-hKfCYjc3MFW20kn2dcvejB5zbYt/uU33Teq82c414/utf5sEoeRF+bxjNku8x1baJby9/SDP6zj2IgWPuedFNA==",
+ "dependencies": {
+ "@ariakit/react-core": "0.4.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ariakit"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ }
+ },
+ "packages/dataviews/node_modules/@ariakit/react-core": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.1.tgz",
+ "integrity": "sha512-cwDczl9XWBloXNg0CuHmJtBfEe7qF265JE0Pwlcp8wMSY9PsJeb0mKBlTygUPKn/FsKpKGaYSI7DlDntbcZciw==",
+ "dependencies": {
+ "@ariakit/core": "0.4.1",
+ "@floating-ui/dom": "^1.0.0",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ }
+ },
"packages/date": {
"name": "@wordpress/date",
"version": "4.50.0",
@@ -58772,9 +58808,9 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
},
"@babel/runtime": {
- "version": "7.22.10",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
- "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
+ "version": "7.23.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
+ "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
"requires": {
"regenerator-runtime": "^0.14.0"
},
@@ -69334,6 +69370,7 @@
"@wordpress/dataviews": {
"version": "file:packages/dataviews",
"requires": {
+ "@ariakit/react": "^0.3.12",
"@babel/runtime": "^7.16.0",
"@wordpress/a11y": "file:../a11y",
"@wordpress/components": "file:../components",
@@ -69346,6 +69383,30 @@
"@wordpress/private-apis": "file:../private-apis",
"classnames": "^2.3.1",
"remove-accents": "^0.5.0"
+ },
+ "dependencies": {
+ "@ariakit/core": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.1.tgz",
+ "integrity": "sha512-Rdhw0/K0x+50gFvzuMW9wp+WJxpkrgiMgegRTOZSU92bv1K+6XfQWnlieIkLt2FC7pZGrDpGlS4C7ztEVF+JRg=="
+ },
+ "@ariakit/react": {
+ "version": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.1.tgz",
+ "integrity": "sha512-hKfCYjc3MFW20kn2dcvejB5zbYt/uU33Teq82c414/utf5sEoeRF+bxjNku8x1baJby9/SDP6zj2IgWPuedFNA==",
+ "requires": {
+ "@ariakit/react-core": "0.4.1"
+ }
+ },
+ "@ariakit/react-core": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.1.tgz",
+ "integrity": "sha512-cwDczl9XWBloXNg0CuHmJtBfEe7qF265JE0Pwlcp8wMSY9PsJeb0mKBlTygUPKn/FsKpKGaYSI7DlDntbcZciw==",
+ "requires": {
+ "@ariakit/core": "0.4.1",
+ "@floating-ui/dom": "^1.0.0",
+ "use-sync-external-store": "^1.2.0"
+ }
+ }
}
},
"@wordpress/date": {
diff --git a/packages/dataviews/package.json b/packages/dataviews/package.json
index 16266bc032d20..605151a00e610 100644
--- a/packages/dataviews/package.json
+++ b/packages/dataviews/package.json
@@ -27,6 +27,7 @@
"types": "build-types",
"sideEffects": false,
"dependencies": {
+ "@ariakit/react": "^0.3.12",
"@babel/runtime": "^7.16.0",
"@wordpress/a11y": "file:../a11y",
"@wordpress/components": "file:../components",
diff --git a/packages/dataviews/src/add-filter.js b/packages/dataviews/src/add-filter.js
index 99078fb5e82e6..b86f22bba10ba 100644
--- a/packages/dataviews/src/add-filter.js
+++ b/packages/dataviews/src/add-filter.js
@@ -5,273 +5,70 @@ import {
privateApis as componentsPrivateApis,
Button,
} from '@wordpress/components';
-import { funnel } from '@wordpress/icons';
-import { __, sprintf } from '@wordpress/i18n';
-import { Children, Fragment } from '@wordpress/element';
+import { plus } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import { forwardRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import { unlock } from './lock-unlock';
-import { LAYOUT_LIST, OPERATORS } from './constants';
-import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
const {
DropdownMenuV2: DropdownMenu,
- DropdownMenuGroupV2: DropdownMenuGroup,
DropdownMenuItemV2: DropdownMenuItem,
- DropdownMenuRadioItemV2: DropdownMenuRadioItem,
- DropdownMenuSeparatorV2: DropdownMenuSeparator,
DropdownMenuItemLabelV2: DropdownMenuItemLabel,
- DropdownMenuItemHelpTextV2: DropdownMenuItemHelpText,
} = unlock( componentsPrivateApis );
-function WithSeparators( { children } ) {
- return Children.toArray( children )
- .filter( Boolean )
- .map( ( child, i ) => (
-
- { i > 0 && }
- { child }
-
- ) );
-}
-
-export default function AddFilter( { filters, view, onChangeView } ) {
- if ( filters.length === 0 ) {
+function AddFilter( { filters, view, onChangeView, setOpenedFilter }, ref ) {
+ if ( ! filters.length || filters.every( ( { isPrimary } ) => isPrimary ) ) {
return null;
}
-
- const filterCount = view.filters.reduce( ( acc, filter ) => {
- if ( filter.value !== undefined ) {
- return acc + 1;
- }
- return acc;
- }, 0 );
-
- const isPrimary = ( field ) =>
- filters.some( ( f ) => f.field === field && f.isPrimary );
- let isResetDisabled = true;
- if (
- view.filters?.length > 0 &&
- ( view.filters.some( ( filter ) => filter.value !== undefined ) ||
- view.filters.some(
- ( filter ) =>
- filter.value === undefined && ! isPrimary( filter.field )
- ) )
- ) {
- isResetDisabled = false;
- }
-
+ const inactiveFilters = filters.filter( ( filter ) => ! filter.isVisible );
return (
- { view.type === LAYOUT_LIST && filterCount > 0 ? (
-
- { filterCount }
-
- ) : null }
+ { __( 'Add filter' ) }
}
- style={ {
- minWidth: '230px',
- } }
>
-
-
- { filters.map( ( filter ) => {
- const filterInView = view.filters.find(
- ( f ) => f.field === filter.field
- );
- const otherFilters = view.filters.filter(
- ( f ) => f.field !== filter.field
- );
- const activeElement = filter.elements.find(
- ( element ) => element.value === filterInView?.value
- );
- const activeOperator =
- filterInView?.operator || filter.operators[ 0 ];
- return (
-
- { activeOperator in
- OPERATORS &&
- `${ OPERATORS[ activeOperator ].label } ` }
- { activeElement.label }
-
- )
- }
- >
-
- { filter.name }
-
-
- }
- style={ {
- minWidth: '200px',
- } }
- >
-
-
- { filter.elements.map( ( element ) => {
- const isActive =
- activeElement?.value ===
- element.value;
- return (
- {
- onChangeView( {
- ...view,
- page: 1,
- filters: [
- ...otherFilters,
- {
- field: filter.field,
- operator:
- activeOperator,
- value: isActive
- ? undefined
- : e
- .target
- .value,
- },
- ],
- } );
- } }
- >
-
- { element.label }
-
- { !! element.description && (
-
- {
- element.description
- }
-
- ) }
-
- );
- } ) }
-
- { filter.operators.length > 1 && (
-
- {
- OPERATORS[
- activeOperator
- ]?.label
- }
-
- }
- >
-
- { __( 'Conditions' ) }
-
-
- }
- >
- { Object.entries( OPERATORS ).map(
- ( [
- operator,
- { label, key },
- ] ) => (
- {
- onChangeView( {
- ...view,
- page: 1,
- filters: [
- ...otherFilters,
- {
- field: filter.field,
- operator:
- e
- .target
- .value,
- value: filterInView?.value,
- },
- ],
- } );
- } }
- >
-
- { label }
-
-
- )
- ) }
-
- ) }
- {
- onChangeView( {
- ...view,
- page: 1,
- filters: view.filters.filter(
- ( f ) =>
- f.field !== filter.field
- ),
- } );
- } }
- >
-
- { sprintf(
- /* translators: 1: Filter name. e.g.: "Reset Author". */
- __( 'Reset %1$s' ),
- filter.name.toLowerCase()
- ) }
-
-
-
-
- );
- } ) }
-
- {
- onChangeView( {
- ...view,
- page: 1,
- filters: [],
- } );
- } }
- >
-
- { __( 'Reset filters' ) }
-
-
-
+ { inactiveFilters.map( ( filter ) => {
+ return (
+ {
+ setOpenedFilter( filter.field );
+ onChangeView( {
+ ...view,
+ page: 1,
+ filters: [
+ ...( view.filters || [] ),
+ {
+ field: filter.field,
+ value: undefined,
+ operator: filter.operators[ 0 ],
+ },
+ ],
+ } );
+ } }
+ >
+
+ { filter.name }
+
+
+ );
+ } ) }
);
}
+
+export default forwardRef( AddFilter );
diff --git a/packages/dataviews/src/dataviews.js b/packages/dataviews/src/dataviews.js
index cff15ec304c23..c7ae6b6e27aad 100644
--- a/packages/dataviews/src/dataviews.js
+++ b/packages/dataviews/src/dataviews.js
@@ -37,6 +37,7 @@ export default function DataViews( {
deferredRendering = false,
} ) {
const [ selection, setSelection ] = useState( [] );
+ const [ openedFilter, setOpenedFilter ] = useState( null );
useEffect( () => {
if (
@@ -76,27 +77,20 @@ export default function DataViews( {
}, [ fields ] );
return (
-
+
-
- { search && (
-
- ) }
-
-
- { ( view.type === LAYOUT_TABLE ||
- view.type === LAYOUT_GRID ) && (
+ ) }
+ { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) && (
+
+
+
{
if ( activeElement === undefined ) {
@@ -62,127 +54,145 @@ const FilterText = ( { activeElement, filterInView, filter } ) => {
);
};
-function WithSeparators( { children } ) {
- return Children.toArray( children )
- .filter( Boolean )
- .map( ( child, i ) => (
-
- { i > 0 && }
- { child }
-
- ) );
+function OperatorSelector( { filter, view, onChangeView } ) {
+ const operatorOptions = filter.operators?.map( ( operator ) => ( {
+ value: operator,
+ label: OPERATORS[ operator ]?.label,
+ } ) );
+ const currentFilter = view.filters.find(
+ ( _filter ) => _filter.field === filter.field
+ );
+ const value = currentFilter?.operator || filter.operators[ 0 ];
+ return (
+ operatorOptions.length > 1 && (
+
+
+ { filter.name }
+
+
+ {
+ const newFilters = currentFilter
+ ? [
+ ...view.filters.map( ( _filter ) => {
+ if ( _filter.field === filter.field ) {
+ return {
+ ..._filter,
+ operator: newValue,
+ };
+ }
+ return _filter;
+ } ),
+ ]
+ : [
+ ...view.filters,
+ {
+ field: filter.field,
+ operator: newValue,
+ },
+ ];
+ onChangeView( {
+ ...view,
+ page: 1,
+ filters: newFilters,
+ } );
+ } }
+ size="small"
+ __nextHasNoMarginBottom
+ hideLabelFromVision
+ />
+
+ )
+ );
}
-export default function FilterSummary( { filter, view, onChangeView } ) {
- const filterInView = view.filters.find( ( f ) => f.field === filter.field );
- const otherFilters = view.filters.filter(
- ( f ) => f.field !== filter.field
+function ResetFilter( { filter, view, onChangeView, addFilterRef } ) {
+ const isDisabled =
+ filter.isPrimary &&
+ view.filters.find( ( _filter ) => _filter.field === filter.field )
+ ?.value === undefined;
+ return (
+
+
+
);
+}
+
+export default function FilterSummary( {
+ addFilterRef,
+ openedFilter,
+ ...commonProps
+} ) {
+ const toggleRef = useRef();
+ const { filter, view } = commonProps;
+ const filterInView = view.filters.find( ( f ) => f.field === filter.field );
const activeElement = filter.elements.find(
( element ) => element.value === filterInView?.value
);
- const activeOperator = filterInView?.operator || filter.operators[ 0 ];
-
return (
-
+ {
+ toggleRef.current?.focus();
+ } }
+ renderToggle={ ( { isOpen, onToggle } ) => (
+
- }
- >
-
-
- { filter.elements.map( ( element ) => {
- const isActive = activeElement?.value === element.value;
- return (
-
- onChangeView( {
- ...view,
- page: 1,
- filters: [
- ...otherFilters,
- {
- field: filter.field,
- operator: activeOperator,
- value: isActive
- ? undefined
- : element.value,
- },
- ],
- } )
- }
- >
-
- { element.label }
-
- { !! element.description && (
-
- { element.description }
-
- ) }
-
- );
- } ) }
-
- { filter.operators.length > 1 && (
-
- { OPERATORS[ activeOperator ]?.label }
-
- }
- >
-
- { __( 'Conditions' ) }
-
-
- }
- >
- { Object.entries( OPERATORS ).map(
- ( [ operator, { label, key } ] ) => (
- {
- onChangeView( {
- ...view,
- page: 1,
- filters: [
- ...otherFilters,
- {
- field: filter.field,
- operator: e.target.value,
- value: filterInView?.value,
- },
- ],
- } );
- } }
- >
-
- { label }
-
-
- )
- ) }
-
- ) }
-
-
+ ) }
+ renderContent={ () => {
+ return (
+
+
+
+
+
+ );
+ } }
+ />
);
}
diff --git a/packages/dataviews/src/filters.js b/packages/dataviews/src/filters.js
index a3dc193605620..80008fb6e569d 100644
--- a/packages/dataviews/src/filters.js
+++ b/packages/dataviews/src/filters.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { memo } from '@wordpress/element';
+import { memo, useRef } from '@wordpress/element';
/**
* Internal dependencies
@@ -9,24 +9,17 @@ import { memo } from '@wordpress/element';
import FilterSummary from './filter-summary';
import AddFilter from './add-filter';
import ResetFilters from './reset-filters';
-import {
- ENUMERATION_TYPE,
- OPERATOR_IN,
- OPERATOR_NOT_IN,
- LAYOUT_LIST,
-} from './constants';
+import { sanitizeOperators } from './utils';
+import { ENUMERATION_TYPE, OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
-const sanitizeOperators = ( field ) => {
- let operators = field.filterBy?.operators;
- if ( ! operators || ! Array.isArray( operators ) ) {
- operators = [ OPERATOR_IN, OPERATOR_NOT_IN ];
- }
- return operators.filter( ( operator ) =>
- [ OPERATOR_IN, OPERATOR_NOT_IN ].includes( operator )
- );
-};
-
-const Filters = memo( function Filters( { fields, view, onChangeView } ) {
+const Filters = memo( function Filters( {
+ fields,
+ view,
+ onChangeView,
+ openedFilter,
+ setOpenedFilter,
+} ) {
+ const addFilterRef = useRef();
const filters = [];
fields.forEach( ( field ) => {
if ( ! field.type ) {
@@ -63,19 +56,30 @@ const Filters = memo( function Filters( { fields, view, onChangeView } ) {
} );
}
} );
-
+ // Sort filters by primary property. We need the primary filters to be first.
+ // Then we sort by name.
+ filters.sort( ( a, b ) => {
+ if ( a.isPrimary && ! b.isPrimary ) {
+ return -1;
+ }
+ if ( ! a.isPrimary && b.isPrimary ) {
+ return 1;
+ }
+ return a.name.localeCompare( b.name );
+ } );
const addFilter = (
);
const filterComponents = [
- addFilter,
...filters.map( ( filter ) => {
- if ( ! filter.isVisible || view.type === LAYOUT_LIST ) {
+ if ( ! filter.isVisible ) {
return null;
}
@@ -85,12 +89,15 @@ const Filters = memo( function Filters( { fields, view, onChangeView } ) {
filter={ filter }
view={ view }
onChangeView={ onChangeView }
+ addFilterRef={ addFilterRef }
+ openedFilter={ openedFilter }
/>
);
} ),
+ addFilter,
];
- if ( filterComponents.length > 1 && view.type !== LAYOUT_LIST ) {
+ if ( filterComponents.length > 1 ) {
filterComponents.push(
- filters.some( ( f ) => f.field === field && f.isPrimary );
- let isDisabled = true;
- if ( view.search !== '' ) {
- isDisabled = false;
- } else if (
- view.filters?.length > 0 &&
- ( view.filters.some( ( filter ) => filter.value !== undefined ) ||
- view.filters.some(
- ( filter ) =>
- filter.value === undefined && ! isPrimary( filter.field )
- ) )
- ) {
- isDisabled = false;
- }
-
+ filters.some(
+ ( _filter ) => _filter.field === field && _filter.isPrimary
+ );
+ const isDisabled =
+ ! view.search &&
+ ! view.filters?.some(
+ ( _filter ) =>
+ _filter.value !== undefined || ! isPrimary( _filter.field )
+ );
return (