/** * External dependencies */ import type { MouseEventHandler } from 'react'; /** * WordPress dependencies */ import { Button, Modal, privateApis as componentsPrivateApis, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMemo, useState } from '@wordpress/element'; import { moreVertical } from '@wordpress/icons'; import { useRegistry } from '@wordpress/data'; import { useViewportMatch } from '@wordpress/compose'; import { Stack } from '@wordpress/ui'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; import type { Action, ActionModal as ActionModalType } from '../../types'; const { Menu, kebabCase } = unlock( componentsPrivateApis ); export interface ActionTriggerProps< Item > { action: Action< Item >; onClick: MouseEventHandler; isBusy?: boolean; items: Item[]; variant?: 'primary' | 'secondary' | 'tertiary' | 'link'; } export interface ActionModalProps< Item > { action: ActionModalType< Item >; items: Item[]; closeModal: () => void; } interface ActionsMenuGroupProps< Item > { actions: Action< Item >[]; item: Item; registry: ReturnType< typeof useRegistry >; setActiveModalAction: ( action: ActionModalType< Item > | null ) => void; } interface ItemActionsProps< Item > { item: Item; actions: Action< Item >[]; isCompact?: boolean; } interface CompactItemActionsProps< Item > { item: Item; actions: Action< Item >[]; isSmall?: boolean; registry: ReturnType< typeof useRegistry >; } interface PrimaryActionsProps< Item > { item: Item; actions: Action< Item >[]; registry: ReturnType< typeof useRegistry >; buttonVariant?: 'primary' | 'secondary' | 'tertiary' | 'link'; } function ButtonTrigger< Item >( { action, onClick, items, variant, }: ActionTriggerProps< Item > ) { const label = typeof action.label === 'string' ? action.label : action.label( items ); return ( ); } function MenuItemTrigger< Item >( { action, onClick, items, }: ActionTriggerProps< Item > ) { const label = typeof action.label === 'string' ? action.label : action.label( items ); return ( { label } ); } export function ActionModal< Item >( { action, items, closeModal, }: ActionModalProps< Item > ) { const label = typeof action.label === 'string' ? action.label : action.label( items ); const modalHeader = typeof action.modalHeader === 'function' ? action.modalHeader( items ) : action.modalHeader; return ( ); } export function ActionsMenuGroup< Item >( { actions, item, registry, setActiveModalAction, }: ActionsMenuGroupProps< Item > ) { const { primaryActions, regularActions } = useMemo( () => { return actions.reduce( ( acc, action ) => { ( action.isPrimary ? acc.primaryActions : acc.regularActions ).push( action ); return acc; }, { primaryActions: [] as Action< Item >[], regularActions: [] as Action< Item >[], } ); }, [ actions ] ); const renderActionGroup = ( actionList: Action< Item >[] ) => actionList.map( ( action ) => ( { if ( 'RenderModal' in action ) { setActiveModalAction( action ); return; } action.callback( [ item ], { registry } ); } } items={ [ item ] } /> ) ); return ( { renderActionGroup( primaryActions ) } { renderActionGroup( regularActions ) } ); } export default function ItemActions< Item >( { item, actions, isCompact, }: ItemActionsProps< Item > ) { const registry = useRegistry(); const { primaryActions, eligibleActions } = useMemo( () => { // If an action is eligible for all items, doesn't need // to provide the `isEligible` function. const _eligibleActions = actions.filter( ( action ) => ! action.isEligible || action.isEligible( item ) ); const _primaryActions = _eligibleActions.filter( ( action ) => action.isPrimary ); return { primaryActions: _primaryActions, eligibleActions: _eligibleActions, }; }, [ actions, item ] ); const isMobileViewport = useViewportMatch( 'medium', '<' ); if ( isCompact ) { return ( ); } return ( { ( primaryActions.length < eligibleActions.length || // Since we hide primary actions on mobile, we need to show the menu // there if there are any actions at all. isMobileViewport ) && ( ) } ); } function CompactItemActions< Item >( { item, actions, isSmall, registry, }: CompactItemActionsProps< Item > ) { const [ activeModalAction, setActiveModalAction ] = useState( null as ActionModalType< Item > | null ); return ( <> } /> { !! activeModalAction && ( setActiveModalAction( null ) } /> ) } ); } export function PrimaryActions< Item >( { item, actions, registry, buttonVariant, }: PrimaryActionsProps< Item > ) { const [ activeModalAction, setActiveModalAction ] = useState( null as any ); const isMobileViewport = useViewportMatch( 'medium', '<' ); if ( isMobileViewport ) { return null; } if ( ! Array.isArray( actions ) || actions.length === 0 ) { return null; } return ( <> { actions.map( ( action ) => ( { if ( 'RenderModal' in action ) { setActiveModalAction( action ); return; } action.callback( [ item ], { registry } ); } } items={ [ item ] } variant={ buttonVariant } /> ) ) } { !! activeModalAction && ( setActiveModalAction( null ) } /> ) } ); }