diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx
index 7081c310c5fb3..8efc00e27e1d5 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx
@@ -1,8 +1,8 @@
import { MultipleRecordsActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect';
import { NoSelectionActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect';
-import { ShowPageSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/ShowPageSingleRecordActionMenuEntrySetterEffect';
import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect';
import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
@@ -67,14 +67,16 @@ const ActionEffects = ({
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && (
<>
{contextStoreCurrentViewType === ContextStoreViewType.ShowPage && (
-
)}
{(contextStoreCurrentViewType === ContextStoreViewType.Table ||
contextStoreCurrentViewType === ContextStoreViewType.Kanban) && (
)}
{isWorkflowEnabled && (
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx
index 296459dc5751f..3280eb62cb065 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect.tsx
@@ -1,78 +1,23 @@
-import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig';
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
-import { wrapActionInCallbacks } from '@/action-menu/actions/utils/wrapActionInCallbacks';
-import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { useActionMenuEntriesWithCallbacks } from '@/action-menu/hooks/useActionMenuEntriesWithCallbacks';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { useContext, useEffect } from 'react';
-import { isDefined } from 'twenty-ui';
+import { useEffect } from 'react';
export const SingleRecordActionMenuEntrySetterEffect = ({
objectMetadataItem,
+ viewType,
}: {
objectMetadataItem: ObjectMetadataItem;
+ viewType: ActionViewType;
}) => {
- const isPageHeaderV2Enabled = useIsFeatureEnabled(
- 'IS_PAGE_HEADER_V2_ENABLED',
- );
-
- const actionConfig = getActionConfig(
- objectMetadataItem,
- isPageHeaderV2Enabled,
- );
-
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
- const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
- contextStoreTargetedRecordsRuleComponentState,
+ const { actionMenuEntries } = useActionMenuEntriesWithCallbacks(
+ objectMetadataItem,
+ viewType,
);
- const selectedRecordId =
- contextStoreTargetedRecordsRule.mode === 'selection'
- ? contextStoreTargetedRecordsRule.selectedRecordIds[0]
- : undefined;
-
- if (!isDefined(selectedRecordId)) {
- throw new Error('Selected record ID is required');
- }
-
- const { onActionStartedCallback, onActionExecutedCallback } =
- useContext(ActionMenuContext);
-
- const actionMenuEntries = Object.values(actionConfig ?? {})
- .filter((action) =>
- action.availableOn?.includes(
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ),
- )
- .map((action) => {
- const { shouldBeRegistered, onClick, ConfirmationModal } =
- action.actionHook({
- recordId: selectedRecordId,
- objectMetadataItem,
- });
-
- if (!shouldBeRegistered) {
- return undefined;
- }
-
- const wrappedAction = wrapActionInCallbacks({
- action: {
- ...action,
- onClick,
- ConfirmationModal,
- },
- onActionStartedCallback,
- onActionExecutedCallback,
- });
-
- return wrappedAction;
- })
- .filter(isDefined);
-
useEffect(() => {
for (const action of actionMenuEntries) {
addActionMenuEntry(action);
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts
index 778fdea06a733..ba65842250234 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV1.ts
@@ -2,7 +2,7 @@ import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/recor
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook';
import {
ActionMenuEntry,
@@ -25,8 +25,8 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
position: 0,
Icon: IconHeart,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useAddToFavoritesSingleRecordAction,
},
@@ -38,8 +38,8 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
position: 1,
Icon: IconHeartOff,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useRemoveFromFavoritesSingleRecordAction,
},
@@ -53,8 +53,8 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
accent: 'danger',
isPinned: true,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useDeleteSingleRecordAction,
},
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts
index 1e3e89530c831..6e41b853e4950 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/DefaultSingleRecordActionsConfigV2.ts
@@ -6,7 +6,7 @@ import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions
import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook';
import {
ActionMenuEntry,
@@ -38,7 +38,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
position: 0,
isPinned: false,
Icon: IconFileExport,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useExportNoteAction,
},
addToFavoritesSingleRecord: {
@@ -51,8 +51,8 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
isPinned: true,
Icon: IconHeart,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useAddToFavoritesSingleRecordAction,
},
@@ -66,8 +66,8 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
position: 2,
Icon: IconHeartOff,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useRemoveFromFavoritesSingleRecordAction,
},
@@ -82,8 +82,8 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
accent: 'danger',
isPinned: true,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useDeleteSingleRecordAction,
},
@@ -98,8 +98,8 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
accent: 'danger',
isPinned: true,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useDestroySingleRecordAction,
},
@@ -112,7 +112,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
position: 5,
isPinned: true,
Icon: IconChevronUp,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToPreviousRecordSingleRecordAction,
},
navigateToNextRecord: {
@@ -124,7 +124,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
position: 6,
isPinned: true,
Icon: IconChevronDown,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToNextRecordSingleRecordAction,
},
};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts
index b672ec5e82c88..f6d85ae3b5ee9 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/constants/WorkflowSingleRecordActionsConfig.ts
@@ -14,7 +14,7 @@ import { useSeeRunsWorkflowSingleRecordAction } from '@/action-menu/actions/reco
import { useSeeVersionsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeVersionsWorkflowSingleRecordAction';
import { useTestWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowSingleRecordAction';
import { WorkflowSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/workflow-actions/types/WorkflowSingleRecordActionsKeys';
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook';
import {
ActionMenuEntry,
@@ -51,8 +51,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useActivateDraftWorkflowSingleRecordAction,
},
@@ -66,8 +66,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useActivateLastPublishedVersionWorkflowSingleRecordAction,
},
@@ -81,8 +81,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useDeactivateWorkflowSingleRecordAction,
},
@@ -96,8 +96,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useDiscardDraftWorkflowSingleRecordAction,
},
@@ -111,8 +111,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useSeeActiveVersionWorkflowSingleRecordAction,
},
@@ -126,8 +126,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useSeeRunsWorkflowSingleRecordAction,
},
@@ -141,8 +141,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useSeeVersionsWorkflowSingleRecordAction,
},
@@ -156,8 +156,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useTestWorkflowSingleRecordAction,
},
@@ -169,7 +169,7 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
shortLabel: '',
position: 9,
Icon: IconChevronUp,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToPreviousRecordSingleRecordAction,
},
navigateToNextRecord: {
@@ -180,7 +180,7 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
shortLabel: '',
position: 10,
Icon: IconChevronDown,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToNextRecordSingleRecordAction,
},
addToFavoritesSingleRecord: {
@@ -193,8 +193,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
isPinned: false,
Icon: IconHeart,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useAddToFavoritesSingleRecordAction,
},
@@ -208,8 +208,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
position: 12,
Icon: IconHeartOff,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useRemoveFromFavoritesSingleRecordAction,
},
@@ -224,8 +224,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
accent: 'danger',
isPinned: false,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useDeleteSingleRecordAction,
},
@@ -240,8 +240,8 @@ export const WORKFLOW_SINGLE_RECORD_ACTIONS_CONFIG: Record<
accent: 'danger',
isPinned: false,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useDestroySingleRecordAction,
},
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts
index 65b97bf750b7b..7f6f246332072 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/constants/WorkflowRunsSingleRecordActionsConfig.ts
@@ -3,7 +3,7 @@ import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions
import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook';
import {
ActionMenuEntry,
@@ -33,8 +33,8 @@ export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
isPinned: true,
Icon: IconHeart,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useAddToFavoritesSingleRecordAction,
},
@@ -48,8 +48,8 @@ export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
position: 1,
Icon: IconHeartOff,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useRemoveFromFavoritesSingleRecordAction,
},
@@ -62,7 +62,7 @@ export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
position: 2,
isPinned: true,
Icon: IconChevronUp,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToPreviousRecordSingleRecordAction,
},
navigateToNextRecord: {
@@ -74,7 +74,7 @@ export const WORKFLOW_RUNS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
position: 3,
isPinned: true,
Icon: IconChevronDown,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToNextRecordSingleRecordAction,
},
};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts
index 10065a3bb4d24..95f531329704b 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/constants/WorkflowVersionsSingleRecordActionsConfig.ts
@@ -7,7 +7,7 @@ import { useSeeRunsWorkflowVersionSingleRecordAction } from '@/action-menu/actio
import { useSeeVersionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeVersionsWorkflowVersionSingleRecordAction';
import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction';
import { WorkflowVersionSingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/types/WorkflowVersionSingleRecordActionsKeys';
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook';
import {
ActionMenuEntry,
@@ -40,8 +40,8 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
scope: ActionMenuEntryScope.RecordSelection,
Icon: IconPencil,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useUseAsDraftWorkflowVersionSingleRecordAction,
},
@@ -54,8 +54,8 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
scope: ActionMenuEntryScope.RecordSelection,
Icon: IconHistoryToggle,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useSeeRunsWorkflowVersionSingleRecordAction,
},
@@ -68,8 +68,8 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
scope: ActionMenuEntryScope.RecordSelection,
Icon: IconHistory,
availableOn: [
- ActionAvailableOn.SHOW_PAGE,
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
],
actionHook: useSeeVersionsWorkflowVersionSingleRecordAction,
},
@@ -81,7 +81,7 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
shortLabel: '',
position: 4,
Icon: IconChevronUp,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToPreviousRecordSingleRecordAction,
},
navigateToNextRecord: {
@@ -92,7 +92,7 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
shortLabel: '',
position: 5,
Icon: IconChevronDown,
- availableOn: [ActionAvailableOn.SHOW_PAGE],
+ availableOn: [ActionViewType.SHOW_PAGE],
actionHook: useNavigateToNextRecordSingleRecordAction,
},
addToFavoritesSingleRecord: {
@@ -105,8 +105,8 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
isPinned: false,
Icon: IconHeart,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useAddToFavoritesSingleRecordAction,
},
@@ -120,8 +120,8 @@ export const WORKFLOW_VERSIONS_SINGLE_RECORD_ACTIONS_CONFIG: Record<
position: 7,
Icon: IconHeartOff,
availableOn: [
- ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
- ActionAvailableOn.SHOW_PAGE,
+ ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
+ ActionViewType.SHOW_PAGE,
],
actionHook: useRemoveFromFavoritesSingleRecordAction,
},
diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/ActionAvailableOn.ts b/packages/twenty-front/src/modules/action-menu/actions/types/ActionViewType.ts
similarity index 87%
rename from packages/twenty-front/src/modules/action-menu/actions/types/ActionAvailableOn.ts
rename to packages/twenty-front/src/modules/action-menu/actions/types/ActionViewType.ts
index fafc2a1e3a256..36c42382ef1c7 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/types/ActionAvailableOn.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/types/ActionViewType.ts
@@ -1,4 +1,4 @@
-export enum ActionAvailableOn {
+export enum ActionViewType {
INDEX_PAGE_BULK_SELECTION = 'INDEX_PAGE_BULK_SELECTION',
INDEX_PAGE_SINGLE_RECORD_SELECTION = 'INDEX_PAGE_SINGLE_RECORD_SELECTION',
INDEX_PAGE_NO_SELECTION = 'INDEX_PAGE_NO_SELECTION',
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/ShowPageSingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts
similarity index 70%
rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/ShowPageSingleRecordActionMenuEntrySetterEffect.tsx
rename to packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts
index c97b1d1df112e..719b7dc408c76 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/ShowPageSingleRecordActionMenuEntrySetterEffect.tsx
+++ b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts
@@ -1,20 +1,18 @@
import { getActionConfig } from '@/action-menu/actions/record-actions/single-record/utils/getActionConfig';
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { wrapActionInCallbacks } from '@/action-menu/actions/utils/wrapActionInCallbacks';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
-import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { useContext, useEffect } from 'react';
+import { useContext } from 'react';
import { isDefined } from 'twenty-ui';
-export const ShowPageSingleRecordActionMenuEntrySetterEffect = ({
- objectMetadataItem,
-}: {
- objectMetadataItem: ObjectMetadataItem;
-}) => {
+export const useActionMenuEntriesWithCallbacks = (
+ objectMetadataItem: ObjectMetadataItem,
+ viewType: ActionViewType,
+) => {
const isPageHeaderV2Enabled = useIsFeatureEnabled(
'IS_PAGE_HEADER_V2_ENABLED',
);
@@ -24,8 +22,6 @@ export const ShowPageSingleRecordActionMenuEntrySetterEffect = ({
isPageHeaderV2Enabled,
);
- const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
-
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
@@ -38,13 +34,12 @@ export const ShowPageSingleRecordActionMenuEntrySetterEffect = ({
if (!isDefined(selectedRecordId)) {
throw new Error('Selected record ID is required');
}
+
const { onActionStartedCallback, onActionExecutedCallback } =
useContext(ActionMenuContext);
const actionMenuEntries = Object.values(actionConfig ?? {})
- .filter((action) =>
- action.availableOn?.includes(ActionAvailableOn.SHOW_PAGE),
- )
+ .filter((action) => action.availableOn?.includes(viewType))
.map((action) => {
const { shouldBeRegistered, onClick, ConfirmationModal } =
action.actionHook({
@@ -70,17 +65,5 @@ export const ShowPageSingleRecordActionMenuEntrySetterEffect = ({
})
.filter(isDefined);
- useEffect(() => {
- for (const action of actionMenuEntries) {
- addActionMenuEntry(action);
- }
-
- return () => {
- for (const action of actionMenuEntries) {
- removeActionMenuEntry(action.key);
- }
- };
- }, [actionMenuEntries, addActionMenuEntry, removeActionMenuEntry]);
-
- return null;
+ return { actionMenuEntries };
};
diff --git a/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts b/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts
index 2b955d0ca475c..a6df7bbc56d3c 100644
--- a/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts
+++ b/packages/twenty-front/src/modules/action-menu/types/ActionMenuEntry.ts
@@ -1,4 +1,4 @@
-import { ActionAvailableOn } from '@/action-menu/actions/types/ActionAvailableOn';
+import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { ConfirmationModalProps } from '@/ui/layout/modal/components/ConfirmationModal';
import { MouseEvent, ReactElement } from 'react';
import { IconComponent, MenuItemAccent } from 'twenty-ui';
@@ -23,7 +23,7 @@ export type ActionMenuEntry = {
Icon: IconComponent;
isPinned?: boolean;
accent?: MenuItemAccent;
- availableOn?: ActionAvailableOn[];
+ availableOn?: ActionViewType[];
onClick?: (event?: MouseEvent) => void;
ConfirmationModal?: ReactElement;
};
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx
index 2d06ba743fea9..c0cf3815d5151 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx
@@ -1,77 +1,29 @@
-import { useOpenCopilotRightDrawer } from '@/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer';
-import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState';
-import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
-import { Note } from '@/activities/types/Note';
-import { Task } from '@/activities/types/Task';
import { CommandGroup } from '@/command-menu/components/CommandGroup';
+import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar';
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
-import { commandMenuCommandsComponentSelector } from '@/command-menu/states/commandMenuCommandsSelector';
+import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
+import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
-import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
-import {
- Command,
- CommandScope,
- CommandType,
-} from '@/command-menu/types/Command';
-import { Company } from '@/companies/types/Company';
-import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
-import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
-import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
-import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
-import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
-import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
-import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
-import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
-import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
-import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
-import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
-import { isNonEmptyString } from '@sniptt/guards';
-import isEmpty from 'lodash.isempty';
-import { useMemo, useRef } from 'react';
-import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
-import { Key } from 'ts-key-enum';
-import {
- Avatar,
- IconCheckbox,
- IconComponent,
- IconNotes,
- IconSparkles,
- isDefined,
-} from 'twenty-ui';
-import { useDebounce } from 'use-debounce';
-import { getLogoUrlFromDomainName } from '~/utils';
-import { capitalize } from '~/utils/string/capitalize';
+import { useRef } from 'react';
+import { useRecoilState } from 'recoil';
+import { isDefined } from 'twenty-ui';
const MOBILE_NAVIGATION_BAR_HEIGHT = 64;
type CommandGroupConfig = {
heading: string;
items?: any[];
- renderItem: (item: any) => {
- id: string;
- Icon?: IconComponent;
- label: string;
- to?: string;
- onClick?: () => void;
- key?: string;
- firstHotKey?: string;
- secondHotKey?: string;
- shouldCloseCommandMenuOnClick?: boolean;
- };
};
const StyledCommandMenu = styled.div`
@@ -122,705 +74,183 @@ const StyledEmpty = styled.div`
`;
export const CommandMenu = () => {
- const { toggleCommandMenu, onItemClick, closeCommandMenu } = useCommandMenu();
+ const { onItemClick, closeCommandMenu } = useCommandMenu();
const commandMenuRef = useRef(null);
- const openActivityRightDrawer = useOpenActivityRightDrawer({
- objectNameSingular: CoreObjectNameSingular.Note,
- });
- const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
+
const [commandMenuSearch, setCommandMenuSearch] = useRecoilState(
commandMenuSearchState,
);
- const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms
- const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
-
- const setContextStoreTargetedRecordsRule = useSetRecoilComponentStateV2(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
- const setContextStoreNumberOfSelectedRecords = useSetRecoilComponentStateV2(
- contextStoreNumberOfSelectedRecordsComponentState,
- );
const isMobile = useIsMobile();
- const commandMenuCommands = useRecoilComponentValueV2(
- commandMenuCommandsComponentSelector,
- );
-
- useScopedHotkeys(
- 'ctrl+k,meta+k',
- () => {
- closeKeyboardShortcutMenu();
- toggleCommandMenu();
- },
- AppHotkeyScope.CommandMenu,
- [toggleCommandMenu],
- );
-
- useScopedHotkeys(
- [Key.Escape],
- () => {
- closeCommandMenu();
- },
- AppHotkeyScope.CommandMenuOpen,
- [closeCommandMenu],
- );
-
- useScopedHotkeys(
- [Key.Backspace, Key.Delete],
- () => {
- if (!isNonEmptyString(commandMenuSearch)) {
- setContextStoreTargetedRecordsRule({
- mode: 'selection',
- selectedRecordIds: [],
- });
+ useCommandMenuHotKeys();
- setContextStoreNumberOfSelectedRecords(0);
- }
- },
- AppHotkeyScope.CommandMenuOpen,
- [closeCommandMenu],
- {
- preventDefault: false,
- },
- );
-
- const {
- matchesSearchFilterObjectRecordsQueryResult,
- matchesSearchFilterObjectRecordsLoading: loading,
- } = useMultiObjectSearch({
- excludedObjects: [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note],
- searchFilterValue: deferredCommandMenuSearch ?? undefined,
- limit: 3,
- });
-
- const { objectRecordsMap: matchesSearchFilterObjectRecords } =
- useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap({
- multiObjectRecordsQueryResult:
- matchesSearchFilterObjectRecordsQueryResult,
- });
-
- const { loading: isNotesLoading, records: notes } = useFindManyRecords({
- skip: !isCommandMenuOpened,
- objectNameSingular: CoreObjectNameSingular.Note,
- filter: deferredCommandMenuSearch
- ? makeOrFilterVariables([
- { title: { ilike: `%${deferredCommandMenuSearch}%` } },
- { body: { ilike: `%${deferredCommandMenuSearch}%` } },
- ])
- : undefined,
- limit: 3,
- });
-
- const { loading: isTasksLoading, records: tasks } = useFindManyRecords({
- skip: !isCommandMenuOpened,
- objectNameSingular: CoreObjectNameSingular.Task,
- filter: deferredCommandMenuSearch
- ? makeOrFilterVariables([
- { title: { ilike: `%${deferredCommandMenuSearch}%` } },
- { body: { ilike: `%${deferredCommandMenuSearch}%` } },
- ])
- : undefined,
- limit: 3,
+ useListenClickOutside({
+ refs: [commandMenuRef],
+ callback: closeCommandMenu,
+ listenerId: 'COMMAND_MENU_LISTENER_ID',
+ hotkeyScope: AppHotkeyScope.CommandMenuOpen,
});
- const people = matchesSearchFilterObjectRecords.people?.map(
- (people) => people.record,
- );
- const companies = matchesSearchFilterObjectRecords.companies?.map(
- (companies) => companies.record,
- );
- const opportunities = matchesSearchFilterObjectRecords.opportunities?.map(
- (opportunities) => opportunities.record,
- );
-
- const customObjectRecordsMap = useMemo(() => {
- return Object.fromEntries(
- Object.entries(matchesSearchFilterObjectRecords).filter(
- ([namePlural, records]) =>
- ![
- CoreObjectNamePlural.Person,
- CoreObjectNamePlural.Opportunity,
- CoreObjectNamePlural.Company,
- ].includes(namePlural as CoreObjectNamePlural) && !isEmpty(records),
- ),
- );
- }, [matchesSearchFilterObjectRecords]);
-
- const peopleCommands = useMemo(
- () =>
- people?.map(({ id, name: { firstName, lastName } }) => ({
- id,
- label: `${firstName} ${lastName}`,
- to: `object/person/${id}`,
- shouldCloseCommandMenuOnClick: true,
- })),
- [people],
- );
-
- const companyCommands = useMemo(
- () =>
- companies?.map(({ id, name }) => ({
- id,
- label: name ?? '',
- to: `object/company/${id}`,
- shouldCloseCommandMenuOnClick: true,
- })),
- [companies],
- );
-
- const opportunityCommands = useMemo(
- () =>
- opportunities?.map(({ id, name }) => ({
- id,
- label: name ?? '',
- to: `object/opportunity/${id}`,
- shouldCloseCommandMenuOnClick: true,
- })),
- [opportunities],
- );
-
- const noteCommands = useMemo(
- () =>
- notes?.map((note) => ({
- id: note.id,
- label: note.title ?? '',
- to: '',
- onCommandClick: () => openActivityRightDrawer(note.id),
- shouldCloseCommandMenuOnClick: true,
- })),
- [notes, openActivityRightDrawer],
- );
-
- const tasksCommands = useMemo(
- () =>
- tasks?.map((task) => ({
- id: task.id,
- label: task.title ?? '',
- to: '',
- onCommandClick: () => openActivityRightDrawer(task.id),
- shouldCloseCommandMenuOnClick: true,
- })),
- [tasks, openActivityRightDrawer],
- );
-
- const customObjectCommands = useMemo(() => {
- const customObjectCommandsArray: Command[] = [];
- Object.values(customObjectRecordsMap).forEach((objectRecords) => {
- customObjectCommandsArray.push(
- ...objectRecords.map((objectRecord) => ({
- id: objectRecord.record.id,
- label: objectRecord.recordIdentifier.name,
- to: `object/${objectRecord.objectMetadataItem.nameSingular}/${objectRecord.record.id}`,
- shouldCloseCommandMenuOnClick: true,
- })),
- );
- });
-
- return customObjectCommandsArray;
- }, [customObjectRecordsMap]);
-
- const otherCommands = useMemo(() => {
- const commandsArray: Command[] = [];
- if (peopleCommands?.length > 0) {
- commandsArray.push(...(peopleCommands as Command[]));
- }
- if (companyCommands?.length > 0) {
- commandsArray.push(...(companyCommands as Command[]));
- }
- if (opportunityCommands?.length > 0) {
- commandsArray.push(...(opportunityCommands as Command[]));
- }
- if (noteCommands?.length > 0) {
- commandsArray.push(...(noteCommands as Command[]));
- }
- if (tasksCommands?.length > 0) {
- commandsArray.push(...(tasksCommands as Command[]));
- }
- if (customObjectCommands?.length > 0) {
- commandsArray.push(...(customObjectCommands as Command[]));
- }
- return commandsArray;
- }, [
+ const {
+ isNoResults,
+ isLoading,
+ copilotCommands,
+ matchingStandardActionRecordSelectionCommands,
+ matchingWorkflowRunRecordSelectionCommands,
+ matchingStandardActionGlobalCommands,
+ matchingWorkflowRunGlobalCommands,
+ matchingNavigateCommand,
peopleCommands,
companyCommands,
opportunityCommands,
noteCommands,
- customObjectCommands,
tasksCommands,
- ]);
-
- const checkInShortcuts = (cmd: Command, search: string) => {
- return (cmd.firstHotKey + (cmd.secondHotKey ?? ''))
- .toLowerCase()
- .includes(search.toLowerCase());
- };
-
- const checkInLabels = (cmd: Command, search: string) => {
- if (isNonEmptyString(cmd.label)) {
- return cmd.label.toLowerCase().includes(search.toLowerCase());
- }
- return false;
- };
-
- const matchingNavigateCommand = commandMenuCommands.filter(
- (cmd) =>
- (deferredCommandMenuSearch.length > 0
- ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
- checkInLabels(cmd, deferredCommandMenuSearch)
- : true) && cmd.type === CommandType.Navigate,
- );
-
- const matchingCreateCommand = commandMenuCommands.filter(
- (cmd) =>
- (deferredCommandMenuSearch.length > 0
- ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
- checkInLabels(cmd, deferredCommandMenuSearch)
- : true) && cmd.type === CommandType.Create,
- );
-
- const matchingStandardActionRecordSelectionCommands =
- commandMenuCommands.filter(
- (cmd) =>
- (deferredCommandMenuSearch.length > 0
- ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
- checkInLabels(cmd, deferredCommandMenuSearch)
- : true) &&
- cmd.type === CommandType.StandardAction &&
- cmd.scope === CommandScope.RecordSelection,
- );
-
- const matchingStandardActionGlobalCommands = commandMenuCommands.filter(
- (cmd) =>
- (deferredCommandMenuSearch.length > 0
- ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
- checkInLabels(cmd, deferredCommandMenuSearch)
- : true) &&
- cmd.type === CommandType.StandardAction &&
- cmd.scope === CommandScope.Global,
- );
-
- const matchingWorkflowRunRecordSelectionCommands = commandMenuCommands.filter(
- (cmd) =>
- (deferredCommandMenuSearch.length > 0
- ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
- checkInLabels(cmd, deferredCommandMenuSearch)
- : true) &&
- cmd.type === CommandType.WorkflowRun &&
- cmd.scope === CommandScope.RecordSelection,
- );
-
- const matchingWorkflowRunGlobalCommands = commandMenuCommands.filter(
- (cmd) =>
- (deferredCommandMenuSearch.length > 0
- ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
- checkInLabels(cmd, deferredCommandMenuSearch)
- : true) &&
- cmd.type === CommandType.WorkflowRun &&
- cmd.scope === CommandScope.Global,
- );
-
- useListenClickOutside({
- refs: [commandMenuRef],
- callback: closeCommandMenu,
- listenerId: 'COMMAND_MENU_LISTENER_ID',
- hotkeyScope: AppHotkeyScope.CommandMenuOpen,
+ customObjectCommands,
+ } = useMatchingCommandMenuCommands({
+ commandMenuSearch,
});
- const isCopilotEnabled = useIsFeatureEnabled('IS_COPILOT_ENABLED');
- const setCopilotQuery = useSetRecoilState(copilotQueryState);
- const openCopilotRightDrawer = useOpenCopilotRightDrawer();
-
- const copilotCommand: Command = {
- id: 'copilot',
- to: '', // TODO
- Icon: IconSparkles,
- label: 'Open Copilot',
- type: CommandType.Navigate,
- onCommandClick: () => {
- setCopilotQuery(deferredCommandMenuSearch);
- openCopilotRightDrawer();
- },
- };
-
- const copilotCommands: Command[] = isCopilotEnabled ? [copilotCommand] : [];
-
- const selectableItemIds = copilotCommands
- .map((cmd) => cmd.id)
- .concat(matchingStandardActionRecordSelectionCommands.map((cmd) => cmd.id))
- .concat(matchingWorkflowRunRecordSelectionCommands.map((cmd) => cmd.id))
- .concat(matchingStandardActionGlobalCommands.map((cmd) => cmd.id))
- .concat(matchingWorkflowRunGlobalCommands.map((cmd) => cmd.id))
- .concat(matchingCreateCommand.map((cmd) => cmd.id))
- .concat(matchingNavigateCommand.map((cmd) => cmd.id))
- .concat(people?.map((person) => person.id))
- .concat(companies?.map((company) => company.id))
- .concat(opportunities?.map((opportunity) => opportunity.id))
- .concat(notes?.map((note) => note.id))
- .concat(tasks?.map((task) => task.id))
- .concat(
- Object.values(customObjectRecordsMap)
- ?.map((objectRecords) =>
- objectRecords.map((objectRecord) => objectRecord.record.id),
- )
- .flat() ?? [],
- );
-
- const isNoResults =
- !matchingStandardActionRecordSelectionCommands.length &&
- !matchingWorkflowRunRecordSelectionCommands.length &&
- !matchingStandardActionGlobalCommands.length &&
- !matchingWorkflowRunGlobalCommands.length &&
- !matchingCreateCommand.length &&
- !matchingNavigateCommand.length &&
- !people?.length &&
- !companies?.length &&
- !notes?.length &&
- !tasks?.length &&
- !opportunities?.length &&
- isEmpty(customObjectRecordsMap);
-
- const isLoading = loading || isNotesLoading || isTasksLoading;
+ const selectableItems = copilotCommands
+ .concat(matchingStandardActionRecordSelectionCommands)
+ .concat(matchingWorkflowRunRecordSelectionCommands)
+ .concat(matchingStandardActionGlobalCommands)
+ .concat(matchingWorkflowRunGlobalCommands)
+ .concat(matchingNavigateCommand)
+ .concat(peopleCommands)
+ .concat(companyCommands)
+ .concat(opportunityCommands)
+ .concat(noteCommands)
+ .concat(tasksCommands)
+ .concat(customObjectCommands)
+ .filter(isDefined);
+
+ const selectableItemIds = selectableItems.map((item) => item.id);
const commandGroups: CommandGroupConfig[] = [
{
- heading: 'Navigate',
- items: matchingNavigateCommand,
- renderItem: (command) => ({
- id: command.id,
- Icon: command.Icon,
- label: command.label,
- to: command.to,
- onClick: command.onCommandClick,
- firstHotKey: command.firstHotKey,
- secondHotKey: command.secondHotKey,
- shouldCloseCommandMenuOnClick: command.shouldCloseCommandMenuOnClick,
- }),
+ heading: 'Copilot',
+ items: copilotCommands,
+ },
+ {
+ heading: 'Record Selection',
+ items: matchingStandardActionRecordSelectionCommands,
+ },
+ {
+ heading: 'Workflow Record Selection',
+ items: matchingWorkflowRunRecordSelectionCommands,
+ },
+ {
+ heading: 'View',
+ items: matchingStandardActionGlobalCommands,
},
{
- heading: 'Other',
- items: matchingCreateCommand,
- renderItem: (command) => ({
- id: command.id,
- Icon: command.Icon,
- label: command.label,
- to: command.to,
- onClick: command.onCommandClick,
- firstHotKey: command.firstHotKey,
- secondHotKey: command.secondHotKey,
- shouldCloseCommandMenuOnClick: command.shouldCloseCommandMenuOnClick,
- }),
+ heading: 'Workflows',
+ items: matchingWorkflowRunGlobalCommands,
+ },
+ {
+ heading: 'Navigate',
+ items: matchingNavigateCommand,
},
{
heading: 'People',
- items: people,
- renderItem: (person) => ({
- id: person.id,
- label: `${person.name.firstName} ${person.name.lastName}`,
- to: `object/person/${person.id}`,
- Icon: () => (
-
- ),
- firstHotKey: person.firstHotKey,
- secondHotKey: person.secondHotKey,
- shouldCloseCommandMenuOnClick: true,
- }),
+ items: peopleCommands,
},
{
heading: 'Companies',
- items: companies,
- renderItem: (company) => ({
- id: company.id,
- label: company.name,
- to: `object/company/${company.id}`,
- Icon: () => (
-
- ),
- firstHotKey: company.firstHotKey,
- secondHotKey: company.secondHotKey,
- shouldCloseCommandMenuOnClick: true,
- }),
+ items: companyCommands,
},
{
heading: 'Opportunities',
- items: opportunities,
- renderItem: (opportunity) => ({
- id: opportunity.id,
- label: opportunity.name ?? '',
- to: `object/opportunity/${opportunity.id}`,
- Icon: () => (
-
- ),
- shouldCloseCommandMenuOnClick: true,
- }),
+ items: opportunityCommands,
},
{
heading: 'Notes',
- items: notes,
- renderItem: (note) => ({
- id: note.id,
- Icon: IconNotes,
- label: note.title ?? '',
- onClick: () => openActivityRightDrawer(note.id),
- shouldCloseCommandMenuOnClick: true,
- }),
+ items: noteCommands,
},
{
heading: 'Tasks',
- items: tasks,
- renderItem: (task) => ({
- id: task.id,
- Icon: IconCheckbox,
- label: task.title ?? '',
- onClick: () => openActivityRightDrawer(task.id),
- shouldCloseCommandMenuOnClick: true,
- }),
+ items: tasksCommands,
+ },
+ {
+ heading: 'Custom Objects',
+ items: customObjectCommands,
},
- ...Object.entries(customObjectRecordsMap).map(
- ([customObjectNamePlural, objectRecords]): CommandGroupConfig => ({
- heading: capitalize(customObjectNamePlural),
- items: objectRecords,
- renderItem: (objectRecord) => ({
- key: objectRecord.record.id,
- id: objectRecord.record.id,
- label: objectRecord.recordIdentifier.name,
- to: `object/${objectRecord.objectMetadataItem.nameSingular}/${objectRecord.record.id}`,
- Icon: () => (
-
- ),
- shouldCloseCommandMenuOnClick: true,
- }),
- }),
- ),
];
return (
<>
- {isCommandMenuOpened && (
-
-
-
-
-
- {
- const command = [
- ...copilotCommands,
- ...commandMenuCommands,
- ...otherCommands,
- ].find((cmd) => cmd.id === itemId);
-
- if (isDefined(command)) {
- const {
- to,
- onCommandClick,
- shouldCloseCommandMenuOnClick,
- } = command;
-
- onItemClick({
- shouldCloseCommandMenuOnClick,
- onClick: onCommandClick,
- to,
- });
- }
- }}
- >
- {isNoResults && !isLoading && (
- No results found
- )}
- {isCopilotEnabled && (
-
-
- 2
- ? `"${deferredCommandMenuSearch}"`
- : ''
- }`}
- onClick={copilotCommand.onCommandClick}
- firstHotKey={copilotCommand.firstHotKey}
- secondHotKey={copilotCommand.secondHotKey}
- />
-
-
- )}
-
- {matchingStandardActionRecordSelectionCommands?.map(
- (standardActionrecordSelectionCommand) => (
-
-
-
- ),
- )}
- {matchingWorkflowRunRecordSelectionCommands?.map(
- (workflowRunRecordSelectionCommand) => (
-
-
-
- ),
- )}
-
- {matchingStandardActionGlobalCommands?.length > 0 && (
-
- {matchingStandardActionGlobalCommands?.map(
- (standardActionGlobalCommand) => (
-
-
-
- ),
- )}
-
- )}
- {matchingWorkflowRunGlobalCommands?.length > 0 && (
-
- {matchingWorkflowRunGlobalCommands?.map(
- (workflowRunGlobalCommand) => (
-
+
+
+
+
+
+
+ {
+ const command = selectableItems.find(
+ (item) => item.id === itemId,
+ );
+
+ if (isDefined(command)) {
+ const {
+ to,
+ onCommandClick,
+ shouldCloseCommandMenuOnClick,
+ } = command;
+
+ onItemClick({
+ shouldCloseCommandMenuOnClick,
+ onClick: onCommandClick,
+ to,
+ });
+ }
+ }}
+ >
+ {isNoResults && !isLoading && (
+ No results found
+ )}
+ {commandGroups.map(({ heading, items }) =>
+ items?.length ? (
+
+ {items.map((item) => {
+ return (
+
- ),
- )}
+ );
+ })}
- )}
-
- {commandGroups.map(({ heading, items, renderItem }) =>
- items?.length ? (
-
- {items.map((item) => {
- const {
- id,
- Icon,
- label,
- to,
- onClick,
- key,
- firstHotKey,
- secondHotKey,
- shouldCloseCommandMenuOnClick,
- } = renderItem(item);
- return (
-
-
-
- );
- })}
-
- ) : null,
- )}
-
-
-
-
-
- )}
+ ) : null,
+ )}
+
+
+
+
+
>
);
};
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
new file mode 100644
index 0000000000000..3575d20c9a0ad
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
@@ -0,0 +1,54 @@
+import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
+import { RecordAgnosticActionsSetterEffect } from '@/action-menu/actions/record-agnostic-actions/components/RecordAgnosticActionsSetterEffect';
+import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
+import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { CommandMenu } from '@/command-menu/components/CommandMenu';
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
+import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
+import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
+import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
+import { useRecoilValue } from 'recoil';
+
+export const CommandMenuContainer = () => {
+ const { toggleCommandMenu } = useCommandMenu();
+ const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
+
+ const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
+ const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
+
+ useScopedHotkeys(
+ 'ctrl+k,meta+k',
+ () => {
+ closeKeyboardShortcutMenu();
+ toggleCommandMenu();
+ },
+ AppHotkeyScope.CommandMenu,
+ [toggleCommandMenu],
+ );
+
+ return (
+
+
+
+
+ {isWorkflowEnabled && }
+
+ {isCommandMenuOpened && }
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuDefaultSelectionEffect.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuDefaultSelectionEffect.tsx
new file mode 100644
index 0000000000000..e12233a8de4ad
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuDefaultSelectionEffect.tsx
@@ -0,0 +1,27 @@
+import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
+import { useEffect } from 'react';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-ui';
+
+export const CommandMenuDefaultSelectionEffect = ({
+ selectableItemIds,
+}: {
+ selectableItemIds: string[];
+}) => {
+ const { setSelectedItemId, selectedItemIdState } =
+ useSelectableList('command-menu-list');
+
+ const selectedItemId = useRecoilValue(selectedItemIdState);
+
+ useEffect(() => {
+ if (isDefined(selectedItemId)) {
+ return;
+ }
+
+ if (selectableItemIds.length > 0) {
+ setSelectedItemId(selectableItemIds[0]);
+ }
+ }, [selectableItemIds, selectedItemId, setSelectedItemId]);
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx
index f9549c7372e92..2ae691d15c490 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenu.test.tsx
@@ -4,9 +4,7 @@ import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
-import { commandMenuCommandsComponentSelector } from '@/command-menu/states/commandMenuCommandsSelector';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
@@ -24,15 +22,10 @@ const renderHooks = () => {
() => {
const commandMenu = useCommandMenu();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
- const commandMenuCommands = useRecoilComponentValueV2(
- commandMenuCommandsComponentSelector,
- 'command-menu',
- );
return {
commandMenu,
isCommandMenuOpened,
- commandMenuCommands,
};
},
{
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
index f8d178cd3920f..3b32e5a6e1c8a 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
@@ -9,6 +9,7 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { isDefined } from '~/utils/isDefined';
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
@@ -126,6 +127,21 @@ export const useCommandMenu = () => {
);
}
+ const actionMenuEntries = snapshot
+ .getLoadable(
+ actionMenuEntriesComponentState.atomFamily({
+ instanceId: mainContextStoreComponentInstanceId,
+ }),
+ )
+ .getValue();
+
+ set(
+ actionMenuEntriesComponentState.atomFamily({
+ instanceId: 'command-menu',
+ }),
+ actionMenuEntries,
+ );
+
setIsCommandMenuOpened(true);
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
},
@@ -188,6 +204,13 @@ export const useCommandMenu = () => {
null,
);
+ set(
+ actionMenuEntriesComponentState.atomFamily({
+ instanceId: 'command-menu',
+ }),
+ new Map(),
+ );
+
if (isCommandMenuOpened) {
setIsCommandMenuOpened(false);
resetSelectedItem();
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCommands.tsx b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCommands.tsx
new file mode 100644
index 0000000000000..42f71648cc73b
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCommands.tsx
@@ -0,0 +1,314 @@
+import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { useOpenCopilotRightDrawer } from '@/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer';
+import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState';
+import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
+import { Note } from '@/activities/types/Note';
+import { Task } from '@/activities/types/Task';
+import { COMMAND_MENU_NAVIGATE_COMMANDS } from '@/command-menu/constants/CommandMenuNavigateCommands';
+import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
+import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
+import {
+ Command,
+ CommandScope,
+ CommandType,
+} from '@/command-menu/types/Command';
+import { Company } from '@/companies/types/Company';
+import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
+import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
+import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
+import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
+import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
+import isEmpty from 'lodash.isempty';
+import { useMemo } from 'react';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
+import { Avatar, IconCheckbox, IconNotes, IconSparkles } from 'twenty-ui';
+import { useDebounce } from 'use-debounce';
+import { getLogoUrlFromDomainName } from '~/utils';
+
+export const useCommandMenuCommands = () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentSelector,
+ );
+ const openActivityRightDrawer = useOpenActivityRightDrawer({
+ objectNameSingular: CoreObjectNameSingular.Note,
+ });
+ const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
+ const commandMenuSearch = useRecoilValue(commandMenuSearchState);
+ const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms
+
+ const isCopilotEnabled = useIsFeatureEnabled('IS_COPILOT_ENABLED');
+ const setCopilotQuery = useSetRecoilState(copilotQueryState);
+ const openCopilotRightDrawer = useOpenCopilotRightDrawer();
+
+ const copilotCommand: Command = {
+ id: 'copilot',
+ to: '', // TODO
+ Icon: IconSparkles,
+ label: 'Open Copilot',
+ type: CommandType.Navigate,
+ onCommandClick: () => {
+ setCopilotQuery(deferredCommandMenuSearch);
+ openCopilotRightDrawer();
+ },
+ };
+
+ const copilotCommands: Command[] = isCopilotEnabled ? [copilotCommand] : [];
+
+ const navigateCommands = Object.values(COMMAND_MENU_NAVIGATE_COMMANDS);
+
+ const actionRecordSelectionCommands: Command[] = actionMenuEntries
+ ?.filter(
+ (actionMenuEntry) =>
+ actionMenuEntry.type === ActionMenuEntryType.Standard &&
+ actionMenuEntry.scope === ActionMenuEntryScope.RecordSelection,
+ )
+ ?.map((actionMenuEntry) => ({
+ id: actionMenuEntry.key,
+ label: actionMenuEntry.label,
+ Icon: actionMenuEntry.Icon,
+ onCommandClick: actionMenuEntry.onClick,
+ type: CommandType.StandardAction,
+ scope: CommandScope.RecordSelection,
+ }));
+
+ const actionGlobalCommands: Command[] = actionMenuEntries
+ ?.filter(
+ (actionMenuEntry) =>
+ actionMenuEntry.type === ActionMenuEntryType.Standard &&
+ actionMenuEntry.scope === ActionMenuEntryScope.Global,
+ )
+ ?.map((actionMenuEntry) => ({
+ id: actionMenuEntry.key,
+ label: actionMenuEntry.label,
+ Icon: actionMenuEntry.Icon,
+ onCommandClick: actionMenuEntry.onClick,
+ type: CommandType.StandardAction,
+ scope: CommandScope.Global,
+ }));
+
+ const workflowRunRecordSelectionCommands: Command[] = actionMenuEntries
+ ?.filter(
+ (actionMenuEntry) =>
+ actionMenuEntry.type === ActionMenuEntryType.WorkflowRun &&
+ actionMenuEntry.scope === ActionMenuEntryScope.RecordSelection,
+ )
+ ?.map((actionMenuEntry) => ({
+ id: actionMenuEntry.key,
+ label: actionMenuEntry.label,
+ Icon: actionMenuEntry.Icon,
+ onCommandClick: actionMenuEntry.onClick,
+ type: CommandType.WorkflowRun,
+ scope: CommandScope.RecordSelection,
+ }));
+
+ const workflowRunGlobalCommands: Command[] = actionMenuEntries
+ ?.filter(
+ (actionMenuEntry) =>
+ actionMenuEntry.type === ActionMenuEntryType.WorkflowRun &&
+ actionMenuEntry.scope === ActionMenuEntryScope.Global,
+ )
+ ?.map((actionMenuEntry) => ({
+ id: actionMenuEntry.key,
+ label: actionMenuEntry.label,
+ Icon: actionMenuEntry.Icon,
+ onCommandClick: actionMenuEntry.onClick,
+ type: CommandType.WorkflowRun,
+ scope: CommandScope.Global,
+ }));
+
+ const {
+ matchesSearchFilterObjectRecordsQueryResult,
+ matchesSearchFilterObjectRecordsLoading: loading,
+ } = useMultiObjectSearch({
+ excludedObjects: [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note],
+ searchFilterValue: deferredCommandMenuSearch ?? undefined,
+ limit: 3,
+ });
+
+ const { objectRecordsMap: matchesSearchFilterObjectRecords } =
+ useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap({
+ multiObjectRecordsQueryResult:
+ matchesSearchFilterObjectRecordsQueryResult,
+ });
+
+ const { loading: isNotesLoading, records: notes } = useFindManyRecords({
+ skip: !isCommandMenuOpened,
+ objectNameSingular: CoreObjectNameSingular.Note,
+ filter: deferredCommandMenuSearch
+ ? makeOrFilterVariables([
+ { title: { ilike: `%${deferredCommandMenuSearch}%` } },
+ { body: { ilike: `%${deferredCommandMenuSearch}%` } },
+ ])
+ : undefined,
+ limit: 3,
+ });
+
+ const { loading: isTasksLoading, records: tasks } = useFindManyRecords({
+ skip: !isCommandMenuOpened,
+ objectNameSingular: CoreObjectNameSingular.Task,
+ filter: deferredCommandMenuSearch
+ ? makeOrFilterVariables([
+ { title: { ilike: `%${deferredCommandMenuSearch}%` } },
+ { body: { ilike: `%${deferredCommandMenuSearch}%` } },
+ ])
+ : undefined,
+ limit: 3,
+ });
+
+ const people = matchesSearchFilterObjectRecords.people?.map(
+ (people) => people.record,
+ );
+ const companies = matchesSearchFilterObjectRecords.companies?.map(
+ (companies) => companies.record,
+ );
+ const opportunities = matchesSearchFilterObjectRecords.opportunities?.map(
+ (opportunities) => opportunities.record,
+ );
+
+ const peopleCommands = useMemo(
+ () =>
+ people?.map(({ id, name: { firstName, lastName } }) => ({
+ id,
+ label: `${firstName} ${lastName}`,
+ to: `object/person/${id}`,
+ shouldCloseCommandMenuOnClick: true,
+ Icon: () => (
+
+ ),
+ })),
+ [people],
+ );
+
+ const companyCommands = useMemo(
+ () =>
+ companies?.map((company) => ({
+ id: company.id,
+ label: company.name ?? '',
+ to: `object/company/${company.id}`,
+ shouldCloseCommandMenuOnClick: true,
+ Icon: () => (
+
+ ),
+ })),
+ [companies],
+ );
+
+ const opportunityCommands = useMemo(
+ () =>
+ opportunities?.map(({ id, name }) => ({
+ id,
+ label: name ?? '',
+ to: `object/opportunity/${id}`,
+ shouldCloseCommandMenuOnClick: true,
+ Icon: () => (
+
+ ),
+ })),
+ [opportunities],
+ );
+
+ const noteCommands = useMemo(
+ () =>
+ notes?.map((note) => ({
+ id: note.id,
+ label: note.title ?? '',
+ to: '',
+ onCommandClick: () => openActivityRightDrawer(note.id),
+ shouldCloseCommandMenuOnClick: true,
+ Icon: IconNotes,
+ })),
+ [notes, openActivityRightDrawer],
+ );
+
+ const tasksCommands = useMemo(
+ () =>
+ tasks?.map((task) => ({
+ id: task.id,
+ label: task.title ?? '',
+ to: '',
+ onCommandClick: () => openActivityRightDrawer(task.id),
+ shouldCloseCommandMenuOnClick: true,
+ Icon: IconCheckbox,
+ })),
+ [tasks, openActivityRightDrawer],
+ );
+
+ const customObjectRecordsMap = useMemo(() => {
+ return Object.fromEntries(
+ Object.entries(matchesSearchFilterObjectRecords).filter(
+ ([namePlural, records]) =>
+ ![
+ CoreObjectNamePlural.Person,
+ CoreObjectNamePlural.Opportunity,
+ CoreObjectNamePlural.Company,
+ ].includes(namePlural as CoreObjectNamePlural) && !isEmpty(records),
+ ),
+ );
+ }, [matchesSearchFilterObjectRecords]);
+
+ const customObjectCommands = useMemo(() => {
+ const customObjectCommandsArray: Command[] = [];
+ Object.values(customObjectRecordsMap).forEach((objectRecords) => {
+ customObjectCommandsArray.push(
+ ...objectRecords.map((objectRecord) => ({
+ id: objectRecord.record.id,
+ label: objectRecord.recordIdentifier.name,
+ to: `object/${objectRecord.objectMetadataItem.nameSingular}/${objectRecord.record.id}`,
+ shouldCloseCommandMenuOnClick: true,
+ Icon: () => (
+
+ ),
+ })),
+ );
+ });
+
+ return customObjectCommandsArray;
+ }, [customObjectRecordsMap]);
+
+ const isLoading = loading || isNotesLoading || isTasksLoading;
+
+ return {
+ copilotCommands,
+ navigateCommands,
+ actionRecordSelectionCommands,
+ actionGlobalCommands,
+ workflowRunRecordSelectionCommands,
+ workflowRunGlobalCommands,
+ peopleCommands,
+ companyCommands,
+ opportunityCommands,
+ noteCommands,
+ tasksCommands,
+ customObjectCommands,
+ isLoading,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
new file mode 100644
index 0000000000000..f905b29ceec67
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
@@ -0,0 +1,54 @@
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
+import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
+import { isNonEmptyString } from '@sniptt/guards';
+import { useRecoilValue } from 'recoil';
+import { Key } from 'ts-key-enum';
+
+export const useCommandMenuHotKeys = () => {
+ const { closeCommandMenu } = useCommandMenu();
+
+ const commandMenuSearch = useRecoilValue(commandMenuSearchState);
+
+ const setContextStoreTargetedRecordsRule = useSetRecoilComponentStateV2(
+ contextStoreTargetedRecordsRuleComponentState,
+ 'command-menu',
+ );
+
+ const setContextStoreNumberOfSelectedRecords = useSetRecoilComponentStateV2(
+ contextStoreNumberOfSelectedRecordsComponentState,
+ 'command-menu',
+ );
+
+ useScopedHotkeys(
+ [Key.Escape],
+ () => {
+ closeCommandMenu();
+ },
+ AppHotkeyScope.CommandMenuOpen,
+ [closeCommandMenu],
+ );
+
+ useScopedHotkeys(
+ [Key.Backspace, Key.Delete],
+ () => {
+ if (!isNonEmptyString(commandMenuSearch)) {
+ setContextStoreTargetedRecordsRule({
+ mode: 'selection',
+ selectedRecordIds: [],
+ });
+
+ setContextStoreNumberOfSelectedRecords(0);
+ }
+ },
+ AppHotkeyScope.CommandMenuOpen,
+ [closeCommandMenu],
+ {
+ preventDefault: false,
+ },
+ );
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useMatchCommands.ts b/packages/twenty-front/src/modules/command-menu/hooks/useMatchCommands.ts
new file mode 100644
index 0000000000000..42bf653a3d008
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useMatchCommands.ts
@@ -0,0 +1,37 @@
+import { Command } from '@/command-menu/types/Command';
+import { isNonEmptyString } from '@sniptt/guards';
+import { useDebounce } from 'use-debounce';
+
+export const useMatchCommands = ({
+ commandMenuSearch,
+}: {
+ commandMenuSearch: string;
+}) => {
+ const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms
+
+ const checkInShortcuts = (cmd: Command, search: string) => {
+ return (cmd.firstHotKey + (cmd.secondHotKey ?? ''))
+ .toLowerCase()
+ .includes(search.toLowerCase());
+ };
+
+ const checkInLabels = (cmd: Command, search: string) => {
+ if (isNonEmptyString(cmd.label)) {
+ return cmd.label.toLowerCase().includes(search.toLowerCase());
+ }
+ return false;
+ };
+
+ const matchCommands = (commands: Command[]) => {
+ return commands.filter((cmd) =>
+ deferredCommandMenuSearch.length > 0
+ ? checkInShortcuts(cmd, deferredCommandMenuSearch) ||
+ checkInLabels(cmd, deferredCommandMenuSearch)
+ : true,
+ );
+ };
+
+ return {
+ matchCommands,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useMatchingCommandMenuCommands.ts b/packages/twenty-front/src/modules/command-menu/hooks/useMatchingCommandMenuCommands.ts
new file mode 100644
index 0000000000000..b4607ada7f02d
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useMatchingCommandMenuCommands.ts
@@ -0,0 +1,73 @@
+import { useCommandMenuCommands } from '@/command-menu/hooks/useCommandMenuCommands';
+import { useMatchCommands } from '@/command-menu/hooks/useMatchCommands';
+
+export const useMatchingCommandMenuCommands = ({
+ commandMenuSearch,
+}: {
+ commandMenuSearch: string;
+}) => {
+ const { matchCommands } = useMatchCommands({ commandMenuSearch });
+
+ const {
+ copilotCommands,
+ navigateCommands,
+ actionRecordSelectionCommands,
+ actionGlobalCommands,
+ workflowRunRecordSelectionCommands,
+ workflowRunGlobalCommands,
+ peopleCommands,
+ companyCommands,
+ opportunityCommands,
+ noteCommands,
+ tasksCommands,
+ customObjectCommands,
+ isLoading,
+ } = useCommandMenuCommands();
+
+ const matchingNavigateCommand = matchCommands(navigateCommands);
+
+ const matchingStandardActionRecordSelectionCommands = matchCommands(
+ actionRecordSelectionCommands,
+ );
+
+ const matchingStandardActionGlobalCommands =
+ matchCommands(actionGlobalCommands);
+
+ const matchingWorkflowRunRecordSelectionCommands = matchCommands(
+ workflowRunRecordSelectionCommands,
+ );
+
+ const matchingWorkflowRunGlobalCommands = matchCommands(
+ workflowRunGlobalCommands,
+ );
+
+ const isNoResults =
+ !matchingStandardActionRecordSelectionCommands.length &&
+ !matchingWorkflowRunRecordSelectionCommands.length &&
+ !matchingStandardActionGlobalCommands.length &&
+ !matchingWorkflowRunGlobalCommands.length &&
+ !matchingNavigateCommand.length &&
+ !peopleCommands?.length &&
+ !companyCommands?.length &&
+ !opportunityCommands?.length &&
+ !noteCommands?.length &&
+ !tasksCommands?.length &&
+ !customObjectCommands?.length;
+
+ return {
+ isNoResults,
+ isLoading,
+ copilotCommands,
+ matchingStandardActionRecordSelectionCommands,
+ matchingWorkflowRunRecordSelectionCommands,
+ matchingStandardActionGlobalCommands,
+ matchingWorkflowRunGlobalCommands,
+ matchingNavigateCommand,
+ peopleCommands,
+ companyCommands,
+ opportunityCommands,
+ noteCommands,
+ tasksCommands,
+ customObjectCommands,
+ };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/states/commandMenuCommandsSelector.ts b/packages/twenty-front/src/modules/command-menu/states/commandMenuCommandsSelector.ts
deleted file mode 100644
index bff43955cca80..0000000000000
--- a/packages/twenty-front/src/modules/command-menu/states/commandMenuCommandsSelector.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
-import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
-import { Command } from '@/command-menu/types/Command';
-import { computeCommandMenuCommands } from '@/command-menu/utils/computeCommandMenuCommands';
-import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
-
-export const commandMenuCommandsComponentSelector = createComponentSelectorV2<
- Command[]
->({
- key: 'commandMenuCommandsComponentSelector',
- componentInstanceContext: ActionMenuComponentInstanceContext,
- get:
- ({ instanceId }) =>
- ({ get }) => {
- const actionMenuEntries = get(
- actionMenuEntriesComponentSelector.selectorFamily({
- instanceId,
- }),
- );
-
- return computeCommandMenuCommands(actionMenuEntries);
- },
-});
diff --git a/packages/twenty-front/src/modules/command-menu/utils/computeCommandMenuCommands.ts b/packages/twenty-front/src/modules/command-menu/utils/computeCommandMenuCommands.ts
deleted file mode 100644
index 706bdd3c33a61..0000000000000
--- a/packages/twenty-front/src/modules/command-menu/utils/computeCommandMenuCommands.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import {
- ActionMenuEntry,
- ActionMenuEntryScope,
- ActionMenuEntryType,
-} from '@/action-menu/types/ActionMenuEntry';
-import { COMMAND_MENU_NAVIGATE_COMMANDS } from '@/command-menu/constants/CommandMenuNavigateCommands';
-import {
- Command,
- CommandScope,
- CommandType,
-} from '@/command-menu/types/Command';
-
-export const computeCommandMenuCommands = (
- actionMenuEntries: ActionMenuEntry[],
-): Command[] => {
- const navigateCommands = Object.values(COMMAND_MENU_NAVIGATE_COMMANDS);
-
- const actionCommands: Command[] = actionMenuEntries
- ?.filter(
- (actionMenuEntry) =>
- actionMenuEntry.type === ActionMenuEntryType.Standard,
- )
- ?.map((actionMenuEntry) => ({
- id: actionMenuEntry.key,
- label: actionMenuEntry.label,
- Icon: actionMenuEntry.Icon,
- onCommandClick: actionMenuEntry.onClick,
- type: CommandType.StandardAction,
- scope:
- actionMenuEntry.scope === ActionMenuEntryScope.RecordSelection
- ? CommandScope.RecordSelection
- : CommandScope.Global,
- }));
-
- const workflowRunCommands: Command[] = actionMenuEntries
- ?.filter(
- (actionMenuEntry) =>
- actionMenuEntry.type === ActionMenuEntryType.WorkflowRun,
- )
- ?.map((actionMenuEntry) => ({
- id: actionMenuEntry.key,
- label: actionMenuEntry.label,
- Icon: actionMenuEntry.Icon,
- onCommandClick: actionMenuEntry.onClick,
- type: CommandType.WorkflowRun,
- scope:
- actionMenuEntry.scope === ActionMenuEntryScope.RecordSelection
- ? CommandScope.RecordSelection
- : CommandScope.Global,
- }));
-
- return [...navigateCommands, ...actionCommands, ...workflowRunCommands];
-};
diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx
index dc5b37ce1cf51..d395310999761 100644
--- a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx
@@ -1,12 +1,5 @@
-import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
-import { RecordAgnosticActionsSetterEffect } from '@/action-menu/actions/record-agnostic-actions/components/RecordAgnosticActionsSetterEffect';
-import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
-import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
-import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { AuthModal } from '@/auth/components/AuthModal';
-import { CommandMenu } from '@/command-menu/components/CommandMenu';
-import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
-import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { CommandMenuContainer } from '@/command-menu/components/CommandMenuContainer';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
import { AppNavigationDrawer } from '@/navigation/components/AppNavigationDrawer';
@@ -18,8 +11,7 @@ import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/S
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { css, Global, useTheme } from '@emotion/react';
+import { Global, css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
import { Outlet } from 'react-router-dom';
@@ -77,9 +69,6 @@ export const DefaultLayout = () => {
const theme = useTheme();
const windowsWidth = useScreenSize().width;
const showAuthModal = useShowAuthModal();
- const { toggleCommandMenu } = useCommandMenu();
-
- const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
return (
<>
@@ -93,25 +82,7 @@ export const DefaultLayout = () => {
{!showAuthModal && (
<>
-
-
-
-
- {isWorkflowEnabled && }
-
-
-
-
-
+
>
)}
diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx
index 96badbceef291..12efb1b612f26 100644
--- a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableList.tsx
@@ -26,7 +26,7 @@ export const SelectableList = ({
}: SelectableListProps) => {
useSelectableListHotKeys(selectableListId, hotkeyScope);
- const { setSelectableItemIds, setSelectableListOnEnter } =
+ const { setSelectableItemIds, setSelectableListOnEnter, setSelectedItemId } =
useSelectableList(selectableListId);
useEffect(() => {
@@ -47,7 +47,12 @@ export const SelectableList = ({
if (isDefined(selectableItemIdArray)) {
setSelectableItemIds(arrayToChunks(selectableItemIdArray, 1));
}
- }, [selectableItemIdArray, selectableItemIdMatrix, setSelectableItemIds]);
+ }, [
+ selectableItemIdArray,
+ selectableItemIdMatrix,
+ setSelectableItemIds,
+ setSelectedItemId,
+ ]);
return (
diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts
index 3dadd58d1c2ed..e48ac7babde33 100644
--- a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts
+++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts
@@ -33,11 +33,23 @@ export const useSelectableList = (selectableListId?: string) => {
[selectedItemIdState, isSelectedItemIdSelector],
);
+ const setSelectedItemId = useRecoilCallback(
+ ({ set }) =>
+ (itemId: string) => {
+ resetSelectedItem();
+ set(selectedItemIdState, itemId);
+ set(isSelectedItemIdSelector(itemId), true);
+ },
+ [resetSelectedItem, selectedItemIdState, isSelectedItemIdSelector],
+ );
+
return {
selectableListId: scopeId,
setSelectableItemIds,
isSelectedItemIdSelector,
setSelectableListOnEnter,
resetSelectedItem,
+ setSelectedItemId,
+ selectedItemIdState,
};
};