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 ( - - ); - } ) } - - { - 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 && ( - - ) } - - + ) } + 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 (