diff --git a/packages/dataviews/src/bulk-actions-toolbar.js b/packages/dataviews/src/bulk-actions-toolbar.js new file mode 100644 index 00000000000000..cecb3cae7342f6 --- /dev/null +++ b/packages/dataviews/src/bulk-actions-toolbar.js @@ -0,0 +1,157 @@ +/** + * WordPress dependencies + */ +import { + ToolbarButton, + Toolbar, + ToolbarGroup, + __unstableMotion as motion, + __unstableAnimatePresence as AnimatePresence, +} from '@wordpress/components'; +import { useMemo } from '@wordpress/element'; +import { _n, sprintf, __ } from '@wordpress/i18n'; +import { closeSmall } from '@wordpress/icons'; +import { useReducedMotion } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { ActionWithModal } from './item-actions'; + +const SNACKBAR_VARIANTS = { + init: { + bottom: -48, + }, + open: { + bottom: 24, + transition: { + bottom: { type: 'tween', duration: 0.2, ease: [ 0, 0, 0.2, 1 ] }, + }, + }, + exit: { + opacity: 0, + bottom: 24, + transition: { + opacity: { type: 'tween', duration: 0.2, ease: [ 0, 0, 0.2, 1 ] }, + }, + }, +}; + +function PrimaryActionTrigger( { action, onClick, items } ) { + const isDisabled = useMemo( () => { + return items.some( ( item ) => ! action.isEligible( item ) ); + }, [ action, items ] ); + return ( + + ); +} + +const EMPTY_ARRAY = []; + +export default function BulkActionsToolbar( { + data, + selection, + actions = EMPTY_ARRAY, + setSelection, + getItemId, +} ) { + const isReducedMotion = useReducedMotion(); + const selectedItems = useMemo( () => { + return data.filter( ( item ) => + selection.includes( getItemId( item ) ) + ); + }, [ selection, data, getItemId ] ); + + const primaryActionsToShow = useMemo( + () => + actions.filter( ( action ) => { + return ( + action.supportsBulk && + action.isPrimary && + selectedItems.some( ( item ) => action.isEligible( item ) ) + ); + } ), + [ actions, selectedItems ] + ); + + if ( + ( selection && selection.length === 0 ) || + primaryActionsToShow.length === 0 + ) { + return null; + } + + return ( + + + +
+ +
+ { + // translators: %s: Total number of selected items. + sprintf( + // translators: %s: Total number of selected items. + _n( + '%s selected', + selection.length + ), + selection.length + ) + } +
+ { primaryActionsToShow.map( ( action ) => { + if ( !! action.RenderModal ) { + return ( + + ); + } + return ( + + action.callback( selectedItems ) + } + /> + ); + } ) } +
+ + { + setSelection( EMPTY_ARRAY ); + } } + /> + +
+
+
+
+ ); +} diff --git a/packages/dataviews/src/dataviews.js b/packages/dataviews/src/dataviews.js index d67115deb3d6b2..f98e2a6352f064 100644 --- a/packages/dataviews/src/dataviews.js +++ b/packages/dataviews/src/dataviews.js @@ -14,6 +14,7 @@ import Search from './search'; import { VIEW_LAYOUTS, LAYOUT_TABLE, LAYOUT_GRID } from './constants'; import BulkActions from './bulk-actions'; import { normalizeFields } from './normalize-fields'; +import BulkActionsToolbar from './bulk-actions-toolbar'; const defaultGetItemId = ( item ) => item.id; const defaultOnSelectionChange = () => {}; @@ -142,6 +143,16 @@ export default function DataViews( { onChangeView={ onChangeView } paginationInfo={ paginationInfo } /> + { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) && + hasPossibleBulkAction && ( + + ) } ); } diff --git a/packages/dataviews/src/item-actions.js b/packages/dataviews/src/item-actions.js index db4da0d4924896..823d264730d5e5 100644 --- a/packages/dataviews/src/item-actions.js +++ b/packages/dataviews/src/item-actions.js @@ -47,11 +47,12 @@ function DropdownMenuItemTrigger( { action, onClick } ) { ); } -function ActionWithModal( { action, item, ActionTrigger } ) { +export function ActionWithModal( { action, items, ActionTrigger } ) { const [ isModalOpen, setIsModalOpen ] = useState( false ); const actionTriggerProps = { action, onClick: () => setIsModalOpen( true ), + items, }; const { RenderModal, hideModalHeader } = action; return ( @@ -69,7 +70,7 @@ function ActionWithModal( { action, item, ActionTrigger } ) { ) }` } > setIsModalOpen( false ) } /> @@ -87,7 +88,7 @@ function ActionsDropdownMenuGroup( { actions, item } ) { ); @@ -139,7 +140,7 @@ export default function ItemActions( { item, actions, isCompact } ) { ); diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index c548565ef848df..db8e792f9e577a 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -785,3 +785,40 @@ } } } + + +.dataviews-bulk-actions-toolbar-wrapper { + display: flex; + flex-grow: 1; + width: 100%; +} + +.dataviews-bulk-actions { + position: absolute; + display: flex; + flex-direction: column; + align-content: center; + flex-wrap: wrap; + width: 100%; + bottom: $grid-unit-30; + + .components-accessible-toolbar { + border-color: $gray-300; + box-shadow: $shadow-popover; + + .components-toolbar-group { + border-color: $gray-200; + + &:last-child { + border: 0; + } + } + } + + .dataviews-bulk-actions__selection-count { + display: flex; + align-items: center; + margin: 0 $grid-unit-10 0 $grid-unit-15; + color: $gray-700; + } +}