Skip to content

Commit

Permalink
DataViews: Refactor actions to render modal outside of the menu (Word…
Browse files Browse the repository at this point in the history
…Press#67664)

Co-authored-by: ntsekouras <[email protected]>
Co-authored-by: oandregal <[email protected]>
Co-authored-by: ciampo <[email protected]>
Co-authored-by: doekenorg <[email protected]>
  • Loading branch information
5 people authored and yogeshbhutkar committed Dec 18, 2024
1 parent 58ca304 commit e7dc999
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 185 deletions.
46 changes: 43 additions & 3 deletions packages/dataviews/src/components/dataviews-bulk-actions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import type { ReactElement } from 'react';

/**
* WordPress dependencies
*/
Expand All @@ -15,11 +20,46 @@ import { closeSmall } from '@wordpress/icons';
* Internal dependencies
*/
import DataViewsContext from '../dataviews-context';
import { ActionWithModal } from '../dataviews-item-actions';
import type { Action } from '../../types';
import { ActionModal } from '../dataviews-item-actions';
import type { Action, ActionModal as ActionModalType } from '../../types';
import type { SetSelection } from '../../private-types';
import type { ActionTriggerProps } from '../dataviews-item-actions';

interface ActionWithModalProps< Item > {
action: ActionModalType< Item >;
items: Item[];
ActionTriggerComponent: (
props: ActionTriggerProps< Item >
) => ReactElement;
}

function ActionWithModal< Item >( {
action,
items,
ActionTriggerComponent,
}: ActionWithModalProps< Item > ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const actionTriggerProps = {
action,
onClick: () => {
setIsModalOpen( true );
},
items,
};
return (
<>
<ActionTriggerComponent { ...actionTriggerProps } />
{ isModalOpen && (
<ActionModal
action={ action }
items={ items }
closeModal={ () => setIsModalOpen( false ) }
/>
) }
</>
);
}

export function useHasAPossibleBulkAction< Item >(
actions: Action< Item >[],
item: Item
Expand Down Expand Up @@ -160,7 +200,7 @@ function ActionButton< Item >( {
key={ action.id }
action={ action }
items={ selectedEligibleItems }
ActionTrigger={ ActionTrigger }
ActionTriggerComponent={ ActionTrigger }
/>
);
}
Expand Down
186 changes: 83 additions & 103 deletions packages/dataviews/src/components/dataviews-item-actions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import type { MouseEventHandler, ReactElement } from 'react';
import type { MouseEventHandler } from 'react';

/**
* WordPress dependencies
Expand Down Expand Up @@ -32,20 +32,17 @@ export interface ActionTriggerProps< Item > {
items: Item[];
}

interface ActionModalProps< Item > {
export interface ActionModalProps< Item > {
action: ActionModalType< Item >;
items: Item[];
closeModal?: () => void;
}

interface ActionWithModalProps< Item > extends ActionModalProps< Item > {
ActionTrigger: ( props: ActionTriggerProps< Item > ) => ReactElement;
isBusy?: boolean;
closeModal: () => void;
}

interface ActionsMenuGroupProps< Item > {
actions: Action< Item >[];
item: Item;
registry: ReturnType< typeof useRegistry >;
setActiveModalAction: ( action: ActionModalType< Item > | null ) => void;
}

interface ItemActionsProps< Item > {
Expand All @@ -58,19 +55,14 @@ 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 >;
}
interface ActionsListProps< Item > {
item: Item;
actions: Action< Item >[];
registry: ReturnType< typeof useRegistry >;
ActionTrigger: ( props: ActionTriggerProps< Item > ) => ReactElement;
}

function ButtonTrigger< Item >( {
action,
Expand Down Expand Up @@ -98,10 +90,7 @@ function MenuItemTrigger< Item >( {
const label =
typeof action.label === 'string' ? action.label : action.label( items );
return (
<Menu.Item
onClick={ onClick }
hideOnClick={ ! ( 'RenderModal' in action ) }
>
<Menu.Item onClick={ onClick }>
<Menu.ItemLabel>{ label }</Menu.ItemLabel>
</Menu.Item>
);
Expand All @@ -118,7 +107,7 @@ export function ActionModal< Item >( {
<Modal
title={ action.modalHeader || label }
__experimentalHideHeader={ !! action.hideModalHeader }
onRequestClose={ closeModal ?? ( () => {} ) }
onRequestClose={ closeModal }
focusOnMount="firstContentElement"
size="medium"
overlayClassName={ `dataviews-action-modal dataviews-action-modal__${ kebabCase(
Expand All @@ -130,48 +119,28 @@ export function ActionModal< Item >( {
);
}

export function ActionWithModal< Item >( {
action,
items,
ActionTrigger,
isBusy,
}: ActionWithModalProps< Item > ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const actionTriggerProps = {
action,
onClick: () => {
setIsModalOpen( true );
},
items,
isBusy,
};
return (
<>
<ActionTrigger { ...actionTriggerProps } />
{ isModalOpen && (
<ActionModal
action={ action }
items={ items }
closeModal={ () => setIsModalOpen( false ) }
/>
) }
</>
);
}

export function ActionsMenuGroup< Item >( {
actions,
item,
registry,
setActiveModalAction,
}: ActionsMenuGroupProps< Item > ) {
const registry = useRegistry();
return (
<Menu.Group>
<ActionsList
actions={ actions }
item={ item }
registry={ registry }
ActionTrigger={ MenuItemTrigger }
/>
{ actions.map( ( action ) => (
<MenuItemTrigger
key={ action.id }
action={ action }
onClick={ () => {
if ( 'RenderModal' in action ) {
setActiveModalAction( action );
return;
}
action.callback( [ item ], { registry } );
} }
items={ [ item ] }
/>
) ) }
</Menu.Group>
);
}
Expand Down Expand Up @@ -210,6 +179,7 @@ export default function ItemActions< Item >( {
item={ item }
actions={ eligibleActions }
isSmall
registry={ registry }
/>
);
}
Expand Down Expand Up @@ -239,7 +209,11 @@ export default function ItemActions< Item >( {
actions={ primaryActions }
registry={ registry }
/>
<CompactItemActions item={ item } actions={ eligibleActions } />
<CompactItemActions
item={ item }
actions={ eligibleActions }
registry={ registry }
/>
</HStack>
);
}
Expand All @@ -248,23 +222,41 @@ function CompactItemActions< Item >( {
item,
actions,
isSmall,
registry,
}: CompactItemActionsProps< Item > ) {
const [ activeModalAction, setActiveModalAction ] = useState(
null as ActionModalType< Item > | null
);
return (
<Menu
trigger={
<Button
size={ isSmall ? 'small' : 'compact' }
icon={ moreVertical }
label={ __( 'Actions' ) }
accessibleWhenDisabled
disabled={ ! actions.length }
className="dataviews-all-actions-button"
<>
<Menu
trigger={
<Button
size={ isSmall ? 'small' : 'compact' }
icon={ moreVertical }
label={ __( 'Actions' ) }
accessibleWhenDisabled
disabled={ ! actions.length }
className="dataviews-all-actions-button"
/>
}
placement="bottom-end"
>
<ActionsMenuGroup
actions={ actions }
item={ item }
registry={ registry }
setActiveModalAction={ setActiveModalAction }
/>
}
placement="bottom-end"
>
<ActionsMenuGroup actions={ actions } item={ item } />
</Menu>
</Menu>
{ !! activeModalAction && (
<ActionModal
action={ activeModalAction }
items={ [ item ] }
closeModal={ () => setActiveModalAction( null ) }
/>
) }
</>
);
}

Expand All @@ -273,45 +265,33 @@ function PrimaryActions< Item >( {
actions,
registry,
}: PrimaryActionsProps< Item > ) {
const [ activeModalAction, setActiveModalAction ] = useState( null as any );
if ( ! Array.isArray( actions ) || actions.length === 0 ) {
return null;
}
return (
<ActionsList
actions={ actions }
item={ item }
registry={ registry }
ActionTrigger={ ButtonTrigger }
/>
);
}

function ActionsList< Item >( {
item,
actions,
registry,
ActionTrigger,
}: ActionsListProps< Item > ) {
return actions.map( ( action ) => {
if ( 'RenderModal' in action ) {
return (
<ActionWithModal
<>
{ actions.map( ( action ) => (
<ButtonTrigger
key={ action.id }
action={ action }
onClick={ () => {
if ( 'RenderModal' in action ) {
setActiveModalAction( action );
return;
}
action.callback( [ item ], { registry } );
} }
items={ [ item ] }
ActionTrigger={ ActionTrigger }
/>
);
}
return (
<ActionTrigger
key={ action.id }
action={ action }
onClick={ () => {
action.callback( [ item ], { registry } );
} }
items={ [ item ] }
/>
);
} );
) ) }
{ !! activeModalAction && (
<ActionModal
action={ activeModalAction }
items={ [ item ] }
closeModal={ () => setActiveModalAction( null ) }
/>
) }
</>
);
}
14 changes: 14 additions & 0 deletions packages/dataviews/src/dataviews-layouts/list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import type {
NormalizedField,
ViewList as ViewListType,
ViewListProps,
ActionModal as ActionModalType,
} from '../../types';

interface ListViewItemProps< Item > {
Expand Down Expand Up @@ -154,7 +155,11 @@ function ListItem< Item >( {
const labelId = `${ idPrefix }-label`;
const descriptionId = `${ idPrefix }-description`;

const registry = useRegistry();
const [ isHovered, setIsHovered ] = useState( false );
const [ activeModalAction, setActiveModalAction ] = useState(
null as ActionModalType< Item > | null
);
const handleHover: React.MouseEventHandler = ( { type } ) => {
const isHover = type === 'mouseenter';
setIsHovered( isHover );
Expand Down Expand Up @@ -233,8 +238,17 @@ function ListItem< Item >( {
<ActionsMenuGroup
actions={ eligibleActions }
item={ item }
registry={ registry }
setActiveModalAction={ setActiveModalAction }
/>
</Menu>
{ !! activeModalAction && (
<ActionModal
action={ activeModalAction }
items={ [ item ] }
closeModal={ () => setActiveModalAction( null ) }
/>
) }
</div>
) }
</HStack>
Expand Down
Loading

0 comments on commit e7dc999

Please sign in to comment.