diff --git a/packages/edit-site/src/components/bulk-actions/index.js b/packages/edit-site/src/components/bulk-actions/index.js
new file mode 100644
index 00000000000000..390f9600a0810a
--- /dev/null
+++ b/packages/edit-site/src/components/bulk-actions/index.js
@@ -0,0 +1,66 @@
+/**
+ * WordPress dependencies
+ */
+import { trash } from '@wordpress/icons';
+import { useMemo } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { store as noticesStore } from '@wordpress/notices';
+import { useDispatch } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
+
+export function useBulkTrashPostAction() {
+ const { createSuccessNotice, createErrorNotice } =
+ useDispatch( noticesStore );
+ const { deleteEntityRecord } = useDispatch( coreStore );
+ return useMemo(
+ () => ( {
+ id: 'move-to-trash',
+ label: __( 'Move to Trash' ),
+ isPrimary: true,
+ icon: trash,
+ isEligible( data, selection ) {
+ console.log( {
+ p: data.filter( ( post ) => selection.includes( post.id ) ),
+ } );
+ return ! data
+ .filter( ( post ) => selection.includes( post.id ) )
+ .some( ( post ) => post.status === 'trash' );
+ },
+ async callback( data, selection ) {
+ const postsToDelete = data.filter( ( post ) =>
+ selection.includes( post.id )
+ );
+ try {
+ await Promise.all(
+ postsToDelete.map( async ( post ) => {
+ deleteEntityRecord(
+ 'postType',
+ post.type,
+ post.id,
+ {},
+ { throwOnError: true }
+ );
+ } )
+ );
+ createSuccessNotice(
+ __( 'The selected posts were moved to the trash.' ),
+ {
+ type: 'snackbar',
+ id: 'edit-site-page-trashed',
+ }
+ );
+ } catch ( error ) {
+ const errorMessage =
+ error.message && error.code !== 'unknown_error'
+ ? error.message
+ : __(
+ 'An error occurred while moving the posts to the trash.'
+ );
+
+ createErrorNotice( errorMessage, { type: 'snackbar' } );
+ }
+ },
+ } ),
+ [ createErrorNotice, createSuccessNotice, deleteEntityRecord ]
+ );
+}
diff --git a/packages/edit-site/src/components/dataviews/bulk-actions.js b/packages/edit-site/src/components/dataviews/bulk-actions.js
new file mode 100644
index 00000000000000..cd55aa94c9b2a7
--- /dev/null
+++ b/packages/edit-site/src/components/dataviews/bulk-actions.js
@@ -0,0 +1,94 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ ToolbarButton,
+ Toolbar,
+ ToolbarGroup,
+ ToolbarItem,
+ Popover,
+} from '@wordpress/components';
+import { useMemo } from '@wordpress/element';
+import { __, _n, sprintf } from '@wordpress/i18n';
+
+function PrimaryActionTrigger( { action, onClick } ) {
+ return (
+
+ );
+}
+
+const EMPTY_ARRAY = [];
+
+export default function BulkActions( {
+ data,
+ selection,
+ bulkActions = EMPTY_ARRAY,
+ setSelection,
+} ) {
+ const primaryActions = useMemo(
+ () =>
+ bulkActions.filter( ( action ) => {
+ return action.isPrimary && action.isEligible( data, selection );
+ } ),
+ [ bulkActions, data, selection ]
+ );
+ if (
+ ( selection && selection.length === 0 ) ||
+ primaryActions.length === 0
+ ) {
+ return null;
+ }
+ return (
+
+
+
+
+ {} } 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
+ )
+ }
+
+ {
+ setSelection( EMPTY_ARRAY );
+ } }
+ >
+ { __( 'Deselect' ) }
+
+
+
+ { primaryActions.map( ( action ) => {
+ return (
+
+ action.callback( data, selection )
+ }
+ />
+ );
+ } ) }
+
+
+
+
+ );
+}
diff --git a/packages/edit-site/src/components/dataviews/dataviews.js b/packages/edit-site/src/components/dataviews/dataviews.js
index 682060203de7c3..b9fbff2b438e0d 100644
--- a/packages/edit-site/src/components/dataviews/dataviews.js
+++ b/packages/edit-site/src/components/dataviews/dataviews.js
@@ -4,6 +4,7 @@
import {
__experimentalVStack as VStack,
__experimentalHStack as HStack,
+ Popover,
} from '@wordpress/components';
import { useMemo } from '@wordpress/element';
@@ -17,6 +18,7 @@ import Filters from './filters';
import Search from './search';
import { ViewGrid } from './view-grid';
import { ViewSideBySide } from './view-side-by-side';
+import BulkActions from './bulk-actions';
// To do: convert to view type registry.
export const viewTypeSupportsMap = {
@@ -45,6 +47,9 @@ export default function DataViews( {
isLoading = false,
paginationInfo,
supportedLayouts,
+ selection,
+ setSelection,
+ bulkActions,
} ) {
const ViewComponent = viewTypeMap[ view.type ];
const _fields = useMemo( () => {
@@ -89,12 +94,23 @@ export default function DataViews( {
data={ data }
getItemId={ getItemId }
isLoading={ isLoading }
+ selection={ selection }
+ setSelection={ setSelection }
/>
-
+
+
);
diff --git a/packages/edit-site/src/components/dataviews/style.scss b/packages/edit-site/src/components/dataviews/style.scss
index 76e6fca78de67d..4aba17a640f417 100644
--- a/packages/edit-site/src/components/dataviews/style.scss
+++ b/packages/edit-site/src/components/dataviews/style.scss
@@ -53,3 +53,14 @@
.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%;
+}
+
diff --git a/packages/edit-site/src/components/dataviews/view-list.js b/packages/edit-site/src/components/dataviews/view-list.js
index 0939297e374dfa..e1e03b39c01d58 100644
--- a/packages/edit-site/src/components/dataviews/view-list.js
+++ b/packages/edit-site/src/components/dataviews/view-list.js
@@ -28,6 +28,7 @@ import {
Button,
Icon,
privateApis as componentsPrivateApis,
+ CheckboxControl,
} from '@wordpress/components';
import { useMemo, Children, Fragment } from '@wordpress/element';
@@ -252,9 +253,12 @@ function ViewList( {
getItemId,
isLoading = false,
paginationInfo,
+ selection,
+ setSelection,
} ) {
+ const areAllSelected = selection && selection.length === data.length;
const columns = useMemo( () => {
- const _columns = fields.map( ( field ) => {
+ const fieldsColumns = fields.map( ( field ) => {
const { render, getValue, ...column } = field;
column.cell = ( props ) =>
render( { item: props.row.original, view } );
@@ -263,6 +267,61 @@ function ViewList( {
}
return column;
} );
+ const _columns =
+ selection !== undefined
+ ? [
+ {
+ header: (
+ {
+ if ( areAllSelected ) {
+ setSelection( [] );
+ } else {
+ setSelection(
+ data.map( ( { id } ) => id )
+ );
+ }
+ } }
+ />
+ ),
+ id: 'selection',
+ cell: ( props ) => {
+ //console.log({ props });
+ const item = props.row.original;
+ const isSelected = selection.includes(
+ item.id
+ );
+ //console.log({ item, isSelected });
+ return (
+ {
+ if ( ! isSelected ) {
+ const newSelection = [
+ ...selection,
+ item.id,
+ ];
+ setSelection( newSelection );
+ } else {
+ setSelection(
+ selection.filter(
+ ( id ) => id !== item.id
+ )
+ );
+ }
+ } }
+ />
+ );
+ },
+ enableHiding: false,
+ width: 40,
+ },
+ ...fieldsColumns,
+ ]
+ : fieldsColumns;
if ( actions?.length ) {
_columns.push( {
header: __( 'Actions' ),
@@ -280,7 +339,15 @@ function ViewList( {
}
return _columns;
- }, [ fields, actions, view ] );
+ }, [
+ areAllSelected,
+ fields,
+ actions,
+ view,
+ selection,
+ setSelection,
+ data,
+ ] );
const columnVisibility = useMemo( () => {
if ( ! view.hiddenFields?.length ) {
diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js
index b933f1d82500e4..66d063a6cf4043 100644
--- a/packages/edit-site/src/components/page-pages/index.js
+++ b/packages/edit-site/src/components/page-pages/index.js
@@ -28,6 +28,7 @@ import {
viewPostAction,
useEditPostAction,
} from '../actions';
+import { useBulkTrashPostAction } from '../bulk-actions';
import SideEditor from './side-editor';
import Media from '../media';
import { unlock } from '../../lock-unlock';
@@ -157,6 +158,20 @@ export default function PagePages() {
totalPages,
} = useEntityRecords( 'postType', postType, queryArgs );
+ useEffect( () => {
+ if (
+ selection.some(
+ ( id ) => ! pages?.some( ( page ) => page.id === id )
+ )
+ ) {
+ setSelection(
+ selection.filter( ( id ) =>
+ pages?.some( ( page ) => page.id === id )
+ )
+ );
+ }
+ }, [ pages, selection ] );
+
const { records: authors, isResolving: isLoadingAuthors } =
useEntityRecords( 'root', 'user' );
@@ -271,6 +286,9 @@ export default function PagePages() {
const permanentlyDeletePostAction = usePermanentlyDeletePostAction();
const restorePostAction = useRestorePostAction();
const editPostAction = useEditPostAction();
+
+ const bulkTrashPostAction = useBulkTrashPostAction();
+
const actions = useMemo(
() => [
viewPostAction,
@@ -287,6 +305,11 @@ export default function PagePages() {
editPostAction,
]
);
+
+ const bulkActions = useMemo(
+ () => [ bulkTrashPostAction ],
+ [ bulkTrashPostAction ]
+ );
const onChangeView = useCallback(
( viewUpdater ) => {
let updatedView =
@@ -315,11 +338,14 @@ export default function PagePages() {
paginationInfo={ paginationInfo }
fields={ fields }
actions={ actions }
+ bulkActions={ bulkActions }
data={ pages || EMPTY_ARRAY }
getItemId={ ( item ) => item.id }
isLoading={ isLoadingPages || isLoadingAuthors }
view={ view }
onChangeView={ onChangeView }
+ selection={ selection }
+ setSelection={ setSelection }
/>
{ viewTypeSupportsMap[ view.type ].preview && (