Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: Bulk actions API to dataviews and an initial bulk trash action. #56476

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions packages/dataviews/src/bulk-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* WordPress dependencies
*/
import {
ToolbarButton,
Toolbar,
ToolbarGroup,
Popover,
} from '@wordpress/components';
import { useMemo } from '@wordpress/element';
import { __, _n, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ActionWithModal } from './item-actions';

function PrimaryActionTrigger( { action, onClick } ) {
return (
<ToolbarButton
label={ action.label }
icon={ action.icon }
isDestructive={ action.isDestructive }
size="compact"
onClick={ onClick }
/>
);
}

const EMPTY_ARRAY = [];

export default function BulkActions( {
data,
selection,
actions = EMPTY_ARRAY,
setSelection,
} ) {
const items = useMemo(
() =>
data?.filter( ( item ) => selection?.includes( item.id ) ) ??
EMPTY_ARRAY,
[ data, selection ]
);
const primaryActions = useMemo(
() =>
actions.filter( ( action ) => {
return (
action.isBulk &&
action.isPrimary &&
items.every( ( item ) => action.isEligible( item ) )
);
} ),
[ actions, items ]
);

if (
( selection && selection.length === 0 ) ||
primaryActions.length === 0
) {
return null;
}

return (
<Popover
placement="top-middle"
className="dataviews-bulk-actions-popover"
>
<Toolbar label="Bulk actions">
<div className="dataviews-bulk-actions-toolbar-wrapper">
<ToolbarGroup>
<ToolbarButton onClick={ () => {} } disabled={ true }>
{
// translators: %s: Total number of selected items.
sprintf(
// translators: %s: Total number of selected items.
_n(
'%s item selected',
'%s items selected',
selection.length
),
selection.length
)
}
</ToolbarButton>
<ToolbarButton
onClick={ () => {
setSelection( EMPTY_ARRAY );
} }
>
{ __( 'Deselect' ) }
</ToolbarButton>
</ToolbarGroup>
<ToolbarGroup>
{ primaryActions.map( ( action ) => {
if ( !! action.RenderModal ) {
return (
<ActionWithModal
key={ action.id }
action={ action }
items={ items }
ActionTrigger={ PrimaryActionTrigger }
/>
);
}
return (
<PrimaryActionTrigger
key={ action.id }
action={ action }
onClick={ () => action.callback( items ) }
/>
);
} ) }
</ToolbarGroup>
</div>
</Toolbar>
</Popover>
);
}
16 changes: 16 additions & 0 deletions packages/dataviews/src/dataviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ViewActions from './view-actions';
import Filters from './filters';
import Search from './search';
import { VIEW_LAYOUTS } from './constants';
import BulkActions from './bulk-actions';

export default function DataViews( {
view,
Expand All @@ -28,6 +29,9 @@ export default function DataViews( {
isLoading = false,
paginationInfo,
supportedLayouts,
selection,
setSelection,
labels,
} ) {
const ViewComponent = VIEW_LAYOUTS.find(
( v ) => v.type === view.type
Expand Down Expand Up @@ -72,12 +76,24 @@ export default function DataViews( {
data={ data }
getItemId={ getItemId }
isLoading={ isLoading }
selection={ selection }
setSelection={ setSelection }
labels={ labels }
/>

<div>
<Pagination
view={ view }
onChangeView={ onChangeView }
paginationInfo={ paginationInfo }
/>
<BulkActions
data={ data }
actions={ actions }
selection={ selection }
setSelection={ setSelection }
/>
</div>
</VStack>
</div>
);
Expand Down
16 changes: 13 additions & 3 deletions packages/dataviews/src/item-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,18 @@ function DropdownMenuItemTrigger( { action, onClick } ) {
);
}

function ActionWithModal( { action, item, ActionTrigger } ) {
export function ActionWithModal( { action, item, items, ActionTrigger } ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const actionTriggerProps = {
action,
onClick: () => setIsModalOpen( true ),
};
const additionalProps = {};
if ( action.isBulk ) {
additionalProps.items = items ? items : [ item ];
} else {
additionalProps.item = item;
}
const { RenderModal, hideModalHeader } = action;
return (
<>
Expand All @@ -66,7 +72,7 @@ function ActionWithModal( { action, item, ActionTrigger } ) {
overlayClassName="dataviews-action-modal"
>
<RenderModal
item={ item }
{ ...additionalProps }
closeModal={ () => setIsModalOpen( false ) }
/>
</Modal>
Expand Down Expand Up @@ -157,7 +163,11 @@ export default function ItemActions( { item, actions, isCompact } ) {
<ButtonTrigger
key={ action.id }
action={ action }
onClick={ () => action.callback( item ) }
onClick={
action.isBulk
? () => action.callback( [ item ] )
: () => action.callback( item )
}
/>
);
} ) }
Expand Down
22 changes: 22 additions & 0 deletions packages/dataviews/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,25 @@
.dataviews-action-modal {
z-index: z-index(".dataviews-action-modal");
}

.dataviews-bulk-actions-popover .components-popover__content {
min-width: max-content;
}

.dataviews-bulk-actions-toolbar-wrapper {
display: flex;
flex-grow: 1;
width: 100%;
}

.dataviews-table-view__selection-column label {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
87 changes: 86 additions & 1 deletion packages/dataviews/src/view-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
Button,
Icon,
privateApis as componentsPrivateApis,
CheckboxControl,
} from '@wordpress/components';
import { useMemo, Children, Fragment } from '@wordpress/element';

Expand Down Expand Up @@ -340,7 +341,11 @@ function ViewTable( {
getItemId,
isLoading = false,
paginationInfo,
selection,
setSelection,
labels,
} ) {
const areAllSelected = selection && selection.length === data.length;
const columns = useMemo( () => {
const _columns = fields.map( ( field ) => {
const { render, getValue, ...column } = field;
Expand All @@ -351,6 +356,70 @@ function ViewTable( {
}
return column;
} );
if ( selection !== undefined ) {
_columns.unshift( {
header: (
<CheckboxControl
__nextHasNoMarginBottom
checked={ areAllSelected }
indeterminate={ ! areAllSelected && selection.length }
onChange={ () => {
if ( areAllSelected ) {
setSelection( [] );
} else {
setSelection( data.map( ( { id } ) => id ) );
}
} }
label={
areAllSelected
? __( 'Deselect all' )
: __( 'Select all' )
}
/>
),
id: 'selection',
cell: ( props ) => {
//console.log({ props });
const item = props.row.original;
const isSelected = selection.includes( item.id );
let selectionLabel;
if ( isSelected ) {
selectionLabel = labels?.getDeselectLabel
? labels?.getDeselectLabel( item )
: __( 'Deselect item' );
} else {
selectionLabel = labels?.getSelectLabel
? labels?.getSelectLabel( item )
: __( 'Select a new item' );
}
return (
<CheckboxControl
__nextHasNoMarginBottom
checked={ isSelected }
label={ selectionLabel }
onChange={ () => {
if ( ! isSelected ) {
const newSelection = [
...selection,
item.id,
];
setSelection( newSelection );
} else {
setSelection(
selection.filter(
( id ) => id !== item.id
)
);
}
} }
/>
);
},
enableHiding: false,
width: 40,
className: 'dataviews-table-view__selection-column',
} );
}
if ( actions?.length ) {
_columns.push( {
header: __( 'Actions' ),
Expand All @@ -368,7 +437,15 @@ function ViewTable( {
}

return _columns;
}, [ fields, actions, view ] );
}, [
areAllSelected,
fields,
actions,
view,
selection,
setSelection,
data,
] );

const columnVisibility = useMemo( () => {
if ( ! view.hiddenFields?.length ) {
Expand Down Expand Up @@ -566,6 +643,10 @@ function ViewTable( {
header.column.columnDef
.maxWidth || undefined,
} }
className={
header.column.columnDef.className ||
undefined
}
data-field-id={ header.id }
>
<HeaderMenu
Expand Down Expand Up @@ -594,6 +675,10 @@ function ViewTable( {
cell.column.columnDef
.maxWidth || undefined,
} }
className={
cell.column.columnDef.className ||
undefined
}
>
{ flexRender(
cell.column.columnDef.cell,
Expand Down
Loading
Loading