From 4b3a46d95301c7bb389af234007b594be525f64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:28:27 +0200 Subject: [PATCH] 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. --- .../components/RecordIndexCommandMenu.tsx | 6 +- .../RecordIndexCommandMenuDropdown.tsx | 21 +- ...RecordPageSidePanelCommandMenuDropdown.tsx | 57 +++-- .../components/RecordShowCommandMenu.tsx | 4 +- ...RecordIndexCommandMenuDropdown.stories.tsx | 38 +-- ...owSidePanelCommandMenuDropdown.stories.tsx | 26 +- .../constants/EmptyCommandMenuContextApi.ts | 30 +++ .../contexts/CommandConfigContext.tsx | 6 - .../contexts/CommandMenuContext.ts | 10 +- .../contexts/CommandMenuContextProvider.tsx | 58 ++++- .../CommandMenuContextProviderContent.tsx | 58 +++++ ...ContextProviderWithWorkflowEnrichment.tsx} | 22 +- .../display/components/Command.tsx | 32 --- .../components/CommandDropdownItem.tsx | 58 ----- .../display/components/CommandLink.tsx | 21 -- .../display/components/CommandListItem.tsx | 61 ----- .../components/CommandMenuItemButton.tsx | 23 -- .../components/CommandMenuItemComponent.tsx | 14 - .../components/CommandMenuItemDisplay.tsx | 89 ------- .../components/CommandMenuItemRenderer.tsx | 154 +++++++++++ .../display/components/CommandModal.tsx | 79 ------ .../FrontComponentCommandMenuItem.tsx | 69 ----- .../components/HeadlessCommandMenuItem.tsx | 75 ------ .../PinnedCommandMenuItemButtons.tsx | 49 ++-- ...nnedCommandMenuItemsInlineMeasurements.tsx | 68 +++++ .../SidePanelCommandMenuItemDisplayPage.tsx | 121 ++++----- .../CommandMenuItemButton.stories.tsx | 65 ----- .../CommandMenuItemComponent.stories.tsx | 67 ----- .../CommandMenuItemDisplay.stories.tsx | 146 ----------- .../CommandMenuItemDropdownItem.stories.tsx | 75 ------ .../CommandMenuItemListItem.stories.tsx | 75 ------ .../constants/PinnedCommandMenuItemsGap.ts | 0 .../usePinnedCommandMenuItemsInlineLayout.ts | 42 +-- .../commandMenuPinnedInlineLayoutState.ts | 2 +- .../types/CommandMenuPinnedInlineLayout.ts | 0 ...mandMenuItemCountForContainerWidth.test.ts | 2 +- ...leCommandMenuItemCountForContainerWidth.ts | 0 .../utils/interpolateCommandMenuItemFields.ts | 32 +++ .../components/CommandMenuItemEditButton.tsx | 0 ...andMenuItemEditRecordSelectionDropdown.tsx | 2 +- .../CommandMenuItemOptionsDropdown.tsx | 2 +- .../PinnedCommandMenuItemButtonsEditMode.tsx | 102 +++----- .../SidePanelCommandMenuItemEditPage.tsx | 40 ++- .../useReorderCommandMenuItemsInDraft.ts | 6 +- .../hooks/useResetCommandMenuItemsDraft.ts | 4 +- .../hooks/useSaveCommandMenuItemsDraft.ts | 6 +- .../hooks/useSelectFirstRecordForEditMode.ts | 2 +- .../hooks/useUpdateCommandMenuItemInDraft.ts | 4 +- .../edit/states/commandMenuItemsDraftState.ts | 0 .../types/CommandMenuItemEditableFields.ts | 0 .../computeInsertPositionFromBounds.test.ts | 2 +- .../getPositionBoundsAtInsertionPoint.test.ts | 2 +- .../utils/computeInsertPositionFromBounds.ts | 0 .../getPositionBoundsAtInsertionPoint.ts | 0 .../edit/utils/getRecordIndexId.ts | 0 .../mutations/updateCommandMenuItem.ts | 0 .../__tests__/useCloseCommandMenu.test.tsx | 27 +- ...lockedByGlobalLayoutCustomization.test.tsx | 87 ------- .../hooks/useCloseCommandMenu.ts | 13 +- .../hooks/useCommandMenuContextApi.ts | 0 .../hooks/useCommandMenuItemClick.ts | 119 +++++++++ .../hooks/useCommandMenuItemsDraftState.ts | 4 +- ...mmandBlockedByGlobalLayoutCustomization.ts | 17 -- .../hooks/useWorkflowsWithCurrentVersions.ts | 0 .../mock/command-menu-items.mock.tsx | 175 ++++++------- .../CommandMenuContextProviderServerItems.tsx | 68 ----- ...dMenuContextProviderServerItemsContent.tsx | 40 --- .../useCommandMenuItemsFromBackend.test.tsx | 154 ----------- .../hooks/useCommandMenuItemsFromBackend.tsx | 242 ------------------ ...nnedCommandMenuItemsInlineMeasurements.tsx | 54 ---- .../states/commandMenuItemsSelector.ts | 0 .../types/CommandMenuItemConfig.ts | 31 --- .../types/CommandMenuItemScope.ts | 6 - .../types/CommandMenuItemType.ts | 7 - ...andMenuDropdownIdFromCommandMenuId.test.ts | 2 +- .../getCommandMenuIdFromRecordIndexId.test.ts | 2 +- ...andMenuDropdownIdFromCommandMenuId.test.ts | 2 +- ...oesCommandMenuItemMatchObjectMetadataId.ts | 0 .../utils/groupCommandMenuItems.ts | 18 ++ .../src/modules/command-menu/types/Command.ts | 26 -- .../hooks/useEnterLayoutCustomizationMode.ts | 4 +- .../hooks/useExitLayoutCustomizationMode.ts | 2 +- .../hooks/useIsLayoutCustomizationDirty.ts | 2 +- .../hooks/useSaveLayoutCustomization.ts | 4 +- .../SidePanelEditFolderPickerSubPage.tsx | 1 - .../components/SidePanelEditLinkItemView.tsx | 2 +- .../SidePanelEditObjectViewBase.tsx | 2 +- .../SidePanelNavigationMenuItemEditPage.tsx | 2 - .../SidePanelNewSidebarItemMainMenu.tsx | 2 +- .../SidePanelNewSidebarItemRecordSubPage.tsx | 1 - ...lNewSidebarItemViewObjectPickerSubView.tsx | 1 - ...dePanelNewSidebarItemViewPickerSubView.tsx | 1 - ...dePanelNewSidebarItemViewSystemSubView.tsx | 1 - .../SidePanelObjectPickerSubView.tsx | 2 - .../SidePanelSystemObjectPickerSubView.tsx | 2 - .../components/RecordIndexPageHeader.tsx | 6 +- .../PageLayoutRecordPageRenderer.tsx | 42 +-- ...UpdateMultipleRecordsContainer.stories.tsx | 3 +- .../components/EmailThreadComposer.tsx | 22 +- .../side-panel/components/SidePanelList.tsx | 16 +- .../components/SidePanelToggleButton.tsx} | 2 +- .../constants/SidePanelPagesConfig.tsx | 4 +- .../page-layout/components/ChartSettings.tsx | 2 +- ...nelPageLayoutDashboardWidgetTypeSelect.tsx | 2 +- ...elPageLayoutRecordPageWidgetTypeSelect.tsx | 2 +- .../SidePanelPageLayoutTabSettingsContent.tsx | 2 +- .../SidePanelDashboardIframeSettings.tsx | 2 +- .../SidePanelDashboardRecordTableSettings.tsx | 5 +- .../SidePanelRecordPageFieldSettings.tsx | 2 +- .../SidePanelRecordPageFieldsSettings.tsx | 2 +- .../root/components/SidePanelRootPage.tsx | 137 +++++----- ...eFilterActionsWithSidePanelSearch.test.tsx | 102 -------- ...mmandMenuItemsWithSidePanelSearch.test.tsx | 142 ++++++++++ .../useFilterActionsWithSidePanelSearch.ts | 44 ---- ...lterCommandMenuItemsWithSidePanelSearch.ts | 63 +++++ .../pages/root/hooks/useSidePanelActions.tsx | 84 ------ .../root/hooks/useSidePanelMatchingActions.ts | 81 ------ .../components/SidePanelSearchRecordsPage.tsx | 80 +++++- .../hooks/useSidePanelSearchRecords.tsx | 124 +++------ .../SidePanelCommandMenuItemGroupConfig.ts | 6 - .../sidePanelWidgetFooterActionsState.ts | 9 - ...ePanelWidgetFooterCommandMenuItemsState.ts | 9 + ...n.ts => SidePanelFooterCommandMenuItem.ts} | 4 +- .../components/WorkflowDiagramCanvasBase.tsx | 11 +- .../WorkflowRunVisualizerEffect.tsx | 3 +- .../hooks/useStartNodeCreation.ts | 5 +- .../WorkflowDiagramEmptyTriggerEditable.tsx | 5 +- .../WorkflowDiagramEmptyTriggerReadonly.tsx | 5 +- .../WorkflowDiagramStepNodeEditable.tsx | 5 +- .../WorkflowDiagramStepNodeReadonly.tsx | 5 +- .../components/WorkflowRunDiagramStepNode.tsx | 5 +- .../pages/object-record/RecordShowPage.tsx | 6 +- 132 files changed, 1376 insertions(+), 2723 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/contexts/CommandConfigContext.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx rename packages/twenty-front/src/modules/command-menu-item/{server-items/common/contexts/CommandMenuContextProviderServerItemsWithWorkflowEnrichment.tsx => contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx} (71%) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/Command.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandDropdownItem.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandLink.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandListItem.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemButton.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemComponent.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemDisplay.tsx create mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/CommandModal.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/FrontComponentCommandMenuItem.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/components/PinnedCommandMenuItemButtons.tsx (61%) create mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/components/SidePanelCommandMenuItemDisplayPage.tsx (54%) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/constants/PinnedCommandMenuItemsGap.ts (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts (70%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/states/commandMenuPinnedInlineLayoutState.ts (85%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/types/CommandMenuPinnedInlineLayout.ts (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/utils/__tests__/getVisibleCommandMenuItemCountForContainerWidth.test.ts (93%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/display/utils/getVisibleCommandMenuItemCountForContainerWidth.ts (100%) create mode 100644 packages/twenty-front/src/modules/command-menu-item/display/utils/interpolateCommandMenuItemFields.ts rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/components/CommandMenuItemEditButton.tsx (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx (98%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/components/CommandMenuItemOptionsDropdown.tsx (98%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx (57%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/components/SidePanelCommandMenuItemEditPage.tsx (90%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/hooks/useReorderCommandMenuItemsInDraft.ts (93%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/hooks/useResetCommandMenuItemsDraft.ts (77%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/hooks/useSaveCommandMenuItemsDraft.ts (85%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/hooks/useSelectFirstRecordForEditMode.ts (95%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/hooks/useUpdateCommandMenuItemInDraft.ts (87%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/states/commandMenuItemsDraftState.ts (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/types/CommandMenuItemEditableFields.ts (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/utils/__tests__/computeInsertPositionFromBounds.test.ts (95%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/utils/__tests__/getPositionBoundsAtInsertionPoint.test.ts (97%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/utils/computeInsertPositionFromBounds.ts (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/utils/getPositionBoundsAtInsertionPoint.ts (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items => }/edit/utils/getRecordIndexId.ts (100%) rename packages/twenty-front/src/modules/command-menu-item/{server-items/common => }/graphql/mutations/updateCommandMenuItem.ts (100%) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useIsCommandBlockedByGlobalLayoutCustomization.test.tsx rename packages/twenty-front/src/modules/command-menu-item/{server-items/common => }/hooks/useCommandMenuContextApi.ts (100%) create mode 100644 packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts rename packages/twenty-front/src/modules/command-menu-item/{server-items/common => }/hooks/useCommandMenuItemsDraftState.ts (84%) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/hooks/useIsCommandBlockedByGlobalLayoutCustomization.ts rename packages/twenty-front/src/modules/command-menu-item/{server-items/common => }/hooks/useWorkflowsWithCurrentVersions.ts (100%) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItems.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItemsContent.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx delete mode 100644 packages/twenty-front/src/modules/command-menu-item/server-items/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx rename packages/twenty-front/src/modules/command-menu-item/{server-items/common => }/states/commandMenuItemsSelector.ts (100%) delete mode 100644 packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemScope.ts delete mode 100644 packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemType.ts rename packages/twenty-front/src/modules/command-menu-item/{server-items/common => }/utils/doesCommandMenuItemMatchObjectMetadataId.ts (100%) create mode 100644 packages/twenty-front/src/modules/command-menu-item/utils/groupCommandMenuItems.ts delete mode 100644 packages/twenty-front/src/modules/command-menu/types/Command.ts rename packages/twenty-front/src/modules/{command-menu-item/server-items/display/components/CommandMenuItemMoreActionsButton.tsx => side-panel/components/SidePanelToggleButton.tsx} (97%) delete mode 100644 packages/twenty-front/src/modules/side-panel/pages/root/hooks/__tests__/useFilterActionsWithSidePanelSearch.test.tsx create mode 100644 packages/twenty-front/src/modules/side-panel/pages/root/hooks/__tests__/useFilterCommandMenuItemsWithSidePanelSearch.test.tsx delete mode 100644 packages/twenty-front/src/modules/side-panel/pages/root/hooks/useFilterActionsWithSidePanelSearch.ts create mode 100644 packages/twenty-front/src/modules/side-panel/pages/root/hooks/useFilterCommandMenuItemsWithSidePanelSearch.ts delete mode 100644 packages/twenty-front/src/modules/side-panel/pages/root/hooks/useSidePanelActions.tsx delete mode 100644 packages/twenty-front/src/modules/side-panel/pages/root/hooks/useSidePanelMatchingActions.ts delete mode 100644 packages/twenty-front/src/modules/side-panel/types/SidePanelCommandMenuItemGroupConfig.ts delete mode 100644 packages/twenty-front/src/modules/ui/layout/side-panel/states/sidePanelWidgetFooterActionsState.ts create mode 100644 packages/twenty-front/src/modules/ui/layout/side-panel/states/sidePanelWidgetFooterCommandMenuItemsState.ts rename packages/twenty-front/src/modules/ui/layout/side-panel/types/{SidePanelFooterAction.ts => SidePanelFooterCommandMenuItem.ts} (77%) diff --git a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx index 51f6007335..7b407eaee6 100644 --- a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx @@ -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'; diff --git a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenuDropdown.tsx b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenuDropdown.tsx index c7453f32e2..7887b0ac86 100644 --- a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenuDropdown.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenuDropdown.tsx @@ -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) => ( - + {recordIndexCommandMenuItems.map((item) => ( + ))} { 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) => ( { closeDropdown(dropdownId); - action.onClick(); + commandMenuItem.onClick(); }} /> ))} - {dropdownWidgetActions.length > 0 && - recordSelectionActions.length > 0 && } - {recordSelectionActions.map((action) => ( - + {dropdownWidgetCommandMenuItems.length > 0 && + recordSelectionCommandMenuItems.length > 0 && ( + + )} + {recordSelectionCommandMenuItems.map((item) => ( + ))} ); diff --git a/packages/twenty-front/src/modules/command-menu-item/components/RecordShowCommandMenu.tsx b/packages/twenty-front/src/modules/command-menu-item/components/RecordShowCommandMenu.tsx index a588f7f0e0..1247fe1ebe 100644 --- a/packages/twenty-front/src/modules/command-menu-item/components/RecordShowCommandMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/components/RecordShowCommandMenu.tsx @@ -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'; diff --git a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx index e62ce6b6e8..5c8e6683a0 100644 --- a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx @@ -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 = { title: 'Modules/CommandMenu/RecordIndexCommandMenuDropdown', @@ -43,14 +41,10 @@ const meta: Meta = { > @@ -60,6 +54,8 @@ const meta: Meta = { ); }, 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(); }, }; diff --git a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx index 9354a42882..2d91768e8f 100644 --- a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx @@ -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 = { title: 'Modules/CommandMenu/RecordPageSidePanelCommandMenuDropdown', @@ -40,14 +37,13 @@ const meta: Meta = { > @@ -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(); - }); }, }; diff --git a/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts new file mode 100644 index 0000000000..b94e4bc366 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts @@ -0,0 +1,30 @@ +import { + CommandMenuContextApiPageType, + type CommandMenuContextApi, +} from 'twenty-shared/types'; + +export const EMPTY_COMMAND_MENU_CONTEXT_API: CommandMenuContextApi = { + pageType: CommandMenuContextApiPageType.INDEX_PAGE, + isInSidePanel: false, + isPageInEditMode: false, + favoriteRecordIds: [], + isSelectAll: false, + hasAnySoftDeleteFilterOnView: false, + numberOfSelectedRecords: 0, + objectPermissions: { + canReadObjectRecords: false, + canUpdateObjectRecords: false, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + restrictedFields: {}, + objectMetadataId: '', + rowLevelPermissionPredicates: [], + rowLevelPermissionPredicateGroups: [], + }, + selectedRecords: [], + featureFlags: {}, + targetObjectReadPermissions: {}, + targetObjectWritePermissions: {}, + objectMetadataItem: {}, + objectMetadataLabel: '', +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandConfigContext.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandConfigContext.tsx deleted file mode 100644 index 9efde55ba8..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandConfigContext.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { createContext } from 'react'; - -export const CommandConfigContext = createContext( - null, -); diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts index ab85985573..2af4c363af 100644 --- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts +++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts @@ -1,17 +1,19 @@ -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; +import { EMPTY_COMMAND_MENU_CONTEXT_API } from '@/command-menu-item/constants/EmptyCommandMenuContextApi'; import { type CommandMenuItemContainerType } from '@/command-menu-item/types/CommandMenuItemContainerType'; import { createContext } from 'react'; +import { type CommandMenuContextApi } from 'twenty-shared/types'; +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; export type CommandMenuContextType = { - isInSidePanel: boolean; displayType: 'button' | 'listItem' | 'dropdownItem'; containerType: CommandMenuItemContainerType; - commandMenuItems: CommandMenuItemConfig[]; + commandMenuItems: CommandMenuItemFieldsFragment[]; + commandMenuContextApi: CommandMenuContextApi; }; export const CommandMenuContext = createContext({ - isInSidePanel: false, containerType: 'command-menu-list', displayType: 'button', commandMenuItems: [], + commandMenuContextApi: EMPTY_COMMAND_MENU_CONTEXT_API, }); diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx index fc9f8727a7..355e38e19d 100644 --- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx @@ -1,21 +1,63 @@ +import { CoreObjectNameSingular } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext'; -import { CommandMenuContextProviderServerItems } from '@/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItems'; +import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi'; + +import { CommandMenuContextProviderContent } from './CommandMenuContextProviderContent'; +import { CommandMenuContextProviderWithWorkflowEnrichment } from './CommandMenuContextProviderWithWorkflowEnrichment'; + +type CommandMenuContextProviderProps = { + isInSidePanel: boolean; + displayType: CommandMenuContextType['displayType']; + containerType: CommandMenuContextType['containerType']; + children: React.ReactNode; +}; export const CommandMenuContextProvider = ({ - children, isInSidePanel, displayType, containerType, -}: Omit & { - children: React.ReactNode; -}) => { + children, +}: CommandMenuContextProviderProps) => { + const commandMenuContextApiFromHook = useCommandMenuContextApi(); + + const commandMenuContextApi = isInSidePanel + ? { ...commandMenuContextApiFromHook, isInSidePanel: true } + : commandMenuContextApiFromHook; + + const currentObjectNameSingular = + commandMenuContextApi.objectMetadataItem.nameSingular; + + const isWorkflow = + currentObjectNameSingular === CoreObjectNameSingular.Workflow; + + const selectedWorkflowRecordIds = isWorkflow + ? commandMenuContextApi.selectedRecords + .map((record) => record.id) + .filter(isDefined) + : []; + + if (selectedWorkflowRecordIds.length > 0) { + return ( + + {children} + + ); + } + return ( - {children} - + ); }; diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx new file mode 100644 index 0000000000..07cf6f59fc --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx @@ -0,0 +1,58 @@ +import { + CommandMenuContext, + type CommandMenuContextType, +} from '@/command-menu-item/contexts/CommandMenuContext'; +import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector'; +import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { useMemo } from 'react'; +import { type CommandMenuContextApi } from 'twenty-shared/types'; +import { evaluateConditionalAvailabilityExpression } from 'twenty-shared/utils'; + +type CommandMenuContextProviderContentProps = { + displayType: CommandMenuContextType['displayType']; + containerType: CommandMenuContextType['containerType']; + children: React.ReactNode; + commandMenuContextApi: CommandMenuContextApi; +}; + +export const CommandMenuContextProviderContent = ({ + displayType, + containerType, + children, + commandMenuContextApi, +}: CommandMenuContextProviderContentProps) => { + const commandMenuItems = useAtomStateValue(commandMenuItemsSelector); + + const filteredCommandMenuItems = useMemo(() => { + const currentObjectMetadataItemId = + commandMenuContextApi.objectMetadataItem.id; + + return commandMenuItems + .filter( + doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId), + ) + .filter((item) => + evaluateConditionalAvailabilityExpression( + item.conditionalAvailabilityExpression, + commandMenuContextApi, + ), + ) + .sort( + (firstItem, secondItem) => firstItem.position - secondItem.position, + ); + }, [commandMenuItems, commandMenuContextApi]); + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItemsWithWorkflowEnrichment.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx similarity index 71% rename from packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItemsWithWorkflowEnrichment.tsx rename to packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx index 63e7769d38..3242645aff 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItemsWithWorkflowEnrichment.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx @@ -2,28 +2,25 @@ import { type CommandMenuContextApi } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext'; -import { useWorkflowsWithCurrentVersions } from '@/command-menu-item/server-items/common/hooks/useWorkflowsWithCurrentVersions'; +import { useWorkflowsWithCurrentVersions } from '@/command-menu-item/hooks/useWorkflowsWithCurrentVersions'; -import { CommandMenuContextProviderServerItemsContent } from './CommandMenuContextProviderServerItemsContent'; +import { CommandMenuContextProviderContent } from './CommandMenuContextProviderContent'; -type CommandMenuContextProviderServerItemsWithWorkflowEnrichmentProps = { - isInSidePanel: CommandMenuContextType['isInSidePanel']; +type CommandMenuContextProviderWithWorkflowEnrichmentProps = { displayType: CommandMenuContextType['displayType']; containerType: CommandMenuContextType['containerType']; children: React.ReactNode; + commandMenuContextApi: CommandMenuContextApi; + selectedWorkflowRecordIds: string[]; }; -export const CommandMenuContextProviderServerItemsWithWorkflowEnrichment = ({ - isInSidePanel, +export const CommandMenuContextProviderWithWorkflowEnrichment = ({ displayType, containerType, children, commandMenuContextApi, selectedWorkflowRecordIds, -}: CommandMenuContextProviderServerItemsWithWorkflowEnrichmentProps & { - commandMenuContextApi: CommandMenuContextApi; - selectedWorkflowRecordIds: string[]; -}) => { +}: CommandMenuContextProviderWithWorkflowEnrichmentProps) => { const workflowsWithCurrentVersions = useWorkflowsWithCurrentVersions( selectedWorkflowRecordIds, ); @@ -53,13 +50,12 @@ export const CommandMenuContextProviderServerItemsWithWorkflowEnrichment = ({ }; return ( - {children} - + ); }; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/Command.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/Command.tsx deleted file mode 100644 index ca4a88f4dc..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/Command.tsx +++ /dev/null @@ -1,32 +0,0 @@ -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 ; -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandDropdownItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandDropdownItem.tsx deleted file mode 100644 index 7ca628cb0a..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandDropdownItem.tsx +++ /dev/null @@ -1,58 +0,0 @@ -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, - disabled = false, -}: { - action: CommandMenuItemDisplayProps; - onClick?: () => void; - to?: string; - disabled?: boolean; -}) => { - const navigate = useNavigate(); - - const handleClick = () => { - if (disabled) { - return; - } - - onClick?.(); - if (isDefined(to)) { - navigate(to); - } - }; - - const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow( - SelectableListComponentInstanceContext, - ); - - const isSelectedItemId = useAtomComponentFamilyStateValue( - isSelectedItemIdComponentFamilyState, - action.key, - selectableListInstanceId, - ); - - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandLink.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandLink.tsx deleted file mode 100644 index ec62ee542d..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandLink.tsx +++ /dev/null @@ -1,21 +0,0 @@ -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 = ({ - to, - params, - queryParams, -}: { - to: T; - params?: { [key in PathParam]: string | null }; - queryParams?: Record; -}) => { - const { closeCommandMenu } = useCloseCommandMenu(); - - const path = getAppPath(to, params, queryParams); - - return ; -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandListItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandListItem.tsx deleted file mode 100644 index f212514482..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandListItem.tsx +++ /dev/null @@ -1,61 +0,0 @@ -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'; -import { CommandListItemLoader } from './CommandListItemLoader'; - -export const CommandListItem = ({ - action, - onClick, - to, - disabled = false, - progress, - showDisabledLoader = false, -}: { - action: CommandMenuItemDisplayProps; - onClick?: () => void; - to?: string; - disabled?: boolean; - progress?: number; - showDisabledLoader?: boolean; -}) => { - const navigate = useNavigate(); - const handleClick = () => { - if (disabled) { - return; - } - - onClick?.(); - if (isDefined(to)) { - navigate(to); - } - }; - - const loaderComponent = - disabled && showDisabledLoader ? ( - isDefined(progress) ? ( - - ) : ( - - ) - ) : undefined; - - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemButton.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemButton.tsx deleted file mode 100644 index ac3f70b71c..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { CommandMenuButton } from '@/command-menu/components/CommandMenuButton'; -import { type CommandMenuItemDisplayProps } from '@/command-menu-item/display/components/CommandMenuItemDisplay'; - -export const CommandMenuItemButton = ({ - action, - onClick, - to, - disabled = false, -}: { - action: CommandMenuItemDisplayProps; - onClick?: (event?: React.MouseEvent) => void; - to?: string; - disabled?: boolean; -}) => { - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemComponent.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemComponent.tsx deleted file mode 100644 index ecc70d584b..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemComponent.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext'; -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; - -export const CommandMenuItemComponent = ({ - action, -}: { - action: CommandMenuItemConfig; -}) => { - return ( - - {action.component} - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemDisplay.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemDisplay.tsx deleted file mode 100644 index eda344a0be..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemDisplay.tsx +++ /dev/null @@ -1,89 +0,0 @@ -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 { useIsCommandBlockedByGlobalLayoutCustomization } from '@/command-menu-item/hooks/useIsCommandBlockedByGlobalLayoutCustomization'; -import { type MessageDescriptor } from '@lingui/core'; -import { useContext } from 'react'; -import { type Nullable } from 'twenty-shared/types'; -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: Nullable; - shortLabel?: Nullable; - description?: MessageDescriptor | string; - Icon: IconComponent; - isPrimaryCTA?: boolean; - accent?: MenuItemAccent; - hotKeys?: Nullable; -}; - -export const CommandMenuItemDisplay = ({ - onClick, - to, - disabled, - progress, - showDisabledLoader = false, -}: { - onClick?: (event?: React.MouseEvent) => void; - to?: string; - disabled?: boolean; - progress?: number; - showDisabledLoader?: boolean; -}) => { - const action = useContext(CommandConfigContext); - const { displayType } = useContext(CommandMenuContext); - const isBlockedByGlobalLayoutCustomization = - useIsCommandBlockedByGlobalLayoutCustomization(action); - - if (!action) { - return null; - } - - const isDisabled = - disabled === true || isBlockedByGlobalLayoutCustomization === true; - - const onClickWhenEnabled = isDisabled ? undefined : onClick; - const toWhenEnabled = isDisabled ? undefined : to; - - if (displayType === 'button') { - return ( - - ); - } - - if (displayType === 'listItem') { - return ( - - ); - } - - if (displayType === 'dropdownItem') { - return ( - - ); - } - - return assertUnreachable(displayType, 'Unsupported display type'); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx new file mode 100644 index 0000000000..7753cceee6 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx @@ -0,0 +1,154 @@ +import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; +import { CommandListItemLoader } from '@/command-menu-item/display/components/CommandListItemLoader'; +import { interpolateCommandMenuItemFields } from '@/command-menu-item/display/utils/interpolateCommandMenuItemFields'; +import { useCommandMenuItemClick } from '@/command-menu-item/hooks/useCommandMenuItemClick'; +import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel'; +import { CommandMenuButton } from '@/command-menu/components/CommandMenuButton'; +import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +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 { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; +import { useContext } from 'react'; +import { assertUnreachable, isDefined } from 'twenty-shared/utils'; +import { useIcons } from 'twenty-ui/display'; +import { Loader } from 'twenty-ui/feedback'; +import { MenuItem } from 'twenty-ui/navigation'; +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; + +type CommandMenuItemRendererProps = { + item: CommandMenuItemFieldsFragment; +}; + +type CommandMenuItemButtonRendererProps = CommandMenuItemRendererProps; + +const CommandMenuItemButtonRenderer = ({ + item, +}: CommandMenuItemButtonRendererProps) => { + const { commandMenuContextApi } = useContext(CommandMenuContext); + const { getIcon } = useIcons(); + + const { iconKey, label, shortLabel } = interpolateCommandMenuItemFields( + item, + commandMenuContextApi, + ); + + const Icon = getIcon(iconKey, COMMAND_MENU_DEFAULT_ICON); + + const { handleClick, disabled } = useCommandMenuItemClick({ + item, + Icon, + label, + }); + + return ( + + ); +}; + +const CommandMenuItemSelectableRenderer = ({ + item, + displayType, +}: CommandMenuItemRendererProps & { + displayType: 'listItem' | 'dropdownItem'; +}) => { + const { commandMenuContextApi } = useContext(CommandMenuContext); + const { getIcon } = useIcons(); + + const { iconKey, label } = interpolateCommandMenuItemFields( + item, + commandMenuContextApi, + ); + + const Icon = getIcon(iconKey, COMMAND_MENU_DEFAULT_ICON); + + const { handleClick, disabled, progress, showDisabledLoader } = + useCommandMenuItemClick({ item, Icon, label }); + + const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow( + SelectableListComponentInstanceContext, + ); + + const isSelectedItemId = useAtomComponentFamilyStateValue( + isSelectedItemIdComponentFamilyState, + item.id, + selectableListInstanceId, + ); + + const onItemClick = () => { + if (disabled) { + return; + } + handleClick(); + }; + + if (displayType === 'listItem') { + const loaderComponent = + disabled && showDisabledLoader ? ( + isDefined(progress) ? ( + + ) : ( + + ) + ) : undefined; + + return ( + + + + ); + } + + return ( + + + + ); +}; + +// oxlint-disable-next-line twenty/effect-components +export const CommandMenuItemRenderer = ({ + item, +}: CommandMenuItemRendererProps) => { + const { displayType } = useContext(CommandMenuContext); + + if (displayType === 'button') { + return ; + } + + if (displayType === 'listItem' || displayType === 'dropdownItem') { + return ( + + ); + } + + return assertUnreachable(displayType, 'Unsupported display type'); +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandModal.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandModal.tsx deleted file mode 100644 index cb934bb80d..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandModal.tsx +++ /dev/null @@ -1,79 +0,0 @@ -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; - 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 ( - <> - - {isModalOpened && ( - - )} - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/FrontComponentCommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/FrontComponentCommandMenuItem.tsx deleted file mode 100644 index 13fb5a0f2e..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/FrontComponentCommandMenuItem.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext'; -import { Command } from '@/command-menu-item/display/components/Command'; -import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; -import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector'; -import { useOpenFrontComponentInSidePanel } from '@/side-panel/hooks/useOpenFrontComponentInSidePanel'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useContext } from 'react'; -import { isDefined } from 'twenty-shared/utils'; - -export const FrontComponentCommandMenuItem = ({ - frontComponentId, -}: { - frontComponentId: string; -}) => { - const { openFrontComponentInSidePanel } = useOpenFrontComponentInSidePanel(); - const commandMenuItemConfig = useContext(CommandConfigContext); - - const contextStoreTargetedRecordsRule = useAtomComponentStateValue( - contextStoreTargetedRecordsRuleComponentState, - ); - - const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue( - contextStoreCurrentObjectMetadataItemIdComponentState, - ); - - const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector); - - const currentObjectMetadataItem = objectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.id === contextStoreCurrentObjectMetadataItemId, - ); - - const selectedRecordIds = - contextStoreTargetedRecordsRule.mode === 'selection' - ? contextStoreTargetedRecordsRule.selectedRecordIds - : []; - - const recordId = - selectedRecordIds.length === 1 ? selectedRecordIds[0] : undefined; - - const objectNameSingular = currentObjectMetadataItem?.nameSingular; - - const displayLabel = - typeof commandMenuItemConfig?.label === 'string' - ? commandMenuItemConfig.label - : ''; - - const Icon = commandMenuItemConfig?.Icon; - - const handleClick = () => { - if (!isDefined(Icon)) { - return; - } - - openFrontComponentInSidePanel({ - frontComponentId, - pageTitle: displayLabel, - pageIcon: Icon, - recordContext: - isDefined(recordId) && isDefined(objectNameSingular) - ? { recordId, objectNameSingular } - : undefined, - }); - }; - - return ; -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx deleted file mode 100644 index 1527a5bd2f..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/HeadlessCommandMenuItem.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useContext } from 'react'; - -import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext'; -import { useMountCommand } from '@/command-menu-item/engine-command/hooks/useMountCommand'; -import { isEngineCommandMountedFamilySelector } from '@/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector'; -import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu'; -import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; -import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; -import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; -import { isDefined } from 'twenty-shared/utils'; -import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; - -import { CommandMenuItemDisplay } from './CommandMenuItemDisplay'; - -export const HeadlessCommandMenuItem = ({ - item, -}: { - item: CommandMenuItemFieldsFragment; -}) => { - const commandMenuItemConfig = useContext(CommandConfigContext); - const mountCommand = useMountCommand(); - - const contextStoreInstanceId = useAvailableComponentInstanceIdOrThrow( - ContextStoreComponentInstanceContext, - ); - - const isMounted = useAtomFamilySelectorValue( - isEngineCommandMountedFamilySelector, - item.id, - ); - - const { closeCommandMenu } = useCloseCommandMenu({ - closeSidePanelOnShowPageOptionsExecution: false, - closeSidePanelOnCommandMenuListExecution: false, - }); - - const commandMenuItemProgress = useAtomFamilyStateValue( - commandMenuItemProgressFamilyState, - item.id, - ); - - if (!isDefined(commandMenuItemConfig)) { - return null; - } - - const handleClick = async () => { - if (isMounted) { - return; - } - - closeCommandMenu(); - - await mountCommand({ - engineCommandId: item.id, - contextStoreInstanceId, - engineComponentKey: item.engineComponentKey, - frontComponentId: item.frontComponentId ?? undefined, - workflowVersionId: item.workflowVersionId ?? undefined, - availabilityType: item.availabilityType, - availabilityObjectMetadataId: item.availabilityObjectMetadataId, - payload: item.payload ?? undefined, - }); - }; - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/PinnedCommandMenuItemButtons.tsx similarity index 61% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons.tsx rename to packages/twenty-front/src/modules/command-menu-item/display/components/PinnedCommandMenuItemButtons.tsx index 5d9e0b6ac6..eaf9a9fd8c 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/display/components/PinnedCommandMenuItemButtons.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/PinnedCommandMenuItemButtons.tsx @@ -1,15 +1,15 @@ -import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent'; +import { CommandMenuItemRenderer } from '@/command-menu-item/display/components/CommandMenuItemRenderer'; import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; -import { PinnedCommandMenuItemsInlineMeasurements } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemsInlineMeasurements'; -import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/server-items/display/constants/PinnedCommandMenuItemsGap'; -import { usePinnedCommandMenuItemsInlineLayout } from '@/command-menu-item/server-items/display/hooks/usePinnedCommandMenuItemsInlineLayout'; +import { PinnedCommandMenuItemsInlineMeasurements } from '@/command-menu-item/display/components/PinnedCommandMenuItemsInlineMeasurements'; +import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/display/constants/PinnedCommandMenuItemsGap'; +import { usePinnedCommandMenuItemsInlineLayout } from '@/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout'; import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension'; import { styled } from '@linaria/react'; import { motion } from 'framer-motion'; import { useContext, useMemo } from 'react'; import { ThemeContext } from 'twenty-ui/theme-constants'; -const StyledActionContainer = styled(motion.div)` +const StyledCommandMenuItemContainer = styled(motion.div)` align-items: center; display: flex; justify-content: center; @@ -40,10 +40,7 @@ export const PinnedCommandMenuItemButtons = () => { const { commandMenuItems } = useContext(CommandMenuContext); const pinnedCommandMenuItems = useMemo( - () => - commandMenuItems.filter( - (commandMenuItem) => commandMenuItem.isPinned === true, - ), + () => commandMenuItems.filter((item) => item.isPinned === true), [commandMenuItems], ); @@ -71,25 +68,21 @@ export const PinnedCommandMenuItemButtons = () => { - {pinnedInlineCommandMenuItems.map( - (pinnedInlineCommandMenuItem) => ( - - - - ), - )} + {pinnedInlineCommandMenuItems.map((item) => ( + + + + ))} diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx new file mode 100644 index 0000000000..bffba1d45f --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx @@ -0,0 +1,68 @@ +import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; +import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/display/constants/PinnedCommandMenuItemsGap'; +import { interpolateCommandMenuItemFields } from '@/command-menu-item/display/utils/interpolateCommandMenuItemFields'; +import { CommandMenuButton } from '@/command-menu/components/CommandMenuButton'; +import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension'; +import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; +import { styled } from '@linaria/react'; +import { useContext } from 'react'; +import { useIcons } from 'twenty-ui/display'; +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; + +type ElementDimensions = { + width: number; + height: number; +}; + +type PinnedCommandMenuItemsInlineMeasurementsProps = { + pinnedCommandMenuItems: CommandMenuItemFieldsFragment[]; + onPinnedCommandMenuItemDimensionChange: ( + commandMenuItemKey: string, + ) => (dimensions: ElementDimensions) => void; +}; + +const StyledHiddenMeasurementsContainer = styled.div` + display: flex; + gap: ${PINNED_COMMAND_MENU_ITEMS_GAP}px; + pointer-events: none; + position: absolute; + top: -9999px; + visibility: hidden; +`; + +export const PinnedCommandMenuItemsInlineMeasurements = ({ + pinnedCommandMenuItems, + onPinnedCommandMenuItemDimensionChange, +}: PinnedCommandMenuItemsInlineMeasurementsProps) => { + const { getIcon } = useIcons(); + const { commandMenuContextApi } = useContext(CommandMenuContext); + + return ( + + {pinnedCommandMenuItems.map((item) => { + const { iconKey, label, shortLabel } = interpolateCommandMenuItemFields( + item, + commandMenuContextApi, + ); + + const Icon = getIcon(iconKey, COMMAND_MENU_DEFAULT_ICON); + + return ( + + + + ); + })} + + ); +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/components/SidePanelCommandMenuItemDisplayPage.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage.tsx similarity index 54% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/components/SidePanelCommandMenuItemDisplayPage.tsx rename to packages/twenty-front/src/modules/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage.tsx index 60af8d31b3..eb28c1b03f 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/display/components/SidePanelCommandMenuItemDisplayPage.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/SidePanelCommandMenuItemDisplayPage.tsx @@ -1,64 +1,64 @@ import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; -import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/server-items/display/constants/PinnedCommandMenuItemsGap'; -import { commandMenuPinnedInlineLayoutState } from '@/command-menu-item/server-items/display/states/commandMenuPinnedInlineLayoutState'; -import { getVisibleCommandMenuItemCountForContainerWidth } from '@/command-menu-item/server-items/display/utils/getVisibleCommandMenuItemCountForContainerWidth'; -import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType'; +import { CommandMenuItemRenderer } from '@/command-menu-item/display/components/CommandMenuItemRenderer'; +import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/display/constants/PinnedCommandMenuItemsGap'; +import { commandMenuPinnedInlineLayoutState } from '@/command-menu-item/display/states/commandMenuPinnedInlineLayoutState'; +import { getVisibleCommandMenuItemCountForContainerWidth } from '@/command-menu-item/display/utils/getVisibleCommandMenuItemCountForContainerWidth'; +import { groupCommandMenuItems } from '@/command-menu-item/utils/groupCommandMenuItems'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; import { SidePanelList } from '@/side-panel/components/SidePanelList'; import { SIDE_PANEL_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelPreviousComponentInstanceId'; import { SIDE_PANEL_RESET_CONTEXT_TO_SELECTION } from '@/side-panel/constants/SidePanelResetContextToSelection'; import { SidePanelResetContextToSelectionButton } from '@/side-panel/pages/root/components/SidePanelResetContextToSelectionButton'; -import { useFilterActionsWithSidePanelSearch } from '@/side-panel/pages/root/hooks/useFilterActionsWithSidePanelSearch'; +import { useFilterCommandMenuItemsWithSidePanelSearch } from '@/side-panel/pages/root/hooks/useFilterCommandMenuItemsWithSidePanelSearch'; import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState'; -import { type SidePanelCommandMenuItemGroupConfig } from '@/side-panel/types/SidePanelCommandMenuItemGroupConfig'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useLingui } from '@lingui/react/macro'; import { isNumber } from '@sniptt/guards'; -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import { isDefined } from 'twenty-shared/utils'; +import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql'; export const SidePanelCommandMenuItemDisplayPage = () => { const { t } = useLingui(); const sidePanelSearch = useAtomStateValue(sidePanelSearchState); - const { commandMenuItems } = useContext(CommandMenuContext); + const { commandMenuItems, commandMenuContextApi } = + useContext(CommandMenuContext); const commandMenuPinnedInlineLayout = useAtomStateValue( commandMenuPinnedInlineLayoutState, ); - const { filterActionsWithSidePanelSearch } = - useFilterActionsWithSidePanelSearch({ + const { filterCommandMenuItemsWithSidePanelSearch } = + useFilterCommandMenuItemsWithSidePanelSearch({ sidePanelSearch, + commandMenuContextApi, }); - const pinnedCommandMenuItems = commandMenuItems - .filter((commandMenuItem) => commandMenuItem.isPinned === true) - .sort( - (firstPinnedCommandMenuItem, secondPinnedCommandMenuItem) => - firstPinnedCommandMenuItem.position - - secondPinnedCommandMenuItem.position, - ); + const { pinned: pinnedCommandMenuItems, other: nonPinnedCommandMenuItems } = + useMemo(() => groupCommandMenuItems(commandMenuItems), [commandMenuItems]); - const unpinnedCommandMenuItems = commandMenuItems - .filter( - (commandMenuItem) => - commandMenuItem.isPinned !== true && - commandMenuItem.type !== CommandMenuItemType.Fallback, - ) - .sort( - (firstUnpinnedCommandMenuItem, secondUnpinnedCommandMenuItem) => - firstUnpinnedCommandMenuItem.position - - secondUnpinnedCommandMenuItem.position, - ); + const unpinnedCommandMenuItems = useMemo( + () => + nonPinnedCommandMenuItems.filter( + (item) => + item.availabilityType !== CommandMenuItemAvailabilityType.FALLBACK, + ), + [nonPinnedCommandMenuItems], + ); - const fallbackCommandMenuItems = commandMenuItems.filter( - (commandMenuItem) => commandMenuItem.type === CommandMenuItemType.Fallback, + const fallbackCommandMenuItems = useMemo( + () => + nonPinnedCommandMenuItems.filter( + (item) => + item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK, + ), + [nonPinnedCommandMenuItems], ); const pinnedCommandMenuItemKeysInDisplayOrder = pinnedCommandMenuItems.map( - (pinnedCommandMenuItem) => pinnedCommandMenuItem.key, + (item) => item.id, ); const visiblePinnedCommandMenuItemCount = @@ -74,11 +74,9 @@ export const SidePanelCommandMenuItemDisplayPage = () => { const hasKnownPinnedInlineLayout = commandMenuPinnedInlineLayout.containerWidth > 0 && - pinnedCommandMenuItemKeysInDisplayOrder.every((commandMenuItemKey) => + pinnedCommandMenuItemKeysInDisplayOrder.every((itemKey) => isNumber( - commandMenuPinnedInlineLayout.commandMenuItemWidthsByKey[ - commandMenuItemKey - ], + commandMenuPinnedInlineLayout.commandMenuItemWidthsByKey[itemKey], ), ); @@ -86,32 +84,20 @@ export const SidePanelCommandMenuItemDisplayPage = () => { ? pinnedCommandMenuItems.slice(visiblePinnedCommandMenuItemCount) : pinnedCommandMenuItems; - const matchingPinnedItems = filterActionsWithSidePanelSearch( + const matchingPinnedItems = filterCommandMenuItemsWithSidePanelSearch( pinnedOverflowCommandMenuItems, ); - const matchingOtherItems = filterActionsWithSidePanelSearch( + const matchingOtherItems = filterCommandMenuItemsWithSidePanelSearch( unpinnedCommandMenuItems, ); const noResults = !matchingPinnedItems.length && !matchingOtherItems.length; - const commandGroups: SidePanelCommandMenuItemGroupConfig[] = [ - { - heading: t`Pinned`, - items: matchingPinnedItems, - }, - { - heading: t`Other`, - items: matchingOtherItems, - }, - { - heading: t`Fallback`, - items: noResults ? fallbackCommandMenuItems : [], - }, - ]; - - const selectableItems = commandGroups.flatMap((group) => group.items ?? []); - const selectableItemIds = selectableItems.map((item) => item.key); + const selectableItemIds = [ + ...matchingPinnedItems, + ...matchingOtherItems, + ...(noResults ? fallbackCommandMenuItems : []), + ].map((item) => item.id); // oxlint-disable-next-line twenty/matching-state-variable const previousContextStoreCurrentObjectMetadataItemId = @@ -125,16 +111,33 @@ export const SidePanelCommandMenuItemDisplayPage = () => { } return ( - + {isDefined(previousContextStoreCurrentObjectMetadataItemId) && ( )} + {matchingPinnedItems.length > 0 && ( + + {matchingPinnedItems.map((item) => ( + + ))} + + )} + {matchingOtherItems.length > 0 && ( + + {matchingOtherItems.map((item) => ( + + ))} + + )} + {noResults && fallbackCommandMenuItems.length > 0 && ( + + {fallbackCommandMenuItems.map((item) => ( + + ))} + + )} ); }; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx deleted file mode 100644 index 82f2063c02..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemButton.stories.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { type Meta, type StoryObj } from '@storybook/react-vite'; -import { expect, fn, userEvent, within } from 'storybook/test'; -import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; - -import { CommandMenuItemButton } from '@/command-menu-item/display/components/CommandMenuItemButton'; -import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock'; -import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel'; -import { EngineComponentKey } from '~/generated-metadata/graphql'; - -const meta: Meta = { - title: 'Modules/CommandMenuItem/Display/CommandMenuItemButton', - component: CommandMenuItemButton, - decorators: [ComponentDecorator, RouterDecorator], -}; - -export default meta; - -type Story = StoryObj; - -const deleteMock = fn(); -const addToFavoritesMock = fn(); - -const mockActions = createMockCommandMenuItems({ - deleteMock, - addToFavoritesMock, -}); - -const addToFavoritesCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES, -); - -const goToPeopleCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.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'); - }, -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx deleted file mode 100644 index 81d39cb370..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemComponent.stories.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { type Meta, type StoryObj } from '@storybook/react-vite'; -import { expect, within } from 'storybook/test'; -import { ComponentDecorator } from 'twenty-ui/testing'; - -import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; -import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent'; -import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock'; -import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel'; -import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext'; -import { EngineComponentKey } from '~/generated-metadata/graphql'; - -const mockActions = createMockCommandMenuItems({}); - -const addToFavoritesCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES, -); - -if (!addToFavoritesCommandMenuItem) { - throw new Error('Add to favorites action not found'); -} - -const meta: Meta = { - title: 'Modules/CommandMenuItem/Display/CommandMenuItemComponent', - component: CommandMenuItemComponent, - decorators: [ - ComponentDecorator, - (Story) => ( - - - - - - ), - ], - args: { - action: addToFavoritesCommandMenuItem, - }, - parameters: { - container: { - width: 'auto', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - expect( - await canvas.findByText( - getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.shortLabel), - ), - ).toBeVisible(); - }, -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx deleted file mode 100644 index cb0973d104..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDisplay.stories.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { type Meta, type StoryObj } from '@storybook/react-vite'; -import { expect, fn, userEvent, within } from 'storybook/test'; -import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; - -import { CommandConfigContext } from '@/command-menu-item/contexts/CommandConfigContext'; -import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; -import { CommandMenuItemDisplay } from '@/command-menu-item/display/components/CommandMenuItemDisplay'; -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 { EngineComponentKey } from '~/generated-metadata/graphql'; - -type Story = StoryObj; - -const deleteMock = fn(); -const addToFavoritesMock = fn(); - -const mockActions = createMockCommandMenuItems({ - deleteMock, - addToFavoritesMock, -}); - -const addToFavoritesCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES, -); - -if (!addToFavoritesCommandMenuItem) { - throw new Error('addToFavoritesCommandMenuItem not found'); -} - -const meta: Meta = { - title: 'Modules/CommandMenuItem/Display/CommandMenuItemDisplay', - component: CommandMenuItemDisplay, - decorators: [ - (Story) => ( - - - - ), - ComponentDecorator, - RouterDecorator, - ], -}; - -export default meta; - -export const AsButton: Story = { - args: { - onClick: addToFavoritesMock, - }, - decorators: [ - (Story) => ( - - - - ), - ], - 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) => ( - - - - ), - (Story) => ( - - - - ), - ], - 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) => ( - - - - ), - (Story) => ( - - - - ), - ], - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - await userEvent.click( - await canvas.findByText( - getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label), - ), - ); - expect(addToFavoritesMock).toHaveBeenCalled(); - }, -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx deleted file mode 100644 index 23d5711daf..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemDropdownItem.stories.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { type Meta, type StoryObj } from '@storybook/react-vite'; -import { expect, fn, userEvent, within } from 'storybook/test'; -import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; - -import { CommandDropdownItem } from '@/command-menu-item/display/components/CommandDropdownItem'; -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 { EngineComponentKey } from '~/generated-metadata/graphql'; - -const meta: Meta = { - title: 'Modules/CommandMenuItem/Display/CommandDropdownItem', - component: CommandDropdownItem, - decorators: [ - (Story) => ( - - - - ), - ComponentDecorator, - RouterDecorator, - ], -}; - -export default meta; - -type Story = StoryObj; - -const deleteMock = fn(); -const addToFavoritesMock = fn(); - -const mockActions = createMockCommandMenuItems({ - deleteMock, - addToFavoritesMock, -}); - -const addToFavoritesCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES, -); - -const goToPeopleCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.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(); - }, -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx deleted file mode 100644 index cd55087b5b..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/__stories__/CommandMenuItemListItem.stories.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { type Meta, type StoryObj } from '@storybook/react-vite'; -import { expect, fn, userEvent, within } from 'storybook/test'; -import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; - -import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem'; -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 { EngineComponentKey } from '~/generated-metadata/graphql'; - -type Story = StoryObj; - -const deleteMock = fn(); -const addToFavoritesMock = fn(); - -const mockActions = createMockCommandMenuItems({ - deleteMock, - addToFavoritesMock, -}); - -const addToFavoritesCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.ADD_TO_FAVORITES, -); - -const goToPeopleCommandMenuItem = mockActions.find( - (action) => action.key === EngineComponentKey.GO_TO_PEOPLE, -); - -const meta: Meta = { - title: 'Modules/CommandMenuItem/Display/CommandListItem', - component: CommandListItem, - decorators: [ - (Story) => ( - - - - ), - 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(); - }, -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/constants/PinnedCommandMenuItemsGap.ts b/packages/twenty-front/src/modules/command-menu-item/display/constants/PinnedCommandMenuItemsGap.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/constants/PinnedCommandMenuItemsGap.ts rename to packages/twenty-front/src/modules/command-menu-item/display/constants/PinnedCommandMenuItemsGap.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts b/packages/twenty-front/src/modules/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts similarity index 70% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts rename to packages/twenty-front/src/modules/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts index c2eac684bf..c8fe027876 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts +++ b/packages/twenty-front/src/modules/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts @@ -1,9 +1,9 @@ -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/server-items/display/constants/PinnedCommandMenuItemsGap'; -import { commandMenuPinnedInlineLayoutState } from '@/command-menu-item/server-items/display/states/commandMenuPinnedInlineLayoutState'; -import { getVisibleCommandMenuItemCountForContainerWidth } from '@/command-menu-item/server-items/display/utils/getVisibleCommandMenuItemCountForContainerWidth'; +import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/display/constants/PinnedCommandMenuItemsGap'; +import { commandMenuPinnedInlineLayoutState } from '@/command-menu-item/display/states/commandMenuPinnedInlineLayoutState'; +import { getVisibleCommandMenuItemCountForContainerWidth } from '@/command-menu-item/display/utils/getVisibleCommandMenuItemCountForContainerWidth'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useCallback, useMemo } from 'react'; +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; type ElementDimensions = { width: number; @@ -11,7 +11,7 @@ type ElementDimensions = { }; type UsePinnedCommandMenuItemsInlineLayoutParams = { - pinnedCommandMenuItems: CommandMenuItemConfig[]; + pinnedCommandMenuItems: CommandMenuItemFieldsFragment[]; }; export const usePinnedCommandMenuItemsInlineLayout = ({ @@ -20,22 +20,9 @@ export const usePinnedCommandMenuItemsInlineLayout = ({ const [commandMenuPinnedInlineLayout, setCommandMenuPinnedInlineLayout] = useAtomState(commandMenuPinnedInlineLayoutState); - const pinnedCommandMenuItemsSortedByPosition = useMemo( - () => - [...pinnedCommandMenuItems].sort( - (firstPinnedCommandMenuItem, secondPinnedCommandMenuItem) => - firstPinnedCommandMenuItem.position - - secondPinnedCommandMenuItem.position, - ), - [pinnedCommandMenuItems], - ); - const pinnedCommandMenuItemKeysInDisplayOrder = useMemo( - () => - pinnedCommandMenuItemsSortedByPosition.map( - (pinnedCommandMenuItem) => pinnedCommandMenuItem.key, - ), - [pinnedCommandMenuItemsSortedByPosition], + () => pinnedCommandMenuItems.map((item) => item.id), + [pinnedCommandMenuItems], ); const visiblePinnedCommandMenuItemCount = useMemo( @@ -53,20 +40,13 @@ export const usePinnedCommandMenuItemsInlineLayout = ({ ); const pinnedInlineCommandMenuItems = useMemo( - () => - pinnedCommandMenuItemsSortedByPosition.slice( - 0, - visiblePinnedCommandMenuItemCount, - ), - [pinnedCommandMenuItemsSortedByPosition, visiblePinnedCommandMenuItemCount], + () => pinnedCommandMenuItems.slice(0, visiblePinnedCommandMenuItemCount), + [pinnedCommandMenuItems, visiblePinnedCommandMenuItemCount], ); const pinnedOverflowCommandMenuItems = useMemo( - () => - pinnedCommandMenuItemsSortedByPosition.slice( - visiblePinnedCommandMenuItemCount, - ), - [pinnedCommandMenuItemsSortedByPosition, visiblePinnedCommandMenuItemCount], + () => pinnedCommandMenuItems.slice(visiblePinnedCommandMenuItemCount), + [pinnedCommandMenuItems, visiblePinnedCommandMenuItemCount], ); const onContainerDimensionChange = useCallback( diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/states/commandMenuPinnedInlineLayoutState.ts b/packages/twenty-front/src/modules/command-menu-item/display/states/commandMenuPinnedInlineLayoutState.ts similarity index 85% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/states/commandMenuPinnedInlineLayoutState.ts rename to packages/twenty-front/src/modules/command-menu-item/display/states/commandMenuPinnedInlineLayoutState.ts index 865435e946..4435868042 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/display/states/commandMenuPinnedInlineLayoutState.ts +++ b/packages/twenty-front/src/modules/command-menu-item/display/states/commandMenuPinnedInlineLayoutState.ts @@ -1,4 +1,4 @@ -import { type CommandMenuPinnedInlineLayout } from '@/command-menu-item/server-items/display/types/CommandMenuPinnedInlineLayout'; +import { type CommandMenuPinnedInlineLayout } from '@/command-menu-item/display/types/CommandMenuPinnedInlineLayout'; import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; export const commandMenuPinnedInlineLayoutState = diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/types/CommandMenuPinnedInlineLayout.ts b/packages/twenty-front/src/modules/command-menu-item/display/types/CommandMenuPinnedInlineLayout.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/types/CommandMenuPinnedInlineLayout.ts rename to packages/twenty-front/src/modules/command-menu-item/display/types/CommandMenuPinnedInlineLayout.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/utils/__tests__/getVisibleCommandMenuItemCountForContainerWidth.test.ts b/packages/twenty-front/src/modules/command-menu-item/display/utils/__tests__/getVisibleCommandMenuItemCountForContainerWidth.test.ts similarity index 93% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/utils/__tests__/getVisibleCommandMenuItemCountForContainerWidth.test.ts rename to packages/twenty-front/src/modules/command-menu-item/display/utils/__tests__/getVisibleCommandMenuItemCountForContainerWidth.test.ts index ae2b7a0d80..1bfcf7e8f1 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/display/utils/__tests__/getVisibleCommandMenuItemCountForContainerWidth.test.ts +++ b/packages/twenty-front/src/modules/command-menu-item/display/utils/__tests__/getVisibleCommandMenuItemCountForContainerWidth.test.ts @@ -1,4 +1,4 @@ -import { getVisibleCommandMenuItemCountForContainerWidth } from '@/command-menu-item/server-items/display/utils/getVisibleCommandMenuItemCountForContainerWidth'; +import { getVisibleCommandMenuItemCountForContainerWidth } from '@/command-menu-item/display/utils/getVisibleCommandMenuItemCountForContainerWidth'; describe('getVisibleCommandMenuItemCountForContainerWidth', () => { it('should return all items when container width is zero', () => { diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/utils/getVisibleCommandMenuItemCountForContainerWidth.ts b/packages/twenty-front/src/modules/command-menu-item/display/utils/getVisibleCommandMenuItemCountForContainerWidth.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/display/utils/getVisibleCommandMenuItemCountForContainerWidth.ts rename to packages/twenty-front/src/modules/command-menu-item/display/utils/getVisibleCommandMenuItemCountForContainerWidth.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/display/utils/interpolateCommandMenuItemFields.ts b/packages/twenty-front/src/modules/command-menu-item/display/utils/interpolateCommandMenuItemFields.ts new file mode 100644 index 0000000000..ee866f1604 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/display/utils/interpolateCommandMenuItemFields.ts @@ -0,0 +1,32 @@ +import { type CommandMenuContextApi, type Nullable } from 'twenty-shared/types'; +import { interpolateCommandMenuItemTemplate } from 'twenty-shared/utils'; +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; + +type InterpolatedCommandMenuItemFields = { + iconKey: Nullable; + label: string; + shortLabel: Nullable; +}; + +export const interpolateCommandMenuItemFields = ( + item: CommandMenuItemFieldsFragment, + commandMenuContextApi: CommandMenuContextApi, +): InterpolatedCommandMenuItemFields => { + const iconKey = interpolateCommandMenuItemTemplate({ + label: item.icon, + context: commandMenuContextApi, + }); + + const label = + interpolateCommandMenuItemTemplate({ + label: item.label, + context: commandMenuContextApi, + }) ?? item.label; + + const shortLabel = interpolateCommandMenuItemTemplate({ + label: item.shortLabel, + context: commandMenuContextApi, + }); + + return { iconKey, label, shortLabel }; +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditButton.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditButton.tsx similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditButton.tsx rename to packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditButton.tsx diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx similarity index 98% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx rename to packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx index 9fac580da4..93d2b4d67a 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx @@ -1,5 +1,5 @@ import { COMMAND_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/command-menu-item/constants/CommandMenuDropdownClickOutsideId'; -import { useSelectFirstRecordForEditMode } from '@/command-menu-item/server-items/edit/hooks/useSelectFirstRecordForEditMode'; +import { useSelectFirstRecordForEditMode } from '@/command-menu-item/edit/hooks/useSelectFirstRecordForEditMode'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { mainContextStoreHasSelectedRecordsSelector } from '@/context-store/states/selectors/mainContextStoreHasSelectedRecordsSelector'; import { useResetRecordIndexSelection } from '@/object-record/record-index/hooks/useResetRecordIndexSelection'; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemOptionsDropdown.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemOptionsDropdown.tsx similarity index 98% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemOptionsDropdown.tsx rename to packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemOptionsDropdown.tsx index 04007cc38a..7b856601fb 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/CommandMenuItemOptionsDropdown.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemOptionsDropdown.tsx @@ -1,4 +1,4 @@ -import { useUpdateCommandMenuItemInDraft } from '@/command-menu-item/server-items/edit/hooks/useUpdateCommandMenuItemInDraft'; +import { useUpdateCommandMenuItemInDraft } from '@/command-menu-item/edit/hooks/useUpdateCommandMenuItemInDraft'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx similarity index 57% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx rename to packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx index 06661556da..abf54726c7 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx @@ -1,26 +1,23 @@ -import { useCommandMenuContextApi } from '@/command-menu-item/server-items/common/hooks/useCommandMenuContextApi'; -import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/server-items/common/utils/doesCommandMenuItemMatchObjectMetadataId'; -import { PinnedCommandMenuItemsInlineMeasurements } from '@/command-menu-item/server-items/display/components/PinnedCommandMenuItemsInlineMeasurements'; -import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/server-items/display/constants/PinnedCommandMenuItemsGap'; -import { usePinnedCommandMenuItemsInlineLayout } from '@/command-menu-item/server-items/display/hooks/usePinnedCommandMenuItemsInlineLayout'; -import { mainContextStoreHasSelectedRecordsSelector } from '@/context-store/states/selectors/mainContextStoreHasSelectedRecordsSelector'; -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope'; -import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType'; +import { PinnedCommandMenuItemsInlineMeasurements } from '@/command-menu-item/display/components/PinnedCommandMenuItemsInlineMeasurements'; +import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/display/constants/PinnedCommandMenuItemsGap'; +import { usePinnedCommandMenuItemsInlineLayout } from '@/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout'; +import { interpolateCommandMenuItemFields } from '@/command-menu-item/display/utils/interpolateCommandMenuItemFields'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; +import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi'; +import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId'; import { CommandMenuButton } from '@/command-menu/components/CommandMenuButton'; +import { mainContextStoreHasSelectedRecordsSelector } from '@/context-store/states/selectors/mainContextStoreHasSelectedRecordsSelector'; import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; import { styled } from '@linaria/react'; import { motion } from 'framer-motion'; import { useContext, useMemo } from 'react'; -import { interpolateCommandMenuItemTemplate } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; import { ThemeContext } from 'twenty-ui/theme-constants'; import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql'; -const StyledActionContainer = styled(motion.div)` +const StyledCommandMenuItemContainer = styled(motion.div)` align-items: center; display: flex; justify-content: center; @@ -72,52 +69,12 @@ export const PinnedCommandMenuItemButtonsEditMode = () => { [mainContextStoreHasSelectedRecords], ); - const interpolateLabel = (rawLabel: string | null | undefined) => - interpolateCommandMenuItemTemplate({ - label: rawLabel, - context: commandMenuContextApi, - }); - - const pinnedCommandMenuItems: CommandMenuItemConfig[] = useMemo( - () => - commandMenuItemsDraft - .filter( - doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId), - ) - .filter((item) => allowedAvailabilityTypes.has(item.availabilityType)) - .filter((item) => item.isPinned) - .map((item) => { - const Icon = getIcon(item.icon, COMMAND_MENU_DEFAULT_ICON); - const label = interpolateLabel(item.label) ?? item.label; - const shortLabel = interpolateLabel(item.shortLabel); - const key = `edit-preview-${item.id}`; - - return { - type: CommandMenuItemType.Standard, - scope: CommandMenuItemScope.Global, - key, - label, - shortLabel, - position: item.position, - Icon, - isPinned: true, - shouldBeRegistered: () => true, - component: ( - - ), - }; - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - currentObjectMetadataItemId, - allowedAvailabilityTypes, - commandMenuContextApi, - getIcon, - ], - ); + const pinnedCommandMenuItems = commandMenuItemsDraft + .filter( + doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId), + ) + .filter((item) => allowedAvailabilityTypes.has(item.availabilityType)) + .filter((item) => item.isPinned); const { pinnedInlineCommandMenuItems, @@ -143,10 +100,15 @@ export const PinnedCommandMenuItemButtonsEditMode = () => { - {pinnedInlineCommandMenuItems.map( - (pinnedInlineCommandMenuItem) => ( - { + const { iconKey, label, shortLabel } = + interpolateCommandMenuItemFields(item, commandMenuContextApi); + + const Icon = getIcon(iconKey, COMMAND_MENU_DEFAULT_ICON); + + return ( + { ease: 'easeInOut', }} > - {pinnedInlineCommandMenuItem.component} - - ), - )} + + + ); + })} diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/SidePanelCommandMenuItemEditPage.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx similarity index 90% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/SidePanelCommandMenuItemEditPage.tsx rename to packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx index 1eeed916df..3e33fed9e4 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/components/SidePanelCommandMenuItemEditPage.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx @@ -1,12 +1,13 @@ -import { useCommandMenuContextApi } from '@/command-menu-item/server-items/common/hooks/useCommandMenuContextApi'; -import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector'; -import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/server-items/common/utils/doesCommandMenuItemMatchObjectMetadataId'; -import { CommandMenuItemEditRecordSelectionDropdown } from '@/command-menu-item/server-items/edit/components/CommandMenuItemEditRecordSelectionDropdown'; -import { CommandMenuItemOptionsDropdown } from '@/command-menu-item/server-items/edit/components/CommandMenuItemOptionsDropdown'; -import { useReorderCommandMenuItemsInDraft } from '@/command-menu-item/server-items/edit/hooks/useReorderCommandMenuItemsInDraft'; -import { useResetCommandMenuItemsDraft } from '@/command-menu-item/server-items/edit/hooks/useResetCommandMenuItemsDraft'; -import { useUpdateCommandMenuItemInDraft } from '@/command-menu-item/server-items/edit/hooks/useUpdateCommandMenuItemInDraft'; -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; +import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi'; +import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector'; +import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId'; +import { groupCommandMenuItems } from '@/command-menu-item/utils/groupCommandMenuItems'; +import { CommandMenuItemEditRecordSelectionDropdown } from '@/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown'; +import { CommandMenuItemOptionsDropdown } from '@/command-menu-item/edit/components/CommandMenuItemOptionsDropdown'; +import { useReorderCommandMenuItemsInDraft } from '@/command-menu-item/edit/hooks/useReorderCommandMenuItemsInDraft'; +import { useResetCommandMenuItemsDraft } from '@/command-menu-item/edit/hooks/useResetCommandMenuItemsDraft'; +import { useUpdateCommandMenuItemInDraft } from '@/command-menu-item/edit/hooks/useUpdateCommandMenuItemInDraft'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; import { mainContextStoreHasSelectedRecordsSelector } from '@/context-store/states/selectors/mainContextStoreHasSelectedRecordsSelector'; import { COMMAND_MENU_CLICK_OUTSIDE_ID } from '@/command-menu/constants/CommandMenuClickOutsideId'; import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; @@ -41,17 +42,6 @@ import { } from '~/generated-metadata/graphql'; import { normalizeSearchText } from '~/utils/normalizeSearchText'; -const partitionByPinned = (items: CommandMenuItemFieldsFragment[]) => { - const pinned = items - .filter((item) => item.isPinned) - .sort((a, b) => a.position - b.position); - const other = items - .filter((item) => !item.isPinned) - .sort((a, b) => a.position - b.position); - - return { pinned, other }; -}; - const StyledContainer = styled.div` display: flex; flex-direction: column; @@ -112,7 +102,8 @@ export const SidePanelCommandMenuItemEditPage = () => { .filter( doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId), ) - .filter((item) => allowedAvailabilityTypes.has(item.availabilityType)); + .filter((item) => allowedAvailabilityTypes.has(item.availabilityType)) + .sort((firstItem, secondItem) => firstItem.position - secondItem.position); const filteredCommandMenuItemIds = new Set( filteredCommandMenuItems.map((item) => item.id), @@ -124,9 +115,8 @@ export const SidePanelCommandMenuItemEditPage = () => { context: commandMenuContextApi, }) ?? item.label; - const { pinned: allPinnedItems, other: allOtherItems } = partitionByPinned( - filteredCommandMenuItems, - ); + const { pinned: allPinnedItems, other: allOtherItems } = + groupCommandMenuItems(filteredCommandMenuItems); const normalizedSearch = sidePanelSearch.length > 0 @@ -247,7 +237,7 @@ export const SidePanelCommandMenuItemEditPage = () => { /> - + { const store = useStore(); diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useResetCommandMenuItemsDraft.ts b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useResetCommandMenuItemsDraft.ts similarity index 77% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useResetCommandMenuItemsDraft.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/hooks/useResetCommandMenuItemsDraft.ts index a03bce9fe3..f92da8b35f 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useResetCommandMenuItemsDraft.ts +++ b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useResetCommandMenuItemsDraft.ts @@ -1,8 +1,8 @@ import { useCallback } from 'react'; import { useStore } from 'jotai'; -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; -import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; +import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector'; // Resets the draft to the current server state, discarding all user edits. export const useResetCommandMenuItemsDraft = () => { diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useSaveCommandMenuItemsDraft.ts b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useSaveCommandMenuItemsDraft.ts similarity index 85% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useSaveCommandMenuItemsDraft.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/hooks/useSaveCommandMenuItemsDraft.ts index 556b9bfa73..107642c230 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useSaveCommandMenuItemsDraft.ts +++ b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useSaveCommandMenuItemsDraft.ts @@ -3,9 +3,9 @@ import { useStore } from 'jotai'; import { useMutation } from '@apollo/client/react'; import { isDefined } from 'twenty-shared/utils'; -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; -import { UPDATE_COMMAND_MENU_ITEM } from '@/command-menu-item/server-items/common/graphql/mutations/updateCommandMenuItem'; -import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; +import { UPDATE_COMMAND_MENU_ITEM } from '@/command-menu-item/graphql/mutations/updateCommandMenuItem'; +import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { type UpdateCommandMenuItemInput } from '~/generated-metadata/graphql'; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useSelectFirstRecordForEditMode.ts b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useSelectFirstRecordForEditMode.ts similarity index 95% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useSelectFirstRecordForEditMode.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/hooks/useSelectFirstRecordForEditMode.ts index 7a65f304b4..424cd0a720 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useSelectFirstRecordForEditMode.ts +++ b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useSelectFirstRecordForEditMode.ts @@ -1,5 +1,5 @@ import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; -import { getRecordIndexId } from '@/command-menu-item/server-items/edit/utils/getRecordIndexId'; +import { getRecordIndexId } from '@/command-menu-item/edit/utils/getRecordIndexId'; import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState'; import { useResetRecordIndexSelection } from '@/object-record/record-index/hooks/useResetRecordIndexSelection'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useUpdateCommandMenuItemInDraft.ts b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useUpdateCommandMenuItemInDraft.ts similarity index 87% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useUpdateCommandMenuItemInDraft.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/hooks/useUpdateCommandMenuItemInDraft.ts index c6513eccd6..a68e90ce12 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/hooks/useUpdateCommandMenuItemInDraft.ts +++ b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useUpdateCommandMenuItemInDraft.ts @@ -2,8 +2,8 @@ import { useCallback } from 'react'; import { useStore } from 'jotai'; import { isDefined } from 'twenty-shared/utils'; -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; -import { type CommandMenuItemEditableFields } from '@/command-menu-item/server-items/edit/types/CommandMenuItemEditableFields'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; +import { type CommandMenuItemEditableFields } from '@/command-menu-item/edit/types/CommandMenuItemEditableFields'; export const useUpdateCommandMenuItemInDraft = () => { const store = useStore(); diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/states/commandMenuItemsDraftState.ts b/packages/twenty-front/src/modules/command-menu-item/edit/states/commandMenuItemsDraftState.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/states/commandMenuItemsDraftState.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/states/commandMenuItemsDraftState.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/types/CommandMenuItemEditableFields.ts b/packages/twenty-front/src/modules/command-menu-item/edit/types/CommandMenuItemEditableFields.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/types/CommandMenuItemEditableFields.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/types/CommandMenuItemEditableFields.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/__tests__/computeInsertPositionFromBounds.test.ts b/packages/twenty-front/src/modules/command-menu-item/edit/utils/__tests__/computeInsertPositionFromBounds.test.ts similarity index 95% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/__tests__/computeInsertPositionFromBounds.test.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/utils/__tests__/computeInsertPositionFromBounds.test.ts index f7d54e10aa..a4a30e06ec 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/__tests__/computeInsertPositionFromBounds.test.ts +++ b/packages/twenty-front/src/modules/command-menu-item/edit/utils/__tests__/computeInsertPositionFromBounds.test.ts @@ -1,4 +1,4 @@ -import { computeInsertPositionFromBounds } from '@/command-menu-item/server-items/edit/utils/computeInsertPositionFromBounds'; +import { computeInsertPositionFromBounds } from '@/command-menu-item/edit/utils/computeInsertPositionFromBounds'; describe('computeInsertPositionFromBounds', () => { it('returns midpoint when both bounds are defined', () => { diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/__tests__/getPositionBoundsAtInsertionPoint.test.ts b/packages/twenty-front/src/modules/command-menu-item/edit/utils/__tests__/getPositionBoundsAtInsertionPoint.test.ts similarity index 97% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/__tests__/getPositionBoundsAtInsertionPoint.test.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/utils/__tests__/getPositionBoundsAtInsertionPoint.test.ts index efb826167d..a1cdc8bad2 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/__tests__/getPositionBoundsAtInsertionPoint.test.ts +++ b/packages/twenty-front/src/modules/command-menu-item/edit/utils/__tests__/getPositionBoundsAtInsertionPoint.test.ts @@ -1,4 +1,4 @@ -import { getPositionBoundsAtInsertionPoint } from '@/command-menu-item/server-items/edit/utils/getPositionBoundsAtInsertionPoint'; +import { getPositionBoundsAtInsertionPoint } from '@/command-menu-item/edit/utils/getPositionBoundsAtInsertionPoint'; const makeItems = (positions: number[]) => positions.map((position, index) => ({ diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/computeInsertPositionFromBounds.ts b/packages/twenty-front/src/modules/command-menu-item/edit/utils/computeInsertPositionFromBounds.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/computeInsertPositionFromBounds.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/utils/computeInsertPositionFromBounds.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/getPositionBoundsAtInsertionPoint.ts b/packages/twenty-front/src/modules/command-menu-item/edit/utils/getPositionBoundsAtInsertionPoint.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/getPositionBoundsAtInsertionPoint.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/utils/getPositionBoundsAtInsertionPoint.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/getRecordIndexId.ts b/packages/twenty-front/src/modules/command-menu-item/edit/utils/getRecordIndexId.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/edit/utils/getRecordIndexId.ts rename to packages/twenty-front/src/modules/command-menu-item/edit/utils/getRecordIndexId.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/graphql/mutations/updateCommandMenuItem.ts b/packages/twenty-front/src/modules/command-menu-item/graphql/mutations/updateCommandMenuItem.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/common/graphql/mutations/updateCommandMenuItem.ts rename to packages/twenty-front/src/modules/command-menu-item/graphql/mutations/updateCommandMenuItem.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx index d488230699..1208b26bc6 100644 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx @@ -3,6 +3,7 @@ import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMe import { type CommandMenuItemContainerType } from '@/command-menu-item/types/CommandMenuItemContainerType'; import { act, renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; +import { CommandMenuContextApiPageType } from 'twenty-shared/types'; const TEST_COMMAND_MENU_ID = 'test-cmd-menu-1'; @@ -40,9 +41,33 @@ const getWrapper = {children} diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useIsCommandBlockedByGlobalLayoutCustomization.test.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useIsCommandBlockedByGlobalLayoutCustomization.test.tsx deleted file mode 100644 index 9c05f14243..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useIsCommandBlockedByGlobalLayoutCustomization.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; -import { useIsCommandBlockedByGlobalLayoutCustomization } from '@/command-menu-item/hooks/useIsCommandBlockedByGlobalLayoutCustomization'; -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope'; -import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType'; -import { renderHook } from '@testing-library/react'; -import { createStore, Provider as JotaiProvider } from 'jotai'; -import { type ReactNode } from 'react'; -import { CommandMenuItemViewType } from 'twenty-shared/types'; -import { Icon123 } from 'twenty-ui/display'; - -const getWrapper = - (store = createStore()) => - ({ children }: { children: ReactNode }) => ( - {children} - ); - -const buildCommandMenuItemConfig = ( - isAllowedDuringGlobalLayoutCustomization?: boolean, -): CommandMenuItemConfig => ({ - type: CommandMenuItemType.Standard, - scope: CommandMenuItemScope.Global, - key: 'test-command', - label: 'Test Command', - position: 1, - Icon: Icon123, - availableOn: [CommandMenuItemViewType.GLOBAL], - shouldBeRegistered: () => true, - component: null, - isAllowedDuringGlobalLayoutCustomization, -}); - -describe('useIsCommandBlockedByGlobalLayoutCustomization', () => { - it('should not block commands when global layout customization is inactive', () => { - const store = createStore(); - const wrapper = getWrapper(store); - const commandMenuItemConfig = buildCommandMenuItemConfig(false); - - store.set(isLayoutCustomizationModeEnabledState.atom, false); - - const { result } = renderHook( - () => - useIsCommandBlockedByGlobalLayoutCustomization(commandMenuItemConfig), - { - wrapper, - }, - ); - - expect(result.current).toBe(false); - }); - - it('should block commands by default when global layout customization is active', () => { - const store = createStore(); - const wrapper = getWrapper(store); - const commandMenuItemConfig = buildCommandMenuItemConfig(); - - store.set(isLayoutCustomizationModeEnabledState.atom, true); - - const { result } = renderHook( - () => - useIsCommandBlockedByGlobalLayoutCustomization(commandMenuItemConfig), - { - wrapper, - }, - ); - - expect(result.current).toBe(true); - }); - - it('should allow commands explicitly marked for global layout customization', () => { - const store = createStore(); - const wrapper = getWrapper(store); - const commandMenuItemConfig = buildCommandMenuItemConfig(true); - - store.set(isLayoutCustomizationModeEnabledState.atom, true); - - const { result } = renderHook( - () => - useIsCommandBlockedByGlobalLayoutCustomization(commandMenuItemConfig), - { - wrapper, - }, - ); - - expect(result.current).toBe(false); - }); -}); diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useCloseCommandMenu.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCloseCommandMenu.ts index 1834f69e1f..6e6f756b05 100644 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/useCloseCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCloseCommandMenu.ts @@ -1,12 +1,11 @@ import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; -import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext'; import { getCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getCommandMenuDropdownIdFromCommandMenuId'; import { getSidePanelCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getSidePanelCommandMenuDropdownIdFromCommandMenuId'; +import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useContext } from 'react'; -import { isDefined } from 'twenty-shared/utils'; export const useCloseCommandMenu = ({ closeSidePanelOnShowPageOptionsExecution = false, @@ -15,7 +14,9 @@ export const useCloseCommandMenu = ({ closeSidePanelOnShowPageOptionsExecution?: boolean; closeSidePanelOnCommandMenuListExecution?: boolean; } = {}) => { - const { containerType, isInSidePanel } = useContext(CommandMenuContext); + const { containerType, commandMenuContextApi } = + useContext(CommandMenuContext); + const isInSidePanel = commandMenuContextApi.isInSidePanel; const { closeSidePanelMenu } = useSidePanelMenu(); @@ -31,10 +32,7 @@ export const useCloseCommandMenu = ({ const closeCommandMenu = () => { if (containerType === 'command-menu-list') { - if ( - isDefined(closeSidePanelOnCommandMenuListExecution) && - !closeSidePanelOnCommandMenuListExecution - ) { + if (!closeSidePanelOnCommandMenuListExecution) { return; } closeSidePanelMenu(); @@ -49,7 +47,6 @@ export const useCloseCommandMenu = ({ if ( containerType === 'command-menu-show-page-dropdown' && - isDefined(closeSidePanelOnShowPageOptionsExecution) && closeSidePanelOnShowPageOptionsExecution ) { closeSidePanelMenu(); diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuContextApi.ts rename to packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts new file mode 100644 index 0000000000..dfe85fa421 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemClick.ts @@ -0,0 +1,119 @@ +import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext'; +import { useMountCommand } from '@/command-menu-item/engine-command/hooks/useMountCommand'; +import { isEngineCommandMountedFamilySelector } from '@/command-menu-item/engine-command/selectors/isEngineCommandMountedFamilySelector'; +import { useCloseCommandMenu } from '@/command-menu-item/hooks/useCloseCommandMenu'; +import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState'; +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { useOpenFrontComponentInSidePanel } from '@/side-panel/hooks/useOpenFrontComponentInSidePanel'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; +import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; +import { useContext } from 'react'; +import { isDefined } from 'twenty-shared/utils'; +import { type IconComponent } from 'twenty-ui/display'; +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; + +export const useCommandMenuItemClick = ({ + item, + Icon, + label, +}: { + item: CommandMenuItemFieldsFragment; + Icon: IconComponent; + label: string; +}) => { + const { commandMenuContextApi } = useContext(CommandMenuContext); + const mountCommand = useMountCommand(); + const { openFrontComponentInSidePanel } = useOpenFrontComponentInSidePanel(); + + const contextStoreInstanceId = useAvailableComponentInstanceIdOrThrow( + ContextStoreComponentInstanceContext, + ); + + const isMounted = useAtomFamilySelectorValue( + isEngineCommandMountedFamilySelector, + item.id, + ); + + const commandMenuItemProgress = useAtomFamilyStateValue( + commandMenuItemProgressFamilyState, + item.id, + ); + + const isHeadless = + isDefined(item.frontComponentId) && + item.frontComponent?.isHeadless === true; + + const isEngineCommand = + isDefined(item.engineComponentKey) && !isDefined(item.frontComponentId); + + const isFrontComponent = + isDefined(item.frontComponentId) && + item.frontComponent?.isHeadless !== true; + + const shouldMountCommand = isHeadless || isEngineCommand; + + const closeBehavior = shouldMountCommand + ? ({ + closeSidePanelOnShowPageOptionsExecution: false, + closeSidePanelOnCommandMenuListExecution: false, + } as const) + : ({} as const); + + const { closeCommandMenu } = useCloseCommandMenu(closeBehavior); + + const disabled = shouldMountCommand ? isMounted : false; + + const handleClick = async () => { + if (shouldMountCommand) { + if (isMounted) { + return; + } + + closeCommandMenu(); + + await mountCommand({ + engineCommandId: item.id, + contextStoreInstanceId, + engineComponentKey: item.engineComponentKey, + frontComponentId: item.frontComponentId ?? undefined, + workflowVersionId: item.workflowVersionId ?? undefined, + availabilityType: item.availabilityType, + availabilityObjectMetadataId: item.availabilityObjectMetadataId, + payload: item.payload ?? undefined, + }); + + return; + } + + if (isFrontComponent && isDefined(item.frontComponentId)) { + const { selectedRecords, objectMetadataItem } = commandMenuContextApi; + + const recordId = + selectedRecords.length === 1 ? selectedRecords[0].id : undefined; + + const objectNameSingular = objectMetadataItem.nameSingular as + | string + | undefined; + + closeCommandMenu(); + + openFrontComponentInSidePanel({ + frontComponentId: item.frontComponentId, + pageTitle: label, + pageIcon: Icon, + recordContext: + isDefined(recordId) && isDefined(objectNameSingular) + ? { recordId, objectNameSingular } + : undefined, + }); + } + }; + + return { + handleClick, + disabled, + progress: shouldMountCommand ? commandMenuItemProgress : undefined, + showDisabledLoader: shouldMountCommand ? isMounted : false, + }; +}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsDraftState.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemsDraftState.ts similarity index 84% rename from packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsDraftState.ts rename to packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemsDraftState.ts index e3ed9a6b11..e7a7c13546 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsDraftState.ts +++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuItemsDraftState.ts @@ -1,7 +1,7 @@ import { isDefined } from 'twenty-shared/utils'; -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; -import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; +import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useIsCommandBlockedByGlobalLayoutCustomization.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useIsCommandBlockedByGlobalLayoutCustomization.ts deleted file mode 100644 index 62462ccf56..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/hooks/useIsCommandBlockedByGlobalLayoutCustomization.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; - -export const useIsCommandBlockedByGlobalLayoutCustomization = ( - commandMenuItemConfig: CommandMenuItemConfig | null, -) => { - const isLayoutCustomizationModeEnabled = useAtomStateValue( - isLayoutCustomizationModeEnabledState, - ); - - if (!isLayoutCustomizationModeEnabled) { - return false; - } - - return !commandMenuItemConfig?.isAllowedDuringGlobalLayoutCustomization; -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useWorkflowsWithCurrentVersions.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useWorkflowsWithCurrentVersions.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useWorkflowsWithCurrentVersions.ts rename to packages/twenty-front/src/modules/command-menu-item/hooks/useWorkflowsWithCurrentVersions.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx b/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx index 12fb70b970..11f56a48a3 100644 --- a/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/mock/command-menu-items.mock.tsx @@ -1,98 +1,81 @@ -import { msg } from '@lingui/core/macro'; -import { CommandMenuItemViewType, AppPath } from 'twenty-shared/types'; import { - IconFileExport, - IconHeart, - IconTrash, - IconUser, -} from 'twenty-ui/display'; + CommandMenuItemAvailabilityType, + EngineComponentKey, + type CommandMenuItemFieldsFragment, +} from '~/generated-metadata/graphql'; -import { Command } from '@/command-menu-item/display/components/Command'; -import { CommandLink } from '@/command-menu-item/display/components/CommandLink'; -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope'; -import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType'; -import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { EngineComponentKey } from '~/generated-metadata/graphql'; - -export const createMockCommandMenuItems = ({ - deleteMock = () => {}, - addToFavoritesMock = () => {}, - exportMock = () => {}, -}: { - deleteMock?: () => void; - addToFavoritesMock?: () => void; - exportMock?: () => void; -}): CommandMenuItemConfig[] => [ - { - type: CommandMenuItemType.Standard, - scope: CommandMenuItemScope.RecordSelection, - key: EngineComponentKey.ADD_TO_FAVORITES, - label: msg`Add to favorites`, - shortLabel: msg`Add to favorites`, - position: 2, - isPinned: true, - Icon: IconHeart, - shouldBeRegistered: () => true, - availableOn: [ - CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, - CommandMenuItemViewType.SHOW_PAGE, - ], - component: , - }, - { - type: CommandMenuItemType.Standard, - scope: CommandMenuItemScope.RecordSelection, - key: EngineComponentKey.EXPORT_FROM_RECORD_INDEX, - label: msg`Export`, - shortLabel: msg`Export`, - position: 4, - Icon: IconFileExport, - accent: 'default', - isPinned: false, - shouldBeRegistered: () => true, - availableOn: [CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION], - component: , - }, - { - type: CommandMenuItemType.Standard, - scope: CommandMenuItemScope.RecordSelection, - key: EngineComponentKey.DELETE_SINGLE_RECORD, - label: msg`Delete`, - shortLabel: msg`Delete`, - position: 7, - Icon: IconTrash, - accent: 'default', - isPinned: true, - shouldBeRegistered: () => true, - availableOn: [ - CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, - CommandMenuItemViewType.SHOW_PAGE, - ], - component: , - }, - { - type: CommandMenuItemType.Navigation, - scope: CommandMenuItemScope.Global, - key: EngineComponentKey.GO_TO_PEOPLE, - label: msg`Go to People`, - shortLabel: msg`People`, - position: 19, - Icon: IconUser, - isPinned: false, - availableOn: [ - CommandMenuItemViewType.INDEX_PAGE_NO_SELECTION, - CommandMenuItemViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, - CommandMenuItemViewType.INDEX_PAGE_BULK_SELECTION, - CommandMenuItemViewType.SHOW_PAGE, - ], - shouldBeRegistered: () => true, - component: ( - - ), - hotKeys: ['G', 'P'], - }, -]; +export const createMockCommandMenuItems = + (): CommandMenuItemFieldsFragment[] => [ + { + __typename: 'CommandMenuItem', + id: 'mock-add-to-favorites', + workflowVersionId: null, + frontComponentId: null, + frontComponent: null, + engineComponentKey: EngineComponentKey.ADD_TO_FAVORITES, + label: 'Add to favorites', + icon: 'IconHeart', + shortLabel: 'Add to favorites', + position: 2, + isPinned: true, + hotKeys: null, + conditionalAvailabilityExpression: null, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, + availabilityObjectMetadataId: null, + payload: null, + }, + { + __typename: 'CommandMenuItem', + id: 'mock-export', + workflowVersionId: null, + frontComponentId: null, + frontComponent: null, + engineComponentKey: EngineComponentKey.EXPORT_FROM_RECORD_INDEX, + label: 'Export', + icon: 'IconFileExport', + shortLabel: 'Export', + position: 4, + isPinned: false, + hotKeys: null, + conditionalAvailabilityExpression: null, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, + availabilityObjectMetadataId: null, + payload: null, + }, + { + __typename: 'CommandMenuItem', + id: 'mock-delete', + workflowVersionId: null, + frontComponentId: null, + frontComponent: null, + engineComponentKey: EngineComponentKey.DELETE_SINGLE_RECORD, + label: 'Delete', + icon: 'IconTrash', + shortLabel: 'Delete', + position: 7, + isPinned: true, + hotKeys: null, + conditionalAvailabilityExpression: null, + availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, + availabilityObjectMetadataId: null, + payload: null, + }, + { + __typename: 'CommandMenuItem', + id: 'mock-go-to-people', + workflowVersionId: null, + frontComponentId: null, + frontComponent: null, + engineComponentKey: EngineComponentKey.GO_TO_PEOPLE, + label: 'Go to People', + icon: 'IconUser', + shortLabel: 'People', + position: 19, + isPinned: false, + hotKeys: ['G', 'P'], + conditionalAvailabilityExpression: null, + availabilityType: CommandMenuItemAvailabilityType.GLOBAL, + availabilityObjectMetadataId: null, + payload: null, + }, + ]; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItems.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItems.tsx deleted file mode 100644 index 46d6437ad2..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItems.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { CoreObjectNameSingular } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; - -import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext'; -import { useCommandMenuContextApi } from '@/command-menu-item/server-items/common/hooks/useCommandMenuContextApi'; - -import { CommandMenuContextProviderServerItemsContent } from './CommandMenuContextProviderServerItemsContent'; -import { CommandMenuContextProviderServerItemsWithWorkflowEnrichment } from './CommandMenuContextProviderServerItemsWithWorkflowEnrichment'; - -type CommandMenuContextProviderServerItemsProps = { - isInSidePanel: CommandMenuContextType['isInSidePanel']; - displayType: CommandMenuContextType['displayType']; - containerType: CommandMenuContextType['containerType']; - children: React.ReactNode; -}; - -export const CommandMenuContextProviderServerItems = ({ - isInSidePanel, - displayType, - containerType, - children, -}: CommandMenuContextProviderServerItemsProps) => { - const commandMenuContextApiFromHook = useCommandMenuContextApi(); - - // SidePanelRecordPage shadows the outer ContextStore provider with a - // per-page instance ID, so useCommandMenuContextApi derives isInSidePanel - // as false. The explicit prop from the caller is the source of truth. - const commandMenuContextApi = isInSidePanel - ? { ...commandMenuContextApiFromHook, isInSidePanel: true as const } - : commandMenuContextApiFromHook; - - const currentObjectNameSingular = - commandMenuContextApi.objectMetadataItem.nameSingular; - - const isWorkflow = - currentObjectNameSingular === CoreObjectNameSingular.Workflow; - - const selectedWorkflowRecordIds = isWorkflow - ? commandMenuContextApi.selectedRecords - .map((record) => record.id) - .filter(isDefined) - : []; - - if (selectedWorkflowRecordIds.length > 0) { - return ( - - {children} - - ); - } - - return ( - - {children} - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItemsContent.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItemsContent.tsx deleted file mode 100644 index e8af6c9b97..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/contexts/CommandMenuContextProviderServerItemsContent.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - CommandMenuContext, - type CommandMenuContextType, -} from '@/command-menu-item/contexts/CommandMenuContext'; -import { useCommandMenuItemsFromBackend } from '@/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend'; -import { type CommandMenuContextApi } from 'twenty-shared/types'; - -type CommandMenuContextProviderServerItemsContentProps = { - isInSidePanel: CommandMenuContextType['isInSidePanel']; - displayType: CommandMenuContextType['displayType']; - containerType: CommandMenuContextType['containerType']; - children: React.ReactNode; -}; - -export const CommandMenuContextProviderServerItemsContent = ({ - isInSidePanel, - displayType, - containerType, - children, - commandMenuContextApi, -}: CommandMenuContextProviderServerItemsContentProps & { - commandMenuContextApi: CommandMenuContextApi; -}) => { - const commandMenuItems = useCommandMenuItemsFromBackend( - commandMenuContextApi, - ); - - return ( - - {children} - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx deleted file mode 100644 index fef21cfc1f..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/__tests__/useCommandMenuItemsFromBackend.test.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { useCommandMenuItemsFromBackend } from '@/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { renderHook } from '@testing-library/react'; -import { - CommandMenuContextApiPageType, - type CommandMenuContextApi, -} from 'twenty-shared/types'; -import { Icon123 } from 'twenty-ui/display'; -import { - CommandMenuItemAvailabilityType, - type CommandMenuItemFieldsFragment, - EngineComponentKey, -} from '~/generated-metadata/graphql'; - -jest.mock('@/ui/utilities/state/jotai/hooks/useAtomStateValue'); -jest.mock('twenty-ui/display', () => ({ - ...jest.requireActual('twenty-ui/display'), - useIcons: () => ({ - getIcon: () => Icon123, - }), -})); -jest.mock('twenty-shared/utils', () => { - const actual = jest.requireActual('twenty-shared/utils'); - - return { - ...actual, - interpolateCommandMenuItemTemplate: jest.fn( - ({ label }: { label?: string | null }) => label ?? null, - ), - evaluateConditionalAvailabilityExpression: jest.fn( - (expression?: string | null) => expression !== 'hide', - ), - }; -}); - -const mockedUseAtomStateValue = jest.mocked(useAtomStateValue); - -const getCommandMenuContextApi = ( - numberOfSelectedRecords: number, -): CommandMenuContextApi => ({ - pageType: CommandMenuContextApiPageType.INDEX_PAGE, - isInSidePanel: false, - isPageInEditMode: false, - favoriteRecordIds: [], - isSelectAll: false, - hasAnySoftDeleteFilterOnView: false, - objectMetadataItem: { - id: 'company-id', - }, - objectMetadataLabel: 'Company', - numberOfSelectedRecords, - objectPermissions: { - objectMetadataId: 'company-id', - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: true, - canDestroyObjectRecords: true, - restrictedFields: {}, - rowLevelPermissionPredicates: [], - rowLevelPermissionPredicateGroups: [], - }, - selectedRecords: [], - featureFlags: {}, - targetObjectReadPermissions: {}, - targetObjectWritePermissions: {}, -}); - -const buildCommandMenuItem = ({ - id, - position, - availabilityType, - expression, - isPinned = true, -}: { - id: string; - position: number; - availabilityType: CommandMenuItemAvailabilityType; - expression?: string; - isPinned?: boolean; -}): CommandMenuItemFieldsFragment => ({ - id, - label: id, - shortLabel: `${id}-short`, - icon: 'Icon123', - position, - isPinned, - hotKeys: null, - engineComponentKey: EngineComponentKey.CREATE_NEW_RECORD, - frontComponentId: null, - workflowVersionId: null, - availabilityType, - availabilityObjectMetadataId: 'company-id', - conditionalAvailabilityExpression: expression ?? null, -}); - -describe('useCommandMenuItemsFromBackend', () => { - it('filters out items hidden by conditional availability expression', () => { - mockedUseAtomStateValue.mockReturnValue([ - buildCommandMenuItem({ - id: 'global-visible', - position: 1, - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, - }), - buildCommandMenuItem({ - id: 'global-hidden', - position: 2, - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, - expression: 'hide', - }), - buildCommandMenuItem({ - id: 'record-visible', - position: 3, - availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, - }), - ]); - - const { result } = renderHook(() => - useCommandMenuItemsFromBackend(getCommandMenuContextApi(0)), - ); - - expect(result.current.map((item) => item.id)).toEqual(['global-visible']); - }); - - it('maps global, record selection and fallback items to V2 output', () => { - mockedUseAtomStateValue.mockReturnValue([ - buildCommandMenuItem({ - id: 'global-item', - position: 2, - availabilityType: CommandMenuItemAvailabilityType.GLOBAL, - }), - buildCommandMenuItem({ - id: 'record-item', - position: 1, - availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, - }), - buildCommandMenuItem({ - id: 'fallback-item', - position: 3, - availabilityType: CommandMenuItemAvailabilityType.FALLBACK, - }), - ]); - - const { result } = renderHook(() => - useCommandMenuItemsFromBackend(getCommandMenuContextApi(1)), - ); - - expect(result.current.map((item) => item.id)).toEqual([ - 'record-item', - 'global-item', - 'fallback-item', - ]); - expect(result.current[2].isPinned).toBe(false); - }); -}); diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx deleted file mode 100644 index 3b4e850b90..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/hooks/useCommandMenuItemsFromBackend.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import { FrontComponentCommandMenuItem } from '@/command-menu-item/display/components/FrontComponentCommandMenuItem'; -import { HeadlessCommandMenuItem } from '@/command-menu-item/display/components/HeadlessCommandMenuItem'; -import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector'; -import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/server-items/common/utils/doesCommandMenuItemMatchObjectMetadataId'; -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope'; -import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType'; - -import { type CommandMenuContextApi } from 'twenty-shared/types'; -import { - evaluateConditionalAvailabilityExpression, - interpolateCommandMenuItemTemplate, - isDefined, -} from 'twenty-shared/utils'; -import { useIcons } from 'twenty-ui/display'; - -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; -import { - CommandMenuItemAvailabilityType, - type CommandMenuItemFieldsFragment, -} from '~/generated-metadata/graphql'; - -type CommandMenuItemWithFrontComponent = CommandMenuItemFieldsFragment & { - frontComponentId: string; - conditionalAvailabilityExpression?: string | null; -}; - -type CommandMenuItemWithSource = CommandMenuItemFieldsFragment & { - conditionalAvailabilityExpression?: string | null; -}; - -type BuildCommandMenuItemFromFrontComponentParams = { - item: CommandMenuItemWithFrontComponent; - type?: CommandMenuItemType; - scope: CommandMenuItemScope; - isPinned: boolean; - getIcon: ReturnType['getIcon']; - commandMenuContextApi: CommandMenuContextApi; -}; - -const buildCommandMenuItemFromFrontComponent = ({ - item, - type = CommandMenuItemType.FrontComponent, - scope, - isPinned, - getIcon, - commandMenuContextApi, -}: BuildCommandMenuItemFromFrontComponentParams): CommandMenuItemConfig => { - const displayLabel = interpolateCommandMenuItemTemplate({ - label: item.label, - context: commandMenuContextApi, - }); - - const displayShortLabel = interpolateCommandMenuItemTemplate({ - label: item.shortLabel, - context: commandMenuContextApi, - }); - - const interpolatedIcon = interpolateCommandMenuItemTemplate({ - label: item.icon, - context: commandMenuContextApi, - }); - - const Icon = getIcon(interpolatedIcon, COMMAND_MENU_DEFAULT_ICON); - - const isHeadless = item.frontComponent?.isHeadless === true; - - return { - type, - key: `command-menu-item-front-component-${item.id}`, - id: item.id, - scope, - label: displayLabel, - shortLabel: displayShortLabel, - position: item.position, - isPinned, - Icon, - hotKeys: item.hotKeys, - component: isHeadless ? ( - - ) : ( - - ), - }; -}; - -type BuildCommandMenuItemFromStandardKeyParams = { - item: CommandMenuItemWithSource; - type?: CommandMenuItemType; - scope: CommandMenuItemScope; - isPinned: boolean; - getIcon: ReturnType['getIcon']; - commandMenuContextApi: CommandMenuContextApi; -}; - -const buildCommandItemFromEngineKey = ({ - item, - type = CommandMenuItemType.Standard, - scope, - isPinned, - getIcon, - commandMenuContextApi, -}: BuildCommandMenuItemFromStandardKeyParams): CommandMenuItemConfig => { - const interpolatedIcon = interpolateCommandMenuItemTemplate({ - label: item.icon, - context: commandMenuContextApi, - }); - const Icon = getIcon(interpolatedIcon, COMMAND_MENU_DEFAULT_ICON); - - return { - type, - key: `command-menu-item-engine-${item.id}`, - id: item.id, - scope, - label: interpolateCommandMenuItemTemplate({ - label: item.label, - context: commandMenuContextApi, - }), - shortLabel: interpolateCommandMenuItemTemplate({ - label: item.shortLabel, - context: commandMenuContextApi, - }), - position: item.position, - isPinned, - Icon, - hotKeys: item.hotKeys, - component: , - }; -}; - -export const useCommandMenuItemsFromBackend = ( - commandMenuContextApi: CommandMenuContextApi, -): CommandMenuItemConfig[] => { - const { getIcon } = useIcons(); - const currentObjectMetadataItemId = - commandMenuContextApi.objectMetadataItem.id; - - const hasRecordSelection = commandMenuContextApi.numberOfSelectedRecords >= 1; - - const commandMenuItems = useAtomStateValue(commandMenuItemsSelector); - - const itemsWithObjectMatches = commandMenuItems.filter( - doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId), - ); - const availableItems = itemsWithObjectMatches.filter((item) => - evaluateConditionalAvailabilityExpression( - item.conditionalAvailabilityExpression, - commandMenuContextApi, - ), - ); - - const buildCommandMenuItem = ({ - item, - scope, - isPinned, - typeOverride, - }: { - item: CommandMenuItemFieldsFragment; - scope: CommandMenuItemScope; - isPinned: boolean; - typeOverride?: CommandMenuItemType; - }): CommandMenuItemConfig | null => { - if (isDefined(item.frontComponentId)) { - return buildCommandMenuItemFromFrontComponent({ - item: item as CommandMenuItemWithFrontComponent, - type: typeOverride, - scope, - isPinned, - getIcon, - commandMenuContextApi, - }); - } - - if (isDefined(item.engineComponentKey)) { - return buildCommandItemFromEngineKey({ - item, - type: typeOverride, - scope, - isPinned, - getIcon, - commandMenuContextApi, - }); - } - - return null; - }; - - const globalItems = availableItems.filter( - (item) => item.availabilityType === CommandMenuItemAvailabilityType.GLOBAL, - ); - - const recordScopedItems = availableItems.filter( - (item) => - item.availabilityType === - CommandMenuItemAvailabilityType.RECORD_SELECTION, - ); - - const fallbackItems = availableItems.filter( - (item) => - item.availabilityType === CommandMenuItemAvailabilityType.FALLBACK, - ); - - const globalCommandMenuItems = globalItems - .map((item) => - buildCommandMenuItem({ - item, - scope: CommandMenuItemScope.Global, - isPinned: item.isPinned, - }), - ) - .filter(isDefined); - - const recordScopedCommandMenuItems = hasRecordSelection - ? recordScopedItems - .map((item) => - buildCommandMenuItem({ - item, - scope: CommandMenuItemScope.RecordSelection, - isPinned: item.isPinned, - }), - ) - .filter(isDefined) - : []; - - const fallbackCommandMenuItems = fallbackItems - .map((item) => - buildCommandMenuItem({ - item, - scope: CommandMenuItemScope.Global, - isPinned: false, - typeOverride: CommandMenuItemType.Fallback, - }), - ) - .filter(isDefined); - - return [ - ...globalCommandMenuItems, - ...recordScopedCommandMenuItems, - ...fallbackCommandMenuItems, - ].sort((a, b) => a.position - b.position); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx b/packages/twenty-front/src/modules/command-menu-item/server-items/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx deleted file mode 100644 index 92997a7d0f..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/display/components/PinnedCommandMenuItemsInlineMeasurements.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { CommandMenuButton } from '@/command-menu/components/CommandMenuButton'; -import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig'; -import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/server-items/display/constants/PinnedCommandMenuItemsGap'; -import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension'; -import { styled } from '@linaria/react'; - -type ElementDimensions = { - width: number; - height: number; -}; - -type PinnedCommandMenuItemsInlineMeasurementsProps = { - pinnedCommandMenuItems: CommandMenuItemConfig[]; - onPinnedCommandMenuItemDimensionChange: ( - commandMenuItemKey: string, - ) => (dimensions: ElementDimensions) => void; -}; - -const StyledHiddenMeasurementsContainer = styled.div` - display: flex; - gap: ${PINNED_COMMAND_MENU_ITEMS_GAP}px; - pointer-events: none; - position: absolute; - top: -9999px; - visibility: hidden; -`; - -export const PinnedCommandMenuItemsInlineMeasurements = ({ - pinnedCommandMenuItems, - onPinnedCommandMenuItemDimensionChange, -}: PinnedCommandMenuItemsInlineMeasurementsProps) => { - return ( - - {pinnedCommandMenuItems.map((pinnedCommandMenuItem) => ( - - - - ))} - - ); -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/states/commandMenuItemsSelector.ts b/packages/twenty-front/src/modules/command-menu-item/states/commandMenuItemsSelector.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/common/states/commandMenuItemsSelector.ts rename to packages/twenty-front/src/modules/command-menu-item/states/commandMenuItemsSelector.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts b/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts deleted file mode 100644 index 8357fc8651..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemConfig.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { type CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope'; -import { type CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType'; -import { type MessageDescriptor } from '@lingui/core'; -import { - type CommandMenuItemViewType, - type Nullable, -} from 'twenty-shared/types'; -import { type IconComponent } from 'twenty-ui/display'; -import { type MenuItemAccent } from 'twenty-ui/navigation'; -import { type PermissionFlagType } from '~/generated-metadata/graphql'; - -export type CommandMenuItemConfig = { - type: CommandMenuItemType; - scope: CommandMenuItemScope; - key: string; - id?: string; - label: Nullable; - shortLabel?: Nullable; - description?: MessageDescriptor | string; - position: number; - Icon: IconComponent; - isPinned?: boolean; - isPrimaryCTA?: boolean; - accent?: MenuItemAccent; - availableOn?: CommandMenuItemViewType[]; - shouldBeRegistered?: () => boolean; - component: React.ReactNode; - hotKeys?: Nullable; - requiredPermissionFlag?: PermissionFlagType; - isAllowedDuringGlobalLayoutCustomization?: boolean; -}; diff --git a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemScope.ts b/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemScope.ts deleted file mode 100644 index 1c00826e95..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemScope.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum CommandMenuItemScope { - Global = 'Global', - RecordSelection = 'RecordSelection', - CreateRelatedRecord = 'CreateRelatedRecord', - Object = 'Object', -} diff --git a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemType.ts b/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemType.ts deleted file mode 100644 index f9f78bd99a..0000000000 --- a/packages/twenty-front/src/modules/command-menu-item/types/CommandMenuItemType.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum CommandMenuItemType { - Standard = 'Standard', - WorkflowRun = 'WorkflowRun', - FrontComponent = 'FrontComponent', - Fallback = 'Fallback', - Navigation = 'Navigation', -} diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuDropdownIdFromCommandMenuId.test.ts b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuDropdownIdFromCommandMenuId.test.ts index d6d27bcfc2..4b70bc6e95 100644 --- a/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuDropdownIdFromCommandMenuId.test.ts +++ b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuDropdownIdFromCommandMenuId.test.ts @@ -1,7 +1,7 @@ import { getCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getCommandMenuDropdownIdFromCommandMenuId'; describe('getCommandMenuDropdownIdFromCommandMenuId', () => { - it('should return the correct action menu dropdown id', () => { + it('should return the correct command menu dropdown id', () => { expect(getCommandMenuDropdownIdFromCommandMenuId('command-menu-id')).toBe( 'command-menu-dropdown-command-menu-id', ); diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuIdFromRecordIndexId.test.ts b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuIdFromRecordIndexId.test.ts index 9758afde4b..4bcdcba9de 100644 --- a/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuIdFromRecordIndexId.test.ts +++ b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getCommandMenuIdFromRecordIndexId.test.ts @@ -1,7 +1,7 @@ import { getCommandMenuIdFromRecordIndexId } from '@/command-menu-item/utils/getCommandMenuIdFromRecordIndexId'; describe('getCommandMenuIdFromRecordIndexId', () => { - it('should return the correct action menu id', () => { + it('should return the correct command menu id', () => { expect(getCommandMenuIdFromRecordIndexId('record-index-id')).toBe( 'command-menu-record-index-record-index-id', ); diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getSidePanelCommandMenuDropdownIdFromCommandMenuId.test.ts b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getSidePanelCommandMenuDropdownIdFromCommandMenuId.test.ts index f6ffb0c8d5..0fd18fd91f 100644 --- a/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getSidePanelCommandMenuDropdownIdFromCommandMenuId.test.ts +++ b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/getSidePanelCommandMenuDropdownIdFromCommandMenuId.test.ts @@ -1,7 +1,7 @@ import { getSidePanelCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getSidePanelCommandMenuDropdownIdFromCommandMenuId'; describe('getSidePanelCommandMenuDropdownIdFromCommandMenuId', () => { - it('should return the side panel action menu dropdown id', () => { + it('should return the side panel command menu dropdown id', () => { expect( getSidePanelCommandMenuDropdownIdFromCommandMenuId('command-menu-id'), ).toBe('side-panel-command-menu-dropdown-command-menu-id'); diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/utils/doesCommandMenuItemMatchObjectMetadataId.ts b/packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId.ts similarity index 100% rename from packages/twenty-front/src/modules/command-menu-item/server-items/common/utils/doesCommandMenuItemMatchObjectMetadataId.ts rename to packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId.ts diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/groupCommandMenuItems.ts b/packages/twenty-front/src/modules/command-menu-item/utils/groupCommandMenuItems.ts new file mode 100644 index 0000000000..6988e79f11 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu-item/utils/groupCommandMenuItems.ts @@ -0,0 +1,18 @@ +import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; + +export const groupCommandMenuItems = ( + items: CommandMenuItemFieldsFragment[], +) => { + const pinned: CommandMenuItemFieldsFragment[] = []; + const other: CommandMenuItemFieldsFragment[] = []; + + for (const item of items) { + if (item.isPinned) { + pinned.push(item); + } else { + other.push(item); + } + } + + return { pinned, other }; +}; diff --git a/packages/twenty-front/src/modules/command-menu/types/Command.ts b/packages/twenty-front/src/modules/command-menu/types/Command.ts deleted file mode 100644 index 590e708a66..0000000000 --- a/packages/twenty-front/src/modules/command-menu/types/Command.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { type IconComponent } from 'twenty-ui/display'; -export enum CommandType { - Navigate = 'Navigate', - Create = 'Create', - StandardAction = 'StandardAction', - WorkflowRun = 'WorkflowRun', - Fallback = 'Fallback', -} - -export enum CommandScope { - Global = 'Global', - RecordSelection = 'RecordSelection', - Object = 'Object', -} - -export type Command = { - id: string; - to?: string; - label: string; - description?: string; - type?: CommandType; - scope?: CommandScope; - Icon?: IconComponent; - hotKeys?: string[]; - onCommandClick?: () => void; -}; diff --git a/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts b/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts index b412d36ed0..c133e47112 100644 --- a/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts +++ b/packages/twenty-front/src/modules/layout-customization/hooks/useEnterLayoutCustomizationMode.ts @@ -4,8 +4,8 @@ import { useCallback } from 'react'; import { SidePanelPages } from 'twenty-shared/types'; import { IconPencil } from 'twenty-ui/display'; -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; -import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; +import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector'; import { activeCustomizationPageLayoutIdsState } from '@/layout-customization/states/activeCustomizationPageLayoutIdsState'; import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState'; diff --git a/packages/twenty-front/src/modules/layout-customization/hooks/useExitLayoutCustomizationMode.ts b/packages/twenty-front/src/modules/layout-customization/hooks/useExitLayoutCustomizationMode.ts index c8c0a3c03d..3ab98de0af 100644 --- a/packages/twenty-front/src/modules/layout-customization/hooks/useExitLayoutCustomizationMode.ts +++ b/packages/twenty-front/src/modules/layout-customization/hooks/useExitLayoutCustomizationMode.ts @@ -1,4 +1,4 @@ -import { commandMenuItemsDraftState } from '@/command-menu-item/server-items/edit/states/commandMenuItemsDraftState'; +import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { activeCustomizationPageLayoutIdsState } from '@/layout-customization/states/activeCustomizationPageLayoutIdsState'; import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; diff --git a/packages/twenty-front/src/modules/layout-customization/hooks/useIsLayoutCustomizationDirty.ts b/packages/twenty-front/src/modules/layout-customization/hooks/useIsLayoutCustomizationDirty.ts index f8c9abd8fd..7ea1d5880d 100644 --- a/packages/twenty-front/src/modules/layout-customization/hooks/useIsLayoutCustomizationDirty.ts +++ b/packages/twenty-front/src/modules/layout-customization/hooks/useIsLayoutCustomizationDirty.ts @@ -1,4 +1,4 @@ -import { useCommandMenuItemsDraftState } from '@/command-menu-item/server-items/common/hooks/useCommandMenuItemsDraftState'; +import { useCommandMenuItemsDraftState } from '@/command-menu-item/hooks/useCommandMenuItemsDraftState'; import { activeCustomizationPageLayoutIdsState } from '@/layout-customization/states/activeCustomizationPageLayoutIdsState'; import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout'; import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/edit/hooks/useNavigationMenuItemsDraftState'; diff --git a/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts b/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts index 7eb705ccd4..a55a3380ab 100644 --- a/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts +++ b/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts @@ -1,5 +1,5 @@ -import { useSaveCommandMenuItemsDraft } from '@/command-menu-item/server-items/edit/hooks/useSaveCommandMenuItemsDraft'; -import { useCommandMenuItemsDraftState } from '@/command-menu-item/server-items/common/hooks/useCommandMenuItemsDraftState'; +import { useSaveCommandMenuItemsDraft } from '@/command-menu-item/edit/hooks/useSaveCommandMenuItemsDraft'; +import { useCommandMenuItemsDraftState } from '@/command-menu-item/hooks/useCommandMenuItemsDraftState'; import { useExitLayoutCustomizationMode } from '@/layout-customization/hooks/useExitLayoutCustomizationMode'; import { activeCustomizationPageLayoutIdsState } from '@/layout-customization/states/activeCustomizationPageLayoutIdsState'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState'; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelEditFolderPickerSubPage.tsx b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelEditFolderPickerSubPage.tsx index 531d06e0c6..4499538d6d 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelEditFolderPickerSubPage.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelEditFolderPickerSubPage.tsx @@ -42,7 +42,6 @@ export const SidePanelEditFolderPickerSubPage = () => { onSearchChange={setSearchValue} > + + {showColorOption && isDefined(selectedItem) && objectMetadataItem?.isSystem !== true && ( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNavigationMenuItemEditPage.tsx b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNavigationMenuItemEditPage.tsx index b17cadb59a..b201525ba3 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNavigationMenuItemEditPage.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNavigationMenuItemEditPage.tsx @@ -162,7 +162,6 @@ export const SidePanelNavigationMenuItemEditPage = () => { case NavigationMenuItemType.FOLDER: return ( { default: return ( {({ innerRef, droppableProps, placeholder }) => ( - + {/* eslint-disable-next-line react/jsx-props-no-spreading */}
diff --git a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordSubPage.tsx b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordSubPage.tsx index 8a3b253ae6..67dec64893 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordSubPage.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordSubPage.tsx @@ -101,7 +101,6 @@ export const SidePanelNewSidebarItemRecordSubPage = () => { {({ innerRef, droppableProps, placeholder }) => ( {({ innerRef, droppableProps, placeholder }) => ( {({ innerRef, droppableProps, placeholder }) => ( ) : ( {({ innerRef, droppableProps, placeholder }) => ( ) : ( { {isDefined(contextStoreCurrentViewId) && ( <> - {!isLayoutCustomizationModeEnabled && ( - - )} + {!isLayoutCustomizationModeEnabled && } )} diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutRecordPageRenderer.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutRecordPageRenderer.tsx index 44f62aa657..01f1326273 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutRecordPageRenderer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/PageLayoutRecordPageRenderer.tsx @@ -1,7 +1,6 @@ import { RecordPageSidePanelCommandMenu } from '@/command-menu-item/components/RecordPageSidePanelCommandMenu'; import { RecordShowSidePanelOpenRecordButton } from '@/command-menu-item/components/RecordShowSidePanelOpenRecordButton'; import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord'; -import { CoreObjectNameSingular } from 'twenty-shared/types'; import { RecordShowContainerContextStoreTargetedRecordsEffect } from '@/object-record/record-show/components/RecordShowContainerContextStoreTargetedRecordsEffect'; import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; @@ -10,10 +9,11 @@ import { usePageLayoutIdForRecord } from '@/page-layout/hooks/usePageLayoutIdFor import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; import { type TargetRecordIdentifier } from '@/ui/layout/contexts/TargetRecordIdentifier'; import { SidePanelFooter } from '@/ui/layout/side-panel/components/SidePanelFooter'; -import { sidePanelWidgetFooterActionsState } from '@/ui/layout/side-panel/states/sidePanelWidgetFooterActionsState'; -import { styled } from '@linaria/react'; +import { sidePanelWidgetFooterCommandMenuItemsState } from '@/ui/layout/side-panel/states/sidePanelWidgetFooterCommandMenuItemsState'; import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { styled } from '@linaria/react'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { Button } from 'twenty-ui/input'; import { themeCssVariables } from 'twenty-ui/theme-constants'; @@ -58,15 +58,17 @@ export const PageLayoutRecordPageRenderer = ({ targetObjectNameSingular: targetRecordIdentifier.targetObjectNameSingular, }); - const sidePanelWidgetFooterActions = useAtomStateValue( - sidePanelWidgetFooterActionsState, + const sidePanelWidgetFooterCommandMenuItems = useAtomStateValue( + sidePanelWidgetFooterCommandMenuItemsState, ); - const pinnedWidgetActions = sidePanelWidgetFooterActions.filter( - (action) => action.isPinned !== false, - ); + const pinnedWidgetCommandMenuItems = + sidePanelWidgetFooterCommandMenuItems.filter( + (commandMenuItem) => commandMenuItem.isPinned !== false, + ); - const hasPinnedWidgetActions = pinnedWidgetActions.length > 0; + const hasPinnedWidgetCommandMenuItems = + pinnedWidgetCommandMenuItems.length > 0; return ( <> @@ -115,18 +117,20 @@ export const PageLayoutRecordPageRenderer = ({ , - ...(hasPinnedWidgetActions - ? pinnedWidgetActions.map((action) => ( + ...(hasPinnedWidgetCommandMenuItems + ? pinnedWidgetCommandMenuItems.map((commandMenuItem) => (