feat: CommandMenuItem entity and FrontComponent support in command menu (#17555)

https://github.com/user-attachments/assets/6f172ffc-d43d-42fd-a26b-94f591fb767e
This commit is contained in:
Abdul Rahman
2026-01-30 22:05:06 +05:30
committed by GitHub
parent 6462ff6c5f
commit 51a2a90b0a
45 changed files with 899 additions and 34 deletions
@@ -28,8 +28,10 @@ export const CommandMenu = () => {
matchingStandardActionRecordSelectionActions,
matchingStandardActionObjectActions,
matchingWorkflowRunRecordSelectionActions,
matchingFrontComponentRecordSelectionActions,
matchingStandardActionGlobalActions,
matchingWorkflowRunGlobalActions,
matchingFrontComponentGlobalActions,
matchingNavigateActions,
fallbackActions,
matchingCreateRelatedRecordActions,
@@ -53,9 +55,9 @@ export const CommandMenu = () => {
const commandGroups: ActionGroupConfig[] = [
{
heading: t`Record Selection`,
items: matchingStandardActionRecordSelectionActions.concat(
matchingWorkflowRunRecordSelectionActions,
),
items: matchingStandardActionRecordSelectionActions
.concat(matchingWorkflowRunRecordSelectionActions)
.concat(matchingFrontComponentRecordSelectionActions),
},
{
heading: t`Create Related Record`,
@@ -69,6 +71,7 @@ export const CommandMenu = () => {
heading: t`Global`,
items: matchingStandardActionGlobalActions
.concat(matchingWorkflowRunGlobalActions)
.concat(matchingFrontComponentGlobalActions)
.concat(matchingNavigateActions),
},
{
@@ -2,6 +2,7 @@ import { CommandMenu } from '@/command-menu/components/CommandMenu';
import { CommandMenuAIChatThreadsPage } from '@/command-menu/pages/AIChatThreads/components/CommandMenuAIChatThreadsPage';
import { CommandMenuAskAIPage } from '@/command-menu/pages/ask-ai/components/CommandMenuAskAIPage';
import { CommandMenuCalendarEventPage } from '@/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage';
import { CommandMenuFrontComponentPage } from '@/command-menu/pages/front-component/components/CommandMenuFrontComponentPage';
import { CommandMenuMessageThreadPage } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadPage';
import { CommandMenuPageLayoutChartSettings } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutChartSettings';
import { CommandMenuPageLayoutGraphFilter } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter';
@@ -64,4 +65,5 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
CommandMenuPages.PageLayoutTabSettings,
<CommandMenuPageLayoutTabSettings />,
],
[CommandMenuPages.ViewFrontComponent, <CommandMenuFrontComponentPage />],
]);
@@ -41,6 +41,18 @@ export const useCommandMenuActions = () => {
action.scope === ActionScope.Global,
);
const frontComponentGlobalActions: ActionConfig[] = actions?.filter(
(action) =>
action.type === ActionType.FrontComponent &&
action.scope === ActionScope.Global,
);
const frontComponentRecordSelectionActions: ActionConfig[] = actions?.filter(
(action) =>
action.type === ActionType.FrontComponent &&
action.scope === ActionScope.RecordSelection,
);
const fallbackActions: ActionConfig[] = actions?.filter(
(action) => action.type === ActionType.Fallback,
);
@@ -58,6 +70,8 @@ export const useCommandMenuActions = () => {
actionObjectActions,
workflowRunRecordSelectionActions,
workflowRunGlobalActions,
frontComponentGlobalActions,
frontComponentRecordSelectionActions,
fallbackActions,
createRelatedRecordActions,
};
@@ -18,6 +18,8 @@ export const useMatchingCommandMenuActions = ({
actionGlobalActions,
workflowRunRecordSelectionActions,
workflowRunGlobalActions,
frontComponentGlobalActions,
frontComponentRecordSelectionActions,
fallbackActions,
createRelatedRecordActions,
} = useCommandMenuActions();
@@ -41,6 +43,12 @@ export const useMatchingCommandMenuActions = ({
workflowRunGlobalActions,
);
const matchingFrontComponentGlobalActions =
filterActionsWithCommandMenuSearch(frontComponentGlobalActions);
const matchingFrontComponentRecordSelectionActions =
filterActionsWithCommandMenuSearch(frontComponentRecordSelectionActions);
const matchingCreateRelatedRecordActions = filterActionsWithCommandMenuSearch(
createRelatedRecordActions,
);
@@ -48,8 +56,10 @@ export const useMatchingCommandMenuActions = ({
const noResults =
!matchingStandardActionRecordSelectionActions.length &&
!matchingWorkflowRunRecordSelectionActions.length &&
!matchingFrontComponentRecordSelectionActions.length &&
!matchingStandardActionGlobalActions.length &&
!matchingWorkflowRunGlobalActions.length &&
!matchingFrontComponentGlobalActions.length &&
!matchingStandardActionObjectActions.length &&
!matchingNavigateActions.length &&
!matchingCreateRelatedRecordActions.length;
@@ -59,8 +69,10 @@ export const useMatchingCommandMenuActions = ({
matchingStandardActionRecordSelectionActions,
matchingStandardActionObjectActions,
matchingWorkflowRunRecordSelectionActions,
matchingFrontComponentRecordSelectionActions,
matchingStandardActionGlobalActions,
matchingWorkflowRunGlobalActions,
matchingFrontComponentGlobalActions,
matchingNavigateActions,
matchingCreateRelatedRecordActions,
fallbackActions: noResults ? fallbackActions : [],
@@ -0,0 +1,47 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { viewableFrontComponentIdComponentState } from '@/command-menu/pages/front-component/states/viewableFrontComponentIdComponentState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useRecoilCallback } from 'recoil';
import { type IconComponent } from 'twenty-ui/display';
import { v4 } from 'uuid';
export const useOpenFrontComponentInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const openFrontComponentInCommandMenu = useRecoilCallback(
({ set }) =>
({
frontComponentId,
pageTitle,
pageIcon,
resetNavigationStack = false,
}: {
frontComponentId: string;
pageTitle: string;
pageIcon: IconComponent;
resetNavigationStack?: boolean;
}) => {
const pageComponentInstanceId = v4();
set(
viewableFrontComponentIdComponentState.atomFamily({
instanceId: pageComponentInstanceId,
}),
frontComponentId,
);
navigateCommandMenu({
page: CommandMenuPages.ViewFrontComponent,
pageTitle,
pageIcon,
pageId: pageComponentInstanceId,
resetNavigationStack,
});
},
[navigateCommandMenu],
);
return {
openFrontComponentInCommandMenu,
};
};
@@ -0,0 +1,16 @@
import { viewableFrontComponentIdComponentState } from '@/command-menu/pages/front-component/states/viewableFrontComponentIdComponentState';
import { FrontComponentRenderer } from '@/front-components/components/FrontComponentRenderer';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuFrontComponentPage = () => {
const viewableFrontComponentId = useRecoilComponentValue(
viewableFrontComponentIdComponentState,
);
if (!isDefined(viewableFrontComponentId)) {
return null;
}
return <FrontComponentRenderer frontComponentId={viewableFrontComponentId} />;
};
@@ -0,0 +1,10 @@
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const viewableFrontComponentIdComponentState = createComponentState<
string | null
>({
key: 'command-menu/viewable-front-component-id',
defaultValue: null,
componentInstanceContext: CommandMenuPageComponentInstanceContext,
});
@@ -21,4 +21,5 @@ export enum CommandMenuPages {
PageLayoutGraphFilter = 'page-layout-graph-filter',
PageLayoutIframeSettings = 'page-layout-iframe-settings',
PageLayoutTabSettings = 'page-layout-tab-settings',
ViewFrontComponent = 'view-front-component',
}