diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts
index 5d4ba1c77..61b8ce087 100644
--- a/packages/twenty-front/src/generated-metadata/graphql.ts
+++ b/packages/twenty-front/src/generated-metadata/graphql.ts
@@ -392,6 +392,7 @@ export enum FeatureFlagKey {
IsAggregateQueryEnabled = 'IsAggregateQueryEnabled',
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
+ IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
IsCopilotEnabled = 'IsCopilotEnabled',
IsCrmMigrationEnabled = 'IsCrmMigrationEnabled',
IsEventObjectEnabled = 'IsEventObjectEnabled',
@@ -400,7 +401,6 @@ export enum FeatureFlagKey {
IsGmailSendEmailScopeEnabled = 'IsGmailSendEmailScopeEnabled',
IsJsonFilterEnabled = 'IsJsonFilterEnabled',
IsMicrosoftSyncEnabled = 'IsMicrosoftSyncEnabled',
- IsPageHeaderV2Enabled = 'IsPageHeaderV2Enabled',
IsPostgreSqlIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
IsSsoEnabled = 'IsSSOEnabled',
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx
index 1bd6c5271..ecc79a5e5 100644
--- a/packages/twenty-front/src/generated/graphql.tsx
+++ b/packages/twenty-front/src/generated/graphql.tsx
@@ -324,6 +324,7 @@ export enum FeatureFlagKey {
IsAggregateQueryEnabled = 'IsAggregateQueryEnabled',
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
+ IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
IsCopilotEnabled = 'IsCopilotEnabled',
IsCrmMigrationEnabled = 'IsCrmMigrationEnabled',
IsEventObjectEnabled = 'IsEventObjectEnabled',
@@ -332,7 +333,6 @@ export enum FeatureFlagKey {
IsGmailSendEmailScopeEnabled = 'IsGmailSendEmailScopeEnabled',
IsJsonFilterEnabled = 'IsJsonFilterEnabled',
IsMicrosoftSyncEnabled = 'IsMicrosoftSyncEnabled',
- IsPageHeaderV2Enabled = 'IsPageHeaderV2Enabled',
IsPostgreSqlIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
IsSsoEnabled = 'IsSSOEnabled',
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
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 5ae6cf1d6..15f8d0850 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
@@ -35,8 +35,8 @@ export const RecordActionMenuEntriesSetter = () => {
FeatureFlagKey.IsWorkflowEnabled,
);
- const isPageHeaderV2Enabled = useIsFeatureEnabled(
- FeatureFlagKey.IsPageHeaderV2Enabled,
+ const isCommandMenuV2Enabled = useIsFeatureEnabled(
+ FeatureFlagKey.IsCommandMenuV2Enabled,
);
if (
@@ -53,7 +53,7 @@ export const RecordActionMenuEntriesSetter = () => {
const actionConfig = getActionConfig(
objectMetadataItem,
- isPageHeaderV2Enabled,
+ isCommandMenuV2Enabled,
);
const actionsToRegister = isDefined(viewType)
diff --git a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts
index 9d2cd4b21..c63566eda 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts
+++ b/packages/twenty-front/src/modules/action-menu/actions/utils/getActionConfig.ts
@@ -8,7 +8,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const getActionConfig = (
objectMetadataItem: ObjectMetadataItem,
- isPageHeaderV2Enabled: boolean,
+ isCommandMenuV2Enabled: boolean,
) => {
switch (objectMetadataItem.nameSingular) {
case CoreObjectNameSingular.Workflow:
@@ -18,7 +18,7 @@ export const getActionConfig = (
case CoreObjectNameSingular.WorkflowRun:
return WORKFLOW_RUNS_ACTIONS_CONFIG;
default:
- return isPageHeaderV2Enabled
+ return isCommandMenuV2Enabled
? DEFAULT_ACTIONS_CONFIG_V2
: DEFAULT_ACTIONS_CONFIG_V1;
}
diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx
index 835322427..2590dadd2 100644
--- a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx
+++ b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx
@@ -25,8 +25,8 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
FeatureFlagKey.IsWorkflowEnabled,
);
- const isPageHeaderV2Enabled = useIsFeatureEnabled(
- FeatureFlagKey.IsPageHeaderV2Enabled,
+ const isCommandMenuV2Enabled = useIsFeatureEnabled(
+ FeatureFlagKey.IsCommandMenuV2Enabled,
);
const isMobile = useIsMobile();
@@ -54,7 +54,7 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
},
}}
>
- {isPageHeaderV2Enabled ? (
+ {isCommandMenuV2Enabled ? (
<>{!isMobile && }>
) : (
diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx
index a1df8e655..3d032945b 100644
--- a/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx
+++ b/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx
@@ -33,8 +33,8 @@ export const RecordShowActionMenu = ({
FeatureFlagKey.IsWorkflowEnabled,
);
- const isPageHeaderV2Enabled = useIsFeatureEnabled(
- FeatureFlagKey.IsPageHeaderV2Enabled,
+ const isCommandMenuV2Enabled = useIsFeatureEnabled(
+ FeatureFlagKey.IsCommandMenuV2Enabled,
);
// TODO: refactor RecordShowPageBaseHeader to use the context store
@@ -48,7 +48,7 @@ export const RecordShowActionMenu = ({
onActionExecutedCallback: () => {},
}}
>
- {isPageHeaderV2Enabled ? (
+ {isCommandMenuV2Enabled ? (
) : (
{
if (
isRightDrawerOpen &&
@@ -34,7 +41,12 @@ export const useOpenActivityRightDrawer = ({
return;
}
- setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
+ if (isCommandMenuV2Enabled) {
+ setHotkeyScope(AppHotkeyScope.CommandMenuOpen, { goto: false });
+ } else {
+ setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
+ }
+
setViewableRecordId(activityId);
setViewableRecordNameSingular(objectNameSingular);
openRightDrawer(RightDrawerPages.ViewRecord);
diff --git a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
index 12fa5ab0f..48a98407d 100644
--- a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
+++ b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts
@@ -17,6 +17,9 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
+import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
+import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
+import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
export const useOpenCreateActivityDrawer = ({
@@ -60,6 +63,10 @@ export const useOpenCreateActivityDrawer = ({
isUpsertingActivityInDBState,
);
+ const isCommandMenuV2Enabled = useIsFeatureEnabled(
+ FeatureFlagKey.IsCommandMenuV2Enabled,
+ );
+
const openCreateActivityDrawer = async ({
targetableObjects,
customAssignee,
@@ -108,7 +115,12 @@ export const useOpenCreateActivityDrawer = ({
setActivityTargetableEntityArray([]);
}
- setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
+ if (isCommandMenuV2Enabled) {
+ setHotkeyScope(AppHotkeyScope.CommandMenuOpen, { goto: false });
+ } else {
+ setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
+ }
+
setViewableRecordId(activity.id);
setIsUpsertingActivityInDB(false);
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 66b152f46..c9c2f9609 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx
@@ -1,23 +1,19 @@
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 { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
+import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { Command } from '@/command-menu/types/Command';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
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 styled from '@emotion/styled';
-import { useRef } from 'react';
-import { useRecoilState } from 'recoil';
+import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui';
const MOBILE_NAVIGATION_BAR_HEIGHT = 64;
@@ -27,21 +23,6 @@ type CommandGroupConfig = {
items?: Command[];
};
-const StyledCommandMenu = styled.div`
- background: ${({ theme }) => theme.background.secondary};
- border-left: 1px solid ${({ theme }) => theme.border.color.medium};
- box-shadow: ${({ theme }) => theme.boxShadow.strong};
- font-family: ${({ theme }) => theme.font.family};
- height: 100%;
- overflow: hidden;
- padding: 0;
- position: fixed;
- right: 0%;
- top: 0%;
- width: ${() => (useIsMobile() ? '100%' : '500px')};
- z-index: 1000;
-`;
-
const StyledList = styled.div`
background: ${({ theme }) => theme.background.secondary};
overscroll-behavior: contain;
@@ -75,24 +56,12 @@ const StyledEmpty = styled.div`
`;
export const CommandMenu = () => {
- const { onItemClick, closeCommandMenu } = useCommandMenu();
- const commandMenuRef = useRef(null);
+ const { onItemClick } = useCommandMenuOnItemClick();
- const [commandMenuSearch, setCommandMenuSearch] = useRecoilState(
- commandMenuSearchState,
- );
+ const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const isMobile = useIsMobile();
- useCommandMenuHotKeys();
-
- useListenClickOutside({
- refs: [commandMenuRef],
- callback: closeCommandMenu,
- listenerId: 'COMMAND_MENU_LISTENER_ID',
- hotkeyScope: AppHotkeyScope.CommandMenuOpen,
- });
-
const {
isNoResults,
isLoading,
@@ -184,74 +153,66 @@ export const CommandMenu = () => {
-
-
-
-
-
- {
- const command = selectableItems.find(
- (item) => item.id === itemId,
- );
- if (isDefined(command)) {
- const {
- to,
- onCommandClick,
- shouldCloseCommandMenuOnClick,
- } = command;
+
+
+
+ {
+ const command = selectableItems.find(
+ (item) => item.id === itemId,
+ );
- onItemClick({
- shouldCloseCommandMenuOnClick,
- onClick: onCommandClick,
- to,
- });
- }
- }}
- >
- {isNoResults && !isLoading && (
- No results found
- )}
- {commandGroups.map(({ heading, items }) =>
- items?.length ? (
-
- {items.map((item) => {
- return (
-
-
-
- );
- })}
-
- ) : null,
- )}
-
-
-
-
-
+ 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 (
+
+
+
+ );
+ })}
+
+ ) : 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
index fe3802cd0..3fa14a772 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
@@ -3,35 +3,56 @@ import { RecordAgnosticActionsSetterEffect } from '@/action-menu/actions/record-
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 { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
+import styled from '@emotion/styled';
+import { useRef } from 'react';
import { useRecoilValue } from 'recoil';
+import { useIsMobile } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated/graphql';
-export const CommandMenuContainer = () => {
- const { toggleCommandMenu } = useCommandMenu();
- const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
+const StyledCommandMenu = styled.div`
+ background: ${({ theme }) => theme.background.secondary};
+ border-left: 1px solid ${({ theme }) => theme.border.color.medium};
+ box-shadow: ${({ theme }) => theme.boxShadow.strong};
+ font-family: ${({ theme }) => theme.font.family};
+ height: 100%;
+ overflow: hidden;
+ padding: 0;
+ position: fixed;
+ right: 0%;
+ top: 0%;
+ width: ${() => (useIsMobile() ? '100%' : '500px')};
+ z-index: 30;
+`;
+
+export const CommandMenuContainer = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ const { toggleCommandMenu, closeCommandMenu } = useCommandMenu();
const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled,
);
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
- useScopedHotkeys(
- 'ctrl+k,meta+k',
- () => {
- closeKeyboardShortcutMenu();
- toggleCommandMenu();
- },
- AppHotkeyScope.CommandMenu,
- [toggleCommandMenu],
- );
+ const commandMenuRef = useRef(null);
+
+ useCommandMenuHotKeys();
+
+ useListenClickOutside({
+ refs: [commandMenuRef],
+ callback: closeCommandMenu,
+ listenerId: 'COMMAND_MENU_LISTENER_ID',
+ hotkeyScope: AppHotkeyScope.CommandMenuOpen,
+ });
return (
{
{isWorkflowEnabled && }
- {isCommandMenuOpened && }
+ {isCommandMenuOpened && (
+
+ {children}
+
+ )}
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx
index c772da280..3907ccb47 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuItem.tsx
@@ -2,10 +2,9 @@ import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { IconArrowUpRight, IconComponent, MenuItemCommand } from 'twenty-ui';
+import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
-import { useCommandMenu } from '../hooks/useCommandMenu';
-
export type CommandMenuItemProps = {
label: string;
to?: string;
@@ -27,7 +26,7 @@ export const CommandMenuItem = ({
secondHotKey,
shouldCloseCommandMenuOnClick,
}: CommandMenuItemProps) => {
- const { onItemClick } = useCommandMenu();
+ const { onItemClick } = useCommandMenuOnItemClick();
if (isNonEmptyString(to) && !Icon) {
Icon = IconArrowUpRight;
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuPages.ts b/packages/twenty-front/src/modules/command-menu/components/CommandMenuPages.ts
new file mode 100644
index 000000000..e783f76de
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuPages.ts
@@ -0,0 +1,4 @@
+export enum CommandMenuPages {
+ Root = 'root',
+ ViewRecord = 'view-record',
+}
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuRouter.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuRouter.tsx
new file mode 100644
index 000000000..9c90f6e47
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuRouter.tsx
@@ -0,0 +1,23 @@
+import { CommandMenuContainer } from '@/command-menu/components/CommandMenuContainer';
+import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar';
+import { COMMAND_MENU_PAGES_CONFIG } from '@/command-menu/constants/CommandMenuPagesConfig';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-ui';
+
+export const CommandMenuRouter = () => {
+ const commandMenuPage = useRecoilValue(commandMenuPageState);
+
+ const commandMenuPageComponent = isDefined(commandMenuPage) ? (
+ COMMAND_MENU_PAGES_CONFIG.get(commandMenuPage)
+ ) : (
+ <>>
+ );
+
+ return (
+
+
+ {commandMenuPageComponent}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx
index 4bb2bc904..47cd9459a 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx
@@ -2,9 +2,11 @@ import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandM
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 { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
+import { useRecoilState } from 'recoil';
import { IconX, LightIconButton, isDefined, useIsMobile } from 'twenty-ui';
const StyledInputContainer = styled.div`
@@ -50,15 +52,11 @@ const StyledCloseButtonContainer = styled.div`
justify-content: center;
`;
-type CommandMenuTopBarProps = {
- commandMenuSearch: string;
- setCommandMenuSearch: (search: string) => void;
-};
+export const CommandMenuTopBar = () => {
+ const [commandMenuSearch, setCommandMenuSearch] = useRecoilState(
+ commandMenuSearchState,
+ );
-export const CommandMenuTopBar = ({
- commandMenuSearch,
- setCommandMenuSearch,
-}: CommandMenuTopBarProps) => {
const handleSearchChange = (event: React.ChangeEvent) => {
setCommandMenuSearch(event.target.value);
};
diff --git a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx
index fb0c8bf13..7a277d6f5 100644
--- a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx
@@ -16,6 +16,7 @@ import {
import { sleep } from '~/utils/sleep';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
@@ -43,7 +44,7 @@ const ContextStoreDecorator: Decorator = (Story) => {
const meta: Meta = {
title: 'Modules/CommandMenu/CommandMenu',
- component: CommandMenu,
+ component: CommandMenuRouter,
decorators: [
(Story) => {
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
diff --git a/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx
new file mode 100644
index 000000000..ca1a09097
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/constants/CommandMenuPagesConfig.tsx
@@ -0,0 +1,11 @@
+import { CommandMenu } from '@/command-menu/components/CommandMenu';
+import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
+import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
+
+export const COMMAND_MENU_PAGES_CONFIG = new Map<
+ CommandMenuPages,
+ React.ReactNode
+>([
+ [CommandMenuPages.Root, ],
+ [CommandMenuPages.ViewRecord, ],
+]);
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 2ae691d15..a5c88a6cb 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
@@ -1,5 +1,5 @@
import { renderHook } from '@testing-library/react';
-import { act } from 'react-dom/test-utils';
+import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot, useRecoilValue } from 'recoil';
@@ -69,20 +69,4 @@ describe('useCommandMenu', () => {
expect(result.current.isCommandMenuOpened).toBe(false);
});
-
- it('onItemClick', () => {
- const { result } = renderHooks();
- const onClickMock = jest.fn();
-
- act(() => {
- result.current.commandMenu.onItemClick({
- shouldCloseCommandMenuOnClick: true,
- onClick: onClickMock,
- to: '/test',
- });
- });
-
- expect(result.current.isCommandMenuOpened).toBe(true);
- expect(onClickMock).toHaveBeenCalledTimes(1);
- });
});
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuOnItemClick.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuOnItemClick.test.tsx
new file mode 100644
index 000000000..a5a82dca6
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useCommandMenuOnItemClick.test.tsx
@@ -0,0 +1,54 @@
+import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { RecoilRoot, useRecoilValue } from 'recoil';
+import { useCommandMenuOnItemClick } from '../useCommandMenuOnItemClick';
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+ {children}
+
+
+);
+
+const renderHooks = () => {
+ const { result } = renderHook(
+ () => {
+ const { onItemClick } = useCommandMenuOnItemClick();
+
+ const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
+
+ return {
+ onItemClick,
+ isCommandMenuOpened,
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+ return { result };
+};
+
+describe('useCommandMenuOnItemClick', () => {
+ it('onItemClick', () => {
+ const { result } = renderHooks();
+ const onClickMock = jest.fn();
+
+ act(() => {
+ result.current.onItemClick({
+ shouldCloseCommandMenuOnClick: true,
+ onClick: onClickMock,
+ to: '/test',
+ });
+ });
+
+ expect(result.current.isCommandMenuOpened).toBe(true);
+ expect(onClickMock).toHaveBeenCalledTimes(1);
+ });
+});
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 3b32e5a6e..fc23fbf96 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenu.ts
@@ -1,6 +1,3 @@
-import { isNonEmptyString } from '@sniptt/guards';
-import { useCallback } from 'react';
-import { useNavigate } from 'react-router-dom';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
@@ -10,6 +7,8 @@ import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { isDefined } from '~/utils/isDefined';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
+import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
@@ -17,10 +16,10 @@ import { contextStoreFiltersComponentState } from '@/context-store/states/contex
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
+import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
export const useCommandMenu = () => {
- const navigate = useNavigate();
const setIsCommandMenuOpened = useSetRecoilState(isCommandMenuOpenedState);
const { resetSelectedItem } = useSelectableList('command-menu-list');
const {
@@ -212,6 +211,8 @@ export const useCommandMenu = () => {
);
if (isCommandMenuOpened) {
+ set(viewableRecordIdState, null);
+ set(commandMenuPageState, CommandMenuPages.Root);
setIsCommandMenuOpened(false);
resetSelectedItem();
goBackToPreviousHotkeyScope();
@@ -238,39 +239,21 @@ export const useCommandMenu = () => {
[closeCommandMenu, openCommandMenu],
);
- const onItemClick = useCallback(
- ({
- shouldCloseCommandMenuOnClick,
- onClick,
- to,
- }: {
- shouldCloseCommandMenuOnClick?: boolean;
- onClick?: () => void;
- to?: string;
- }) => {
- if (
- isDefined(shouldCloseCommandMenuOnClick) &&
- shouldCloseCommandMenuOnClick
- ) {
- toggleCommandMenu();
- }
-
- if (isDefined(onClick)) {
- onClick();
- return;
- }
- if (isNonEmptyString(to)) {
- navigate(to);
- return;
- }
+ const openRecordInCommandMenu = useRecoilCallback(
+ ({ set }) => {
+ return (recordId: string) => {
+ openCommandMenu();
+ set(commandMenuPageState, CommandMenuPages.ViewRecord);
+ set(viewableRecordIdState, recordId);
+ };
},
- [navigate, toggleCommandMenu],
+ [openCommandMenu],
);
return {
openCommandMenu,
closeCommandMenu,
+ openRecordInCommandMenu,
toggleCommandMenu,
- onItemClick,
};
};
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
index f905b29ce..d237a4f41 100644
--- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuHotKeys.ts
@@ -2,6 +2,7 @@ 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 { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
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';
@@ -10,7 +11,7 @@ import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
export const useCommandMenuHotKeys = () => {
- const { closeCommandMenu } = useCommandMenu();
+ const { closeCommandMenu, toggleCommandMenu } = useCommandMenu();
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
@@ -24,6 +25,18 @@ export const useCommandMenuHotKeys = () => {
'command-menu',
);
+ const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
+
+ useScopedHotkeys(
+ 'ctrl+k,meta+k',
+ () => {
+ closeKeyboardShortcutMenu();
+ toggleCommandMenu();
+ },
+ AppHotkeyScope.CommandMenu,
+ [toggleCommandMenu],
+ );
+
useScopedHotkeys(
[Key.Escape],
() => {
diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuOnItemClick.ts b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuOnItemClick.ts
new file mode 100644
index 000000000..c656e82eb
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuOnItemClick.ts
@@ -0,0 +1,41 @@
+import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
+import { isNonEmptyString } from '@sniptt/guards';
+import { useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { isDefined } from 'twenty-ui';
+
+export const useCommandMenuOnItemClick = () => {
+ const { toggleCommandMenu } = useCommandMenu();
+ const navigate = useNavigate();
+
+ const onItemClick = useCallback(
+ ({
+ shouldCloseCommandMenuOnClick,
+ onClick,
+ to,
+ }: {
+ shouldCloseCommandMenuOnClick?: boolean;
+ onClick?: () => void;
+ to?: string;
+ }) => {
+ if (
+ isDefined(shouldCloseCommandMenuOnClick) &&
+ shouldCloseCommandMenuOnClick
+ ) {
+ toggleCommandMenu();
+ }
+
+ if (isDefined(onClick)) {
+ onClick();
+ return;
+ }
+ if (isNonEmptyString(to)) {
+ navigate(to);
+ return;
+ }
+ },
+ [navigate, toggleCommandMenu],
+ );
+
+ return { onItemClick };
+};
diff --git a/packages/twenty-front/src/modules/command-menu/states/commandMenuPageState.ts b/packages/twenty-front/src/modules/command-menu/states/commandMenuPageState.ts
new file mode 100644
index 000000000..d5a41917d
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu/states/commandMenuPageState.ts
@@ -0,0 +1,7 @@
+import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
+import { createState } from '@ui/utilities/state/utils/createState';
+
+export const commandMenuPageState = createState({
+ key: 'command-menu/commandMenuPageState',
+ defaultValue: CommandMenuPages.Root,
+});
diff --git a/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx b/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx
index 3ab95cd67..100abe308 100644
--- a/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx
+++ b/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx
@@ -13,13 +13,13 @@ export const PageFavoriteButton = ({
}: PageFavoriteButtonProps) => {
const title = isFavorite ? 'Remove from favorites' : 'Add to favorites';
- const isPageHeaderV2Enabled = useIsFeatureEnabled(
- FeatureFlagKey.IsPageHeaderV2Enabled,
+ const isCommandMenuV2Enabled = useIsFeatureEnabled(
+ FeatureFlagKey.IsCommandMenuV2Enabled,
);
return (
<>
- {isPageHeaderV2Enabled ? (
+ {isCommandMenuV2Enabled ? (