Refactor command menu items deprecated code (#19508)

- Removes the intermediate `CommandMenuItemConfig` /
`CommandConfigContext` / `CommandMenuItemDisplay` abstraction layers,
replacing them with a single `CommandMenuItemRenderer` that renders
directly from the command menu items from the backend
- Eliminates the server-items/ subdirectory by moving its contents
(hooks/, contexts/, states/, display/, edit/) up into the parent
command-menu-item/ module, removing an unnecessary nesting level.
This commit is contained in:
Raphaël Bosi
2026-04-10 11:28:27 +02:00
committed by GitHub
parent f13e7e01fe
commit 4b3a46d953
132 changed files with 1376 additions and 2723 deletions
@@ -1,8 +1,8 @@
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons';
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/display/components/PinnedCommandMenuItemButtons';
import { RecordIndexCommandMenuDropdown } from '@/command-menu-item/components/RecordIndexCommandMenuDropdown';
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
import { CommandMenuItemEditButton } from '@/command-menu-item/server-items/edit/components/CommandMenuItemEditButton';
import { PinnedCommandMenuItemButtonsEditMode } from '@/command-menu-item/server-items/edit/components/PinnedCommandMenuItemButtonsEditMode';
import { CommandMenuItemEditButton } from '@/command-menu-item/edit/components/CommandMenuItemEditButton';
import { PinnedCommandMenuItemButtonsEditMode } from '@/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
@@ -1,11 +1,9 @@
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
import { COMMAND_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/command-menu-item/constants/CommandMenuDropdownClickOutsideId';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { CommandMenuItemRenderer } from '@/command-menu-item/display/components/CommandMenuItemRenderer';
import { recordIndexCommandMenuDropdownPositionComponentState } from '@/command-menu-item/states/recordIndexCommandMenuDropdownPositionComponentState';
import { getCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getCommandMenuDropdownIdFromCommandMenuId';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
@@ -21,6 +19,7 @@ import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { IconLayoutSidebarRightExpand } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql';
const StyledDropdownMenuContainer = styled.div`
align-items: center;
@@ -36,10 +35,10 @@ export const RecordIndexCommandMenuDropdown = () => {
const { t } = useLingui();
const { commandMenuItems } = useContext(CommandMenuContext);
const recordIndexActions = commandMenuItems.filter(
(action) =>
action.type === CommandMenuItemType.Standard &&
action.scope === CommandMenuItemScope.RecordSelection,
const recordIndexCommandMenuItems = commandMenuItems.filter(
(item) =>
item.availabilityType ===
CommandMenuItemAvailabilityType.RECORD_SELECTION,
);
const commandMenuId = useAvailableComponentInstanceIdOrThrow(
@@ -57,7 +56,7 @@ export const RecordIndexCommandMenuDropdown = () => {
const { openSidePanelMenu } = useSidePanelMenu();
const selectedItemIdArray = [
...recordIndexActions.map((action) => action.key),
...recordIndexCommandMenuItems.map((item) => item.id),
'more-actions',
];
@@ -87,8 +86,8 @@ export const RecordIndexCommandMenuDropdown = () => {
selectableItemIdArray={selectedItemIdArray}
selectableListInstanceId={dropdownId}
>
{recordIndexActions.map((action) => (
<CommandMenuItemComponent action={action} key={action.key} />
{recordIndexCommandMenuItems.map((item) => (
<CommandMenuItemRenderer item={item} key={item.id} />
))}
<SelectableListItem
itemId="more-actions"
@@ -1,16 +1,16 @@
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { CommandMenuItemRenderer } from '@/command-menu-item/display/components/CommandMenuItemRenderer';
import { getSidePanelCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getSidePanelCommandMenuDropdownIdFromCommandMenuId';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { OptionsDropdownMenu } from '@/ui/layout/dropdown/components/OptionsDropdownMenu';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { sidePanelWidgetFooterActionsState } from '@/ui/layout/side-panel/states/sidePanelWidgetFooterActionsState';
import { sidePanelWidgetFooterCommandMenuItemsState } from '@/ui/layout/side-panel/states/sidePanelWidgetFooterCommandMenuItemsState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useContext } from 'react';
import { useContext, useMemo } from 'react';
import { HorizontalSeparator } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql';
export const RecordPageSidePanelCommandMenuDropdown = () => {
const { commandMenuItems } = useContext(CommandMenuContext);
@@ -24,21 +24,30 @@ export const RecordPageSidePanelCommandMenuDropdown = () => {
const { closeDropdown } = useCloseDropdown();
const sidePanelWidgetFooterActions = useAtomStateValue(
sidePanelWidgetFooterActionsState,
const sidePanelWidgetFooterCommandMenuItems = useAtomStateValue(
sidePanelWidgetFooterCommandMenuItemsState,
);
const dropdownWidgetActions = sidePanelWidgetFooterActions.filter(
(action) => action.isPinned === false,
);
const dropdownWidgetCommandMenuItems =
sidePanelWidgetFooterCommandMenuItems.filter(
(commandMenuItem) => commandMenuItem.isPinned === false,
);
const recordSelectionActions = commandMenuItems.filter(
(action) => action.scope === CommandMenuItemScope.RecordSelection,
const recordSelectionCommandMenuItems = useMemo(
() =>
commandMenuItems.filter(
(item) =>
item.availabilityType ===
CommandMenuItemAvailabilityType.RECORD_SELECTION,
),
[commandMenuItems],
);
const selectableItemIdArray = [
...dropdownWidgetActions.map((action) => action.key),
...recordSelectionActions.map((action) => action.key),
...dropdownWidgetCommandMenuItems.map(
(commandMenuItem) => commandMenuItem.id,
),
...recordSelectionCommandMenuItems.map((item) => item.id),
];
return (
@@ -47,21 +56,23 @@ export const RecordPageSidePanelCommandMenuDropdown = () => {
selectableListId={commandMenuId}
selectableItemIdArray={selectableItemIdArray}
>
{dropdownWidgetActions.map((action) => (
{dropdownWidgetCommandMenuItems.map((commandMenuItem) => (
<MenuItem
key={action.key}
text={action.label}
LeftIcon={action.Icon}
key={commandMenuItem.id}
text={commandMenuItem.label}
LeftIcon={commandMenuItem.Icon}
onClick={() => {
closeDropdown(dropdownId);
action.onClick();
commandMenuItem.onClick();
}}
/>
))}
{dropdownWidgetActions.length > 0 &&
recordSelectionActions.length > 0 && <HorizontalSeparator noMargin />}
{recordSelectionActions.map((action) => (
<CommandMenuItemComponent action={action} key={action.key} />
{dropdownWidgetCommandMenuItems.length > 0 &&
recordSelectionCommandMenuItems.length > 0 && (
<HorizontalSeparator noMargin />
)}
{recordSelectionCommandMenuItems.map((item) => (
<CommandMenuItemRenderer item={item} key={item.id} />
))}
</OptionsDropdownMenu>
);
@@ -1,6 +1,6 @@
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons';
import { CommandMenuItemEditButton } from '@/command-menu-item/server-items/edit/components/CommandMenuItemEditButton';
import { PinnedCommandMenuItemButtons } from '@/command-menu-item/display/components/PinnedCommandMenuItemButtons';
import { CommandMenuItemEditButton } from '@/command-menu-item/edit/components/CommandMenuItemEditButton';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
@@ -1,22 +1,20 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { Provider as JotaiProvider } from 'jotai';
import * as test from 'storybook/test';
import { expect, userEvent, waitFor, within } from 'storybook/test';
import { expect, within } from 'storybook/test';
import { RecordIndexCommandMenuDropdown } from '@/command-menu-item/components/RecordIndexCommandMenuDropdown';
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 { recordIndexCommandMenuDropdownPositionComponentState } from '@/command-menu-item/states/recordIndexCommandMenuDropdownPositionComponentState';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { EMPTY_COMMAND_MENU_CONTEXT_API } from '@/command-menu-item/constants/EmptyCommandMenuContextApi';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { RouterDecorator } from 'twenty-ui/testing';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
const deleteMock = test.fn();
const addToFavoritesMock = test.fn();
const exportMock = test.fn();
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
const meta: Meta<typeof RecordIndexCommandMenuDropdown> = {
title: 'Modules/CommandMenu/RecordIndexCommandMenuDropdown',
@@ -43,14 +41,10 @@ const meta: Meta<typeof RecordIndexCommandMenuDropdown> = {
>
<CommandMenuContext.Provider
value={{
isInSidePanel: true,
displayType: 'dropdownItem',
containerType: 'index-page-dropdown',
commandMenuItems: createMockCommandMenuItems({
deleteMock,
addToFavoritesMock,
exportMock,
}),
commandMenuItems: createMockCommandMenuItems(),
commandMenuContextApi: EMPTY_COMMAND_MENU_CONTEXT_API,
}}
>
<Story />
@@ -60,6 +54,8 @@ const meta: Meta<typeof RecordIndexCommandMenuDropdown> = {
);
},
ContextStoreDecorator,
ObjectMetadataItemsDecorator,
SnackBarDecorator,
RouterDecorator,
],
};
@@ -82,21 +78,13 @@ export const WithInteractions: Story = {
const canvas = within(canvasElement.ownerDocument.body);
const deleteButton = await canvas.findByText('Delete');
await userEvent.click(deleteButton);
const addToFavoritesButton = await canvas.findByText('Add to favorites');
await userEvent.click(addToFavoritesButton);
const exportButton = await canvas.findByText('Export');
await userEvent.click(exportButton);
const moreActionsButton = await canvas.findByText('More actions');
await waitFor(() => {
expect(deleteMock).toHaveBeenCalled();
expect(addToFavoritesMock).toHaveBeenCalled();
expect(exportMock).toHaveBeenCalled();
expect(moreActionsButton).toBeInTheDocument();
});
expect(deleteButton).toBeInTheDocument();
expect(addToFavoritesButton).toBeInTheDocument();
expect(exportButton).toBeInTheDocument();
expect(moreActionsButton).toBeInTheDocument();
},
};
@@ -1,7 +1,6 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { Provider as JotaiProvider } from 'jotai';
import * as test from 'storybook/test';
import { expect, userEvent, waitFor, within } from 'storybook/test';
import { userEvent, within } from 'storybook/test';
import { RecordPageSidePanelCommandMenuDropdown } from '@/command-menu-item/components/RecordPageSidePanelCommandMenuDropdown';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
@@ -9,15 +8,13 @@ import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-men
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { EMPTY_COMMAND_MENU_CONTEXT_API } from '@/command-menu-item/constants/EmptyCommandMenuContextApi';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
const deleteMock = test.fn();
const addToFavoritesMock = test.fn();
const exportMock = test.fn();
const meta: Meta<typeof RecordPageSidePanelCommandMenuDropdown> = {
title: 'Modules/CommandMenu/RecordPageSidePanelCommandMenuDropdown',
@@ -40,14 +37,13 @@ const meta: Meta<typeof RecordPageSidePanelCommandMenuDropdown> = {
>
<CommandMenuContext.Provider
value={{
isInSidePanel: true,
displayType: 'dropdownItem',
containerType: 'command-menu-show-page-dropdown',
commandMenuItems: createMockCommandMenuItems({
deleteMock,
addToFavoritesMock,
exportMock,
}),
commandMenuItems: createMockCommandMenuItems(),
commandMenuContextApi: {
...EMPTY_COMMAND_MENU_CONTEXT_API,
isInSidePanel: true,
},
}}
>
<Story />
@@ -102,11 +98,5 @@ export const WithButtonClicks: Story = {
const exportButton = await canvas.findByText('Export');
await userEvent.click(exportButton);
await waitFor(() => {
expect(deleteMock).toHaveBeenCalled();
expect(addToFavoritesMock).toHaveBeenCalled();
expect(exportMock).toHaveBeenCalled();
});
},
};