/**
* 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 ) }
/>
) }
>
);
}