refactor(command-menu-item): rename Actions to CommandMenuItem (#18489)

actions are being renamed to command menu item, they will be migrated to
server and will be served as headless front components

---------

Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
This commit is contained in:
nitin
2026-03-09 19:33:12 +05:30
committed by GitHub
parent 06bdb5ad6a
commit 36bcc71f3d
275 changed files with 4544 additions and 4436 deletions
@@ -0,0 +1,32 @@
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
import { useContext } from 'react';
export const Command = ({
onClick,
closeSidePanelOnShowPageOptionsExecution = false,
closeSidePanelOnCommandMenuListExecution = true,
}: {
onClick: () => void;
closeSidePanelOnShowPageOptionsExecution?: boolean;
closeSidePanelOnCommandMenuListExecution?: boolean;
}) => {
const commandMenuItemConfig = useContext(CommandConfigContext);
const { closeCommandMenu } = useCloseCommandMenu({
closeSidePanelOnShowPageOptionsExecution,
closeSidePanelOnCommandMenuListExecution,
});
if (!commandMenuItemConfig) {
return null;
}
const handleClick = () => {
closeCommandMenu();
onClick();
};
return <CommandMenuItemDisplay onClick={handleClick} />;
};
@@ -0,0 +1,51 @@
import { type CommandMenuItemDisplayProps } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-list/states/isSelectedItemIdComponentFamilyState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useNavigate } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui/navigation';
export const CommandDropdownItem = ({
action,
onClick,
to,
}: {
action: CommandMenuItemDisplayProps;
onClick?: () => void;
to?: string;
}) => {
const navigate = useNavigate();
const handleClick = () => {
onClick?.();
if (isDefined(to)) {
navigate(to);
}
};
const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow(
SelectableListComponentInstanceContext,
);
const isSelectedItemId = useAtomComponentFamilyStateValue(
isSelectedItemIdComponentFamilyState,
action.key,
selectableListInstanceId,
);
return (
<SelectableListItem itemId={action.key} onEnter={handleClick}>
<MenuItem
focused={isSelectedItemId}
key={action.key}
LeftIcon={action.Icon}
onClick={handleClick}
text={getCommandMenuItemLabel(action.label)}
/>
</SelectableListItem>
);
};
@@ -0,0 +1,21 @@
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
import { type PathParam } from 'react-router-dom';
import { type AppPath } from 'twenty-shared/types';
import { getAppPath } from 'twenty-shared/utils';
export const CommandLink = <T extends AppPath>({
to,
params,
queryParams,
}: {
to: T;
params?: { [key in PathParam<T>]: string | null };
queryParams?: Record<string, any>;
}) => {
const { closeCommandMenu } = useCloseCommandMenu();
const path = getAppPath(to, params, queryParams);
return <CommandMenuItemDisplay onClick={closeCommandMenu} to={path} />;
};
@@ -0,0 +1,48 @@
import { type CommandMenuItemDisplayProps } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useNavigate } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { Loader } from 'twenty-ui/feedback';
export const CommandListItem = ({
action,
onClick,
to,
disabled = false,
}: {
action: CommandMenuItemDisplayProps;
onClick?: () => void;
to?: string;
disabled?: boolean;
}) => {
const navigate = useNavigate();
const handleClick = () => {
if (disabled) {
return;
}
onClick?.();
if (isDefined(to)) {
navigate(to);
}
};
return (
<SelectableListItem itemId={action.key} onEnter={handleClick}>
<CommandMenuItem
id={action.key}
Icon={action.Icon}
label={getCommandMenuItemLabel(action.label)}
description={getCommandMenuItemLabel(action.description ?? '')}
to={to}
onClick={disabled ? undefined : onClick}
hotKeys={action.hotKeys}
disabled={disabled}
RightComponent={disabled ? <Loader /> : undefined}
/>
</SelectableListItem>
);
};
@@ -0,0 +1,14 @@
import { CommandMenuButton } from '@/command-menu/components/CommandMenuButton';
import { type CommandMenuItemDisplayProps } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
export const CommandMenuItemButton = ({
action,
onClick,
to,
}: {
action: CommandMenuItemDisplayProps;
onClick?: (event?: React.MouseEvent<HTMLElement>) => void;
to?: string;
}) => {
return <CommandMenuButton command={action} to={to} onClick={onClick} />;
};
@@ -0,0 +1,14 @@
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
export const CommandMenuItemComponent = ({
action,
}: {
action: CommandMenuItemConfig;
}) => {
return (
<CommandConfigContext.Provider value={action}>
{action.component}
</CommandConfigContext.Provider>
);
};
@@ -0,0 +1,59 @@
import { CommandMenuItemButton } from '@/command-menu-item/display/components/CommandMenuItemButton';
import { CommandDropdownItem } from '@/command-menu-item/display/components/CommandDropdownItem';
import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem';
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { type MessageDescriptor } from '@lingui/core';
import { useContext } from 'react';
import { assertUnreachable } from 'twenty-shared/utils';
import { type IconComponent } from 'twenty-ui/display';
import { type MenuItemAccent } from 'twenty-ui/navigation';
export type CommandMenuItemDisplayProps = {
key: string;
label: MessageDescriptor | string;
shortLabel?: MessageDescriptor | string;
description?: MessageDescriptor | string;
Icon: IconComponent;
isPrimaryCTA?: boolean;
accent?: MenuItemAccent;
hotKeys?: string[];
};
export const CommandMenuItemDisplay = ({
onClick,
to,
disabled,
}: {
onClick?: (event?: React.MouseEvent<HTMLElement>) => void;
to?: string;
disabled?: boolean;
}) => {
const action = useContext(CommandConfigContext);
const { displayType } = useContext(CommandMenuContext);
if (!action) {
return null;
}
if (displayType === 'button') {
return <CommandMenuItemButton action={action} onClick={onClick} to={to} />;
}
if (displayType === 'listItem') {
return (
<CommandListItem
action={action}
onClick={onClick}
to={to}
disabled={disabled}
/>
);
}
if (displayType === 'dropdownItem') {
return <CommandDropdownItem action={action} onClick={onClick} to={to} />;
}
return assertUnreachable(displayType, 'Unsupported display type');
};
@@ -0,0 +1,50 @@
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { type MessageDescriptor } from '@lingui/core';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { type SidePanelPages } from 'twenty-shared/types';
import { type IconComponent } from 'twenty-ui/display';
export const CommandMenuItemOpenSidePanelPage = ({
page,
pageTitle,
pageIcon,
onClick,
shouldResetSearchState = false,
}: {
page: SidePanelPages;
pageTitle: MessageDescriptor;
pageIcon: IconComponent;
onClick?: () => void;
shouldResetSearchState?: boolean;
}) => {
const actionConfig = useContext(CommandConfigContext);
const { navigateSidePanel } = useNavigateSidePanel();
const setSidePanelSearch = useSetAtomState(sidePanelSearchState);
if (!actionConfig) {
return null;
}
const handleClick = () => {
onClick?.();
navigateSidePanel({
page,
pageTitle: t(pageTitle),
pageIcon,
});
if (shouldResetSearchState) {
setSidePanelSearch('');
}
};
return <CommandMenuItemDisplay onClick={handleClick} />;
};
@@ -0,0 +1,79 @@
import { type ReactNode, useContext } from 'react';
import { t } from '@lingui/core/macro';
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { type ButtonAccent } from 'twenty-ui/input';
export type CommandModalProps = {
title: string;
subtitle: ReactNode;
onConfirmClick: () => void | Promise<void>;
confirmButtonText?: string;
confirmButtonAccent?: ButtonAccent;
isLoading?: boolean;
closeSidePanelOnShowPageOptionsExecution?: boolean;
closeSidePanelOnCommandMenuListExecution?: boolean;
};
export const CommandModal = ({
title,
subtitle,
onConfirmClick,
confirmButtonText = t`Confirm`,
confirmButtonAccent = 'danger',
isLoading = false,
closeSidePanelOnShowPageOptionsExecution,
closeSidePanelOnCommandMenuListExecution,
}: CommandModalProps) => {
const { openModal } = useModal();
const { closeCommandMenu } = useCloseCommandMenu({
closeSidePanelOnShowPageOptionsExecution,
closeSidePanelOnCommandMenuListExecution,
});
const handleConfirmClick = async () => {
await onConfirmClick();
closeCommandMenu();
};
const commandMenuItemConfig = useContext(CommandConfigContext);
const { containerType } = useContext(CommandMenuContext);
const modalId = `${commandMenuItemConfig?.key}-command-menu-item-modal-${containerType}`;
const isModalOpened = useAtomComponentStateValue(
isModalOpenedComponentState,
modalId,
);
if (!commandMenuItemConfig) {
return null;
}
const handleClick = () => openModal(modalId);
return (
<>
<CommandMenuItemDisplay onClick={handleClick} />
{isModalOpened && (
<ConfirmationModal
modalInstanceId={modalId}
title={title}
subtitle={subtitle}
onConfirmClick={handleConfirmClick}
confirmButtonText={confirmButtonText}
confirmButtonAccent={confirmButtonAccent}
loading={isLoading}
/>
)}
</>
);
};
@@ -0,0 +1,42 @@
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu';
import { isHeadlessFrontComponentMountedFamilySelector } from '@/front-components/selectors/isHeadlessFrontComponentMountedFamilySelector';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { useContext } from 'react';
import { CommandMenuItemDisplay } from './CommandMenuItemDisplay';
export const HeadlessFrontComponentCommandMenuItem = ({
frontComponentId,
onClick,
}: {
frontComponentId: string;
onClick: () => void;
}) => {
const commandMenuItemConfig = useContext(CommandConfigContext);
const { closeCommandMenu } = useCloseCommandMenu({
closeSidePanelOnShowPageOptionsExecution: false,
closeSidePanelOnCommandMenuListExecution: false,
});
const isMounted = useAtomFamilySelectorValue(
isHeadlessFrontComponentMountedFamilySelector,
frontComponentId,
);
if (!commandMenuItemConfig) {
return null;
}
const handleClick = () => {
if (isMounted) {
return;
}
closeCommandMenu();
onClick();
};
return <CommandMenuItemDisplay onClick={handleClick} disabled={isMounted} />;
};
@@ -0,0 +1,67 @@
import { CommandMenuItemButton } from '@/command-menu-item/display/components/CommandMenuItemButton';
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
const meta: Meta<typeof CommandMenuItemButton> = {
title: 'Modules/CommandMenuItem/Display/CommandMenuItemButton',
component: CommandMenuItemButton,
decorators: [ComponentDecorator, RouterDecorator],
};
export default meta;
type Story = StoryObj<typeof CommandMenuItemButton>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockCommandMenuItems({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesCommandMenuItem = mockActions.find(
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
);
const goToPeopleCommandMenuItem = mockActions.find(
(action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
);
export const Default: Story = {
args: {
action: addToFavoritesCommandMenuItem,
onClick: addToFavoritesMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(
addToFavoritesCommandMenuItem?.shortLabel ?? '',
),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const WithLink: Story = {
args: {
action: goToPeopleCommandMenuItem,
to: '/objects/people',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const menuItem = await canvas.findByText(
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.shortLabel ?? ''),
);
expect(menuItem).toBeVisible();
expect(canvas.getByRole('link')).toHaveAttribute('href', '/objects/people');
},
};
@@ -0,0 +1,69 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, within } from 'storybook/test';
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { ComponentDecorator } from 'twenty-ui/testing';
const mockActions = createMockCommandMenuItems({});
const addToFavoritesCommandMenuItem = mockActions.find(
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
);
if (!addToFavoritesCommandMenuItem) {
throw new Error('Add to favorites action not found');
}
const meta: Meta<typeof CommandMenuItemComponent> = {
title: 'Modules/CommandMenuItem/Display/CommandMenuItemComponent',
component: CommandMenuItemComponent,
decorators: [
ComponentDecorator,
(Story) => (
<CommandMenuComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<CommandMenuContext.Provider
value={{
isInSidePanel: false,
containerType: 'index-page-header',
displayType: 'button',
commandMenuItems: [addToFavoritesCommandMenuItem],
}}
>
<Story />
</CommandMenuContext.Provider>
</CommandMenuComponentInstanceContext.Provider>
),
],
args: {
action: addToFavoritesCommandMenuItem,
},
parameters: {
container: {
width: 'auto',
},
},
};
export default meta;
type Story = StoryObj<typeof CommandMenuItemComponent>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(
await canvas.findByText(
getCommandMenuItemLabel(
addToFavoritesCommandMenuItem?.shortLabel ?? '',
),
),
).toBeVisible();
},
};
@@ -0,0 +1,147 @@
import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay';
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
type Story = StoryObj<typeof CommandMenuItemDisplay>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockCommandMenuItems({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesCommandMenuItem = mockActions.find(
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
);
if (!addToFavoritesCommandMenuItem) {
throw new Error('addToFavoritesCommandMenuItem not found');
}
const meta: Meta<typeof CommandMenuItemDisplay> = {
title: 'Modules/CommandMenuItem/Display/CommandMenuItemDisplay',
component: CommandMenuItemDisplay,
decorators: [
(Story) => (
<CommandConfigContext.Provider value={addToFavoritesCommandMenuItem}>
<Story />
</CommandConfigContext.Provider>
),
ComponentDecorator,
RouterDecorator,
],
};
export default meta;
export const AsButton: Story = {
args: {
onClick: addToFavoritesMock,
},
decorators: [
(Story) => (
<CommandMenuContext.Provider
value={{
isInSidePanel: false,
containerType: 'command-menu-list',
displayType: 'button',
commandMenuItems: [],
}}
>
<Story />
</CommandMenuContext.Provider>
),
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(
addToFavoritesCommandMenuItem?.shortLabel ?? '',
),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const AsListItem: Story = {
args: {
onClick: addToFavoritesMock,
},
decorators: [
(Story) => (
<SelectableListComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<Story />
</SelectableListComponentInstanceContext.Provider>
),
(Story) => (
<CommandMenuContext.Provider
value={{
isInSidePanel: false,
containerType: 'command-menu-list',
displayType: 'listItem',
commandMenuItems: [],
}}
>
<Story />
</CommandMenuContext.Provider>
),
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const AsDropdownItem: Story = {
args: {
onClick: addToFavoritesMock,
},
decorators: [
(Story) => (
<SelectableListComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<Story />
</SelectableListComponentInstanceContext.Provider>
),
(Story) => (
<CommandMenuContext.Provider
value={{
isInSidePanel: false,
containerType: 'command-menu-list',
displayType: 'dropdownItem',
commandMenuItems: [],
}}
>
<Story />
</CommandMenuContext.Provider>
),
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
@@ -0,0 +1,75 @@
import { CommandDropdownItem } from '@/command-menu-item/display/components/CommandDropdownItem';
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
const meta: Meta<typeof CommandDropdownItem> = {
title: 'Modules/CommandMenuItem/Display/CommandDropdownItem',
component: CommandDropdownItem,
decorators: [
(Story) => (
<SelectableListComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<Story />
</SelectableListComponentInstanceContext.Provider>
),
ComponentDecorator,
RouterDecorator,
],
};
export default meta;
type Story = StoryObj<typeof CommandDropdownItem>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockCommandMenuItems({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesCommandMenuItem = mockActions.find(
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
);
const goToPeopleCommandMenuItem = mockActions.find(
(action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
);
export const Default: Story = {
args: {
action: addToFavoritesCommandMenuItem,
onClick: addToFavoritesMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const WithLink: Story = {
args: {
action: goToPeopleCommandMenuItem,
to: '/objects/people',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const dropdownItem = await canvas.findByText(
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.label ?? ''),
);
expect(dropdownItem).toBeVisible();
},
};
@@ -0,0 +1,75 @@
import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem';
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
type Story = StoryObj<typeof CommandListItem>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockCommandMenuItems({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesCommandMenuItem = mockActions.find(
(action) => action.key === SingleRecordCommandKeys.ADD_TO_FAVORITES,
);
const goToPeopleCommandMenuItem = mockActions.find(
(action) => action.key === NoSelectionRecordCommandKeys.GO_TO_PEOPLE,
);
const meta: Meta<typeof CommandListItem> = {
title: 'Modules/CommandMenuItem/Display/CommandListItem',
component: CommandListItem,
decorators: [
(Story) => (
<SelectableListComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<Story />
</SelectableListComponentInstanceContext.Provider>
),
ComponentDecorator,
RouterDecorator,
],
};
export default meta;
export const Default: Story = {
args: {
action: addToFavoritesCommandMenuItem,
onClick: addToFavoritesMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const WithLink: Story = {
args: {
action: goToPeopleCommandMenuItem,
to: '/objects/people',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const listItem = await canvas.findByText(
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.label ?? ''),
);
expect(listItem).toBeVisible();
},
};