Skip to content

Commit

Permalink
Add: Bulk actions to dataviews with the new design. (#57255)
Browse files Browse the repository at this point in the history
* Add: Bulk actions to dataviews with the new design.

* post rebase fixes.

* Add missing secondary variant

* Move bulk actions button position

* Don't render bulk actions functionality when there are no bulk actions avaliable.

* Fix button padding

* Fix focus loss

* fix flex shring

* Fix focus issues

* remove data items meanwhile removed from the selection

* remove line accidently added

* Fix vertical alignment post rebase.

* Fix unrequired decodeEntities usage.

* Apply feedback to the promises

* lint fix

* Feedback application

* Labels object instead of function

* Introduce removeTemplates action

* fix css rebase issue

* lint fixes.

* remove labels

* simplify logic condition

* fix some classnames

* multiple small changes of feedback

* remove getItemTitle

* Inspect Promise.allSettled result

* typo

* case fixing

* update patterns actions

* Added a selection mark

* lint fixes

* Style adjustments

---------

Co-authored-by: James Koster <[email protected]>
  • Loading branch information
jorgefilipecosta and jameskoster authored Jan 11, 2024
1 parent 167c439 commit f154dc7
Show file tree
Hide file tree
Showing 11 changed files with 628 additions and 115 deletions.
187 changes: 187 additions & 0 deletions packages/dataviews/src/bulk-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* WordPress dependencies
*/
import {
privateApis as componentsPrivateApis,
Button,
Modal,
} from '@wordpress/components';
import { __, sprintf, _n } from '@wordpress/i18n';
import { useMemo, useState, useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import { unlock } from './lock-unlock';

const {
DropdownMenuV2: DropdownMenu,
DropdownMenuGroupV2: DropdownMenuGroup,
DropdownMenuItemV2: DropdownMenuItem,
DropdownMenuSeparatorV2: DropdownMenuSeparator,
} = unlock( componentsPrivateApis );

function ActionWithModal( {
action,
selectedItems,
setActionWithModal,
onMenuOpenChange,
} ) {
const eligibleItems = useMemo( () => {
return selectedItems.filter( ( item ) => action.isEligible( item ) );
}, [ action, selectedItems ] );
const { RenderModal, hideModalHeader } = action;
const onCloseModal = useCallback( () => {
setActionWithModal( undefined );
}, [ setActionWithModal ] );
return (
<Modal
title={ ! hideModalHeader && action.label }
__experimentalHideHeader={ !! hideModalHeader }
onRequestClose={ onCloseModal }
overlayClassName="dataviews-action-modal"
>
<RenderModal
items={ eligibleItems }
closeModal={ onCloseModal }
onPerform={ () => onMenuOpenChange( false ) }
/>
</Modal>
);
}

function BulkActionItem( { action, selectedItems, setActionWithModal } ) {
const eligibleItems = useMemo( () => {
return selectedItems.filter( ( item ) => action.isEligible( item ) );
}, [ action, selectedItems ] );

const shouldShowModal = !! action.RenderModal;

return (
<DropdownMenuItem
key={ action.id }
disabled={ eligibleItems.length === 0 }
hideOnClick={ ! shouldShowModal }
onClick={ async () => {
if ( shouldShowModal ) {
setActionWithModal( action );
} else {
await action.callback( eligibleItems );
}
} }
suffix={
eligibleItems.length > 0 ? eligibleItems.length : undefined
}
>
{ action.label }
</DropdownMenuItem>
);
}

function ActionsMenuGroup( { actions, selectedItems, setActionWithModal } ) {
return (
<>
<DropdownMenuGroup>
{ actions.map( ( action ) => (
<BulkActionItem
key={ action.id }
action={ action }
selectedItems={ selectedItems }
setActionWithModal={ setActionWithModal }
/>
) ) }
</DropdownMenuGroup>
<DropdownMenuSeparator />
</>
);
}

export default function BulkActions( {
data,
actions,
selection,
onSelectionChange,
getItemId,
} ) {
const bulkActions = useMemo(
() => actions.filter( ( action ) => action.supportsBulk ),
[ actions ]
);
const areAllSelected = selection && selection.length === data.length;
const [ isMenuOpen, onMenuOpenChange ] = useState( false );
const [ actionWithModal, setActionWithModal ] = useState();
const selectedItems = useMemo( () => {
return data.filter( ( item ) =>
selection.includes( getItemId( item ) )
);
}, [ selection, data, getItemId ] );

if ( bulkActions.length === 0 ) {
return null;
}
return (
<>
<DropdownMenu
open={ isMenuOpen }
onOpenChange={ onMenuOpenChange }
label={ __( 'Bulk actions' ) }
style={ { minWidth: '240px' } }
trigger={
<Button
className="dataviews-bulk-edit-button"
__next40pxDefaultSize
variant="tertiary"
size="compact"
>
{ selection.length
? sprintf(
/* translators: %d: Number of items. */
_n(
'Edit %d item',
'Edit %d items',
selection.length
),
selection.length
)
: __( 'Bulk edit' ) }
</Button>
}
>
<ActionsMenuGroup
actions={ bulkActions }
setActionWithModal={ setActionWithModal }
selectedItems={ selectedItems }
/>
<DropdownMenuGroup>
<DropdownMenuItem
disabled={ areAllSelected }
hideOnClick={ false }
onClick={ () => {
onSelectionChange( data );
} }
suffix={ data.length }
>
{ __( 'Select all' ) }
</DropdownMenuItem>
<DropdownMenuItem
disabled={ selection.length === 0 }
hideOnClick={ false }
onClick={ () => {
onSelectionChange( [] );
} }
>
{ __( 'Deselect' ) }
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
{ actionWithModal && (
<ActionWithModal
action={ actionWithModal }
selectedItems={ selectedItems }
setActionWithModal={ setActionWithModal }
onMenuOpenChange={ onMenuOpenChange }
/>
) }
</>
);
}
31 changes: 29 additions & 2 deletions packages/dataviews/src/dataviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
__experimentalVStack as VStack,
__experimentalHStack as HStack,
} from '@wordpress/components';
import { useMemo, useState, useCallback } from '@wordpress/element';
import { useMemo, useState, useCallback, useEffect } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -14,7 +14,8 @@ import Pagination from './pagination';
import ViewActions from './view-actions';
import Filters from './filters';
import Search from './search';
import { VIEW_LAYOUTS } from './constants';
import { VIEW_LAYOUTS, LAYOUT_TABLE } from './constants';
import BulkActions from './bulk-actions';

const defaultGetItemId = ( item ) => item.id;
const defaultOnSelectionChange = () => {};
Expand All @@ -37,6 +38,23 @@ export default function DataViews( {
} ) {
const [ selection, setSelection ] = useState( [] );

useEffect( () => {
if (
selection.length > 0 &&
selection.some(
( id ) => ! data.some( ( item ) => item.id === id )
)
) {
const newSelection = selection.filter( ( id ) =>
data.some( ( item ) => item.id === id )
);
setSelection( newSelection );
onSelectionChange(
data.filter( ( item ) => newSelection.includes( item.id ) )
);
}
}, [ selection, data, onSelectionChange ] );

const onSetSelection = useCallback(
( items ) => {
setSelection( items.map( ( item ) => item.id ) );
Expand Down Expand Up @@ -75,6 +93,15 @@ export default function DataViews( {
onChangeView={ onChangeView }
/>
</HStack>
{ view.type === LAYOUT_TABLE && (
<BulkActions
actions={ actions }
data={ data }
onSelectionChange={ onSetSelection }
selection={ selection }
getItemId={ getItemId }
/>
) }
<ViewActions
fields={ _fields }
view={ view }
Expand Down
6 changes: 3 additions & 3 deletions packages/dataviews/src/item-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function ActionWithModal( { action, item, ActionTrigger } ) {
) }` }
>
<RenderModal
item={ item }
items={ [ item ] }
closeModal={ () => setIsModalOpen( false ) }
/>
</Modal>
Expand All @@ -96,7 +96,7 @@ function ActionsDropdownMenuGroup( { actions, item } ) {
<DropdownMenuItemTrigger
key={ action.id }
action={ action }
onClick={ () => action.callback( item ) }
onClick={ () => action.callback( [ item ] ) }
/>
);
} ) }
Expand Down Expand Up @@ -157,7 +157,7 @@ export default function ItemActions( { item, actions, isCompact } ) {
<ButtonTrigger
key={ action.id }
action={ action }
onClick={ () => action.callback( item ) }
onClick={ () => action.callback( [ item ] ) }
/>
);
} ) }
Expand Down
56 changes: 54 additions & 2 deletions packages/dataviews/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
}
}

.dataviews-filters__view-actions.components-h-stack {
align-items: center;
}

.dataviews-filters-button {
position: relative;
}
Expand Down Expand Up @@ -81,6 +85,14 @@
&[data-field-id="actions"] {
text-align: right;
}

&.dataviews-view-table__checkbox-column {
padding-right: 0;
}

.components-checkbox-control__input-container {
margin: $grid-unit-05;
}
}
tr {
border-bottom: 1px solid $gray-100;
Expand Down Expand Up @@ -109,8 +121,32 @@
}

&:hover {
td {
background-color: #f8f8f8;
background-color: #f8f8f8;
}

.components-checkbox-control__input {
opacity: 0;

&:checked,
&:indeterminate,
&:focus {
opacity: 1;
}
}

&:focus-within,
&:hover {
.components-checkbox-control__input {
opacity: 1;
}
}

&.is-selected {
background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04);
color: $gray-700;

&:hover {
background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08);
}
}
}
Expand Down Expand Up @@ -373,7 +409,23 @@
padding: 0 $grid-unit-40;
}

.dataviews-view-table-selection-checkbox label {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.dataviews-filters__custom-menu-radio-item-prefix {
display: block;
width: 24px;
}

.dataviews-bulk-edit-button.components-button {
flex-shrink: 0;
}
Loading

0 comments on commit f154dc7

Please sign in to comment.