diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss
index 0333a8bb6adf9..109926ebf411a 100644
--- a/packages/base-styles/_mixins.scss
+++ b/packages/base-styles/_mixins.scss
@@ -297,6 +297,16 @@
}
}
}
+
+ &[aria-disabled="true"],
+ &:disabled {
+ background: $gray-100;
+ border-color: $gray-300;
+ cursor: default;
+
+ // Override style inherited from wp-admin. Required to avoid degraded appearance on different backgrounds.
+ opacity: 1;
+ }
}
@mixin radio-control {
diff --git a/packages/dataviews/src/bulk-actions.js b/packages/dataviews/src/bulk-actions.js
index 9fd9f628286e0..5e4139fe0622e 100644
--- a/packages/dataviews/src/bulk-actions.js
+++ b/packages/dataviews/src/bulk-actions.js
@@ -7,7 +7,7 @@ import {
Modal,
} from '@wordpress/components';
import { __, sprintf, _n } from '@wordpress/i18n';
-import { useMemo, useState, useCallback } from '@wordpress/element';
+import { useMemo, useState, useCallback, useEffect } from '@wordpress/element';
/**
* Internal dependencies
@@ -21,6 +21,24 @@ const {
DropdownMenuSeparatorV2: DropdownMenuSeparator,
} = unlock( componentsPrivateApis );
+export function useHasAPossibleBulkAction( actions, item ) {
+ return useMemo( () => {
+ return actions.some( ( action ) => {
+ return action.supportsBulk && action.isEligible( item );
+ } );
+ }, [ actions, item ] );
+}
+
+export function useSomeItemHasAPossibleBulkAction( actions, data ) {
+ return useMemo( () => {
+ return data.some( ( item ) => {
+ return actions.some( ( action ) => {
+ return action.supportsBulk && action.isEligible( item );
+ } );
+ } );
+ }, [ actions, data ] );
+}
+
function ActionWithModal( {
action,
selectedItems,
@@ -107,15 +125,47 @@ export default function BulkActions( {
() => actions.filter( ( action ) => action.supportsBulk ),
[ actions ]
);
- const areAllSelected = selection && selection.length === data.length;
const [ isMenuOpen, onMenuOpenChange ] = useState( false );
const [ actionWithModal, setActionWithModal ] = useState();
+ const selectableItems = useMemo( () => {
+ return data.filter( ( item ) => {
+ return bulkActions.some( ( action ) => action.isEligible( item ) );
+ } );
+ }, [ data, bulkActions ] );
+
+ const numberSelectableItems = selectableItems.length;
+ const areAllSelected =
+ selection && selection.length === numberSelectableItems;
+
const selectedItems = useMemo( () => {
return data.filter( ( item ) =>
selection.includes( getItemId( item ) )
);
}, [ selection, data, getItemId ] );
+ const hasNonSelectableItemSelected = useMemo( () => {
+ return selectedItems.some( ( item ) => {
+ return ! selectableItems.includes( item );
+ } );
+ }, [ selectedItems, selectableItems ] );
+ useEffect( () => {
+ if ( hasNonSelectableItemSelected ) {
+ onSelectionChange(
+ selectedItems.filter( ( selectedItem ) => {
+ return selectableItems.some( ( item ) => {
+ return getItemId( selectedItem ) === getItemId( item );
+ } );
+ } )
+ );
+ }
+ }, [
+ hasNonSelectableItemSelected,
+ selectedItems,
+ selectableItems,
+ getItemId,
+ onSelectionChange,
+ ] );
+
if ( bulkActions.length === 0 ) {
return null;
}
@@ -157,9 +207,9 @@ export default function BulkActions( {
disabled={ areAllSelected }
hideOnClick={ false }
onClick={ () => {
- onSelectionChange( data );
+ onSelectionChange( selectableItems );
} }
- suffix={ data.length }
+ suffix={ numberSelectableItems }
>
{ __( 'Select all' ) }
diff --git a/packages/dataviews/src/dataviews.js b/packages/dataviews/src/dataviews.js
index 49143d0bb0753..9bcf531006406 100644
--- a/packages/dataviews/src/dataviews.js
+++ b/packages/dataviews/src/dataviews.js
@@ -20,6 +20,16 @@ import BulkActions from './bulk-actions';
const defaultGetItemId = ( item ) => item.id;
const defaultOnSelectionChange = () => {};
+function useSomeItemHasAPossibleBulkAction( actions, data ) {
+ return useMemo( () => {
+ return data.some( ( item ) => {
+ return actions.some( ( action ) => {
+ return action.supportsBulk && action.isEligible( item );
+ } );
+ } );
+ }, [ actions, data ] );
+}
+
export default function DataViews( {
view,
onChangeView,
@@ -75,6 +85,11 @@ export default function DataViews( {
render: field.render || field.getValue,
} ) );
}, [ fields ] );
+
+ const hasPossibleBulkAction = useSomeItemHasAPossibleBulkAction(
+ actions,
+ data
+ );
return (
@@ -103,15 +118,16 @@ export default function DataViews( {
setOpenedFilter={ setOpenedFilter }
/>
- { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) && (
-
- ) }
+ { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) &&
+ hasPossibleBulkAction && (
+
+ ) }
{
if ( ! isSelected ) {
onSelectionChange(
diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss
index e865ff8a25fed..7e45bec25856f 100644
--- a/packages/dataviews/src/style.scss
+++ b/packages/dataviews/src/style.scss
@@ -119,7 +119,7 @@
background-color: #f8f8f8;
}
- .components-checkbox-control__input {
+ .components-checkbox-control__input.components-checkbox-control__input {
opacity: 0;
&:checked,
diff --git a/packages/dataviews/src/view-grid.js b/packages/dataviews/src/view-grid.js
index 44ab1822a6075..3b46a7424dc1b 100644
--- a/packages/dataviews/src/view-grid.js
+++ b/packages/dataviews/src/view-grid.js
@@ -22,6 +22,8 @@ import { useState } from '@wordpress/element';
import ItemActions from './item-actions';
import SingleSelectionCheckbox from './single-selection-checkbox';
+import { useHasAPossibleBulkAction } from './bulk-actions';
+
function GridItem( {
selection,
data,
@@ -34,6 +36,7 @@ function GridItem( {
visibleFields,
} ) {
const [ hasNoPointerEvents, setHasNoPointerEvents ] = useState( false );
+ const hasBulkAction = useHasAPossibleBulkAction( actions, item );
const id = getItemId( item );
const isSelected = selection.includes( id );
return (
@@ -41,11 +44,11 @@ function GridItem( {
spacing={ 0 }
key={ id }
className={ classnames( 'dataviews-view-grid__card', {
- 'is-selected': isSelected,
+ 'is-selected': hasBulkAction && isSelected,
'has-no-pointer-events': hasNoPointerEvents,
} ) }
onMouseDown={ ( event ) => {
- if ( event.ctrlKey || event.metaKey ) {
+ if ( hasBulkAction && ( event.ctrlKey || event.metaKey ) ) {
setHasNoPointerEvents( true );
if ( ! isSelected ) {
onSelectionChange(
@@ -91,6 +94,7 @@ function GridItem( {
getItemId={ getItemId }
data={ data }
primaryField={ primaryField }
+ disabled={ ! hasBulkAction }
/>
{ primaryField?.render( { item } ) }
diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js
index dc16657703425..9737aa3d46283 100644
--- a/packages/dataviews/src/view-table.js
+++ b/packages/dataviews/src/view-table.js
@@ -23,6 +23,7 @@ import {
useState,
Children,
Fragment,
+ useMemo,
} from '@wordpress/element';
/**
@@ -33,6 +34,10 @@ import { unlock } from './lock-unlock';
import ItemActions from './item-actions';
import { sanitizeOperators } from './utils';
import { ENUMERATION_TYPE, SORTING_DIRECTIONS } from './constants';
+import {
+ useSomeItemHasAPossibleBulkAction,
+ useHasAPossibleBulkAction,
+} from './bulk-actions';
const {
DropdownMenuV2: DropdownMenu,
@@ -186,8 +191,20 @@ const HeaderMenu = forwardRef( function HeaderMenu(
);
} );
-function BulkSelectionCheckbox( { selection, onSelectionChange, data } ) {
- const areAllSelected = selection.length === data.length;
+function BulkSelectionCheckbox( {
+ selection,
+ onSelectionChange,
+ data,
+ actions,
+} ) {
+ const selectableItems = useMemo( () => {
+ return data.filter( ( item ) => {
+ return actions.some(
+ ( action ) => action.supportsBulk && action.isEligible( item )
+ );
+ } );
+ }, [ data, actions ] );
+ const areAllSelected = selection.length === selectableItems.length;
return (
+ { hasBulkActions && (
+
+
+
+
+ |
+ ) }
+ { visibleFields.map( ( field ) => (
+
+
+ { field.render( {
+ item,
+ } ) }
+
+ |
+ ) ) }
+ { !! actions?.length && (
+
+
+ |
+ ) }
+
+ );
+}
+
function ViewTable( {
view,
onChangeView,
@@ -219,10 +311,10 @@ function ViewTable( {
onSelectionChange,
setOpenedFilter,
} ) {
- const hasBulkActions = actions?.some( ( action ) => action.supportsBulk );
const headerMenuRefs = useRef( new Map() );
const headerMenuToFocusRef = useRef();
const [ nextHeaderMenuToFocus, setNextHeaderMenuToFocus ] = useState();
+ const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data );
useEffect( () => {
if ( headerMenuToFocusRef.current ) {
@@ -285,6 +377,7 @@ function ViewTable( {
selection={ selection }
onSelectionChange={ onSelectionChange }
data={ data }
+ actions={ actions }
/>
) }
@@ -347,78 +440,19 @@ function ViewTable( {
{ hasData &&
usedData.map( ( item, index ) => (
-
- { hasBulkActions && (
-
-
-
-
- |
- ) }
- { visibleFields.map( ( field ) => (
-
-
- { field.render( {
- item,
- } ) }
-
- |
- ) ) }
- { !! actions?.length && (
-
-
- |
- ) }
-
+ item={ item }
+ hasBulkActions={ hasBulkActions }
+ actions={ actions }
+ id={ getItemId( item ) || index }
+ visibleFields={ visibleFields }
+ primaryField={ primaryField }
+ selection={ selection }
+ getItemId={ getItemId }
+ onSelectionChange={ onSelectionChange }
+ data={ data }
+ />
) ) }