2439 improve command menu item display in right panel (#21020)

## Before

<img width="1512" height="389" alt="image"
src="https://github.com/user-attachments/assets/33274356-fb99-4a02-baa7-c324e6d151c6"
/>


## After

<img width="1512" height="357" alt="image"
src="https://github.com/user-attachments/assets/c0affb71-e920-4d64-b2f0-1bed53209ea5"
/>
This commit is contained in:
martmull
2026-05-29 13:37:27 +02:00
committed by GitHub
parent 10c0bed462
commit 4e5d47168c
9 changed files with 102 additions and 24 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
import { AppChip } from '@/applications/components/AppChip';
import { useApplicationChipData } from '@/applications/hooks/useApplicationChipData';
import { MenuItem } from 'twenty-ui/navigation';
type AppMenuItemProps = {
applicationId?: string | null;
text: string;
onClick?: () => void;
focused?: boolean;
disabled?: boolean;
};
export const AppMenuItem = ({
applicationId,
text,
onClick,
focused,
disabled,
}: AppMenuItemProps) => {
const { applicationChipData } = useApplicationChipData({
applicationId,
});
return (
<MenuItem
withIconContainer={true}
LeftIcon={() => (
<AppChip applicationId={applicationId} size={'md'} chipOnly />
)}
text={text}
contextualText={applicationChipData.name}
onClick={onClick}
focused={focused}
disabled={disabled}
/>
);
};
@@ -0,0 +1,25 @@
import { isTwentyStandardApplication } from '@/applications/utils/isTwentyStandardApplication';
import { isWorkspaceCustomApplication } from '@/applications/utils/isWorkspaceCustomApplication';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
export const useIsThirdPartyApplication = (
applicationId?: string | null,
): boolean => {
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
if (!isDefined(applicationId)) {
return false;
}
const application = currentWorkspace?.installedApplications.find(
(app) => app.id === applicationId,
);
return (
isDefined(application) &&
!isTwentyStandardApplication(application) &&
!isWorkspaceCustomApplication(application, currentWorkspace)
);
};
@@ -1,3 +1,5 @@
import { AppMenuItem } from '@/applications/components/AppMenuItem';
import { useIsThirdPartyApplication } from '@/applications/hooks/useIsThirdPartyApplication';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { CommandListItemLoader } from '@/command-menu-item/display/components/CommandListItemLoader';
import { interpolateCommandMenuItemFields } from '@/command-menu-item/display/utils/interpolateCommandMenuItemFields';
@@ -101,6 +103,8 @@ const CommandMenuItemSelectableRenderer = ({
selectableListInstanceId,
);
const isThirdPartyApp = useIsThirdPartyApplication(item.applicationId);
const onItemClick = () => {
if (disabled) {
return;
@@ -108,6 +112,20 @@ const CommandMenuItemSelectableRenderer = ({
handleClick();
};
if (isThirdPartyApp) {
return (
<SelectableListItem itemId={item.id} onEnter={onItemClick}>
<AppMenuItem
applicationId={item.applicationId}
text={getCommandMenuItemLabel(label)}
onClick={disabled ? undefined : handleClick}
focused={!disabled && isSelectedItemId}
disabled={disabled}
/>
</SelectableListItem>
);
}
if (displayType === 'listItem') {
const loaderComponent =
disabled && showDisabledLoader ? (
@@ -3,6 +3,7 @@ import { gql } from '@apollo/client';
export const COMMAND_MENU_ITEM_FRAGMENT = gql`
fragment CommandMenuItemFields on CommandMenuItem {
id
applicationId
workflowVersionId
frontComponentId
frontComponent {
@@ -1,7 +1,5 @@
import { AppChip } from '@/applications/components/AppChip';
import { useApplicationChipData } from '@/applications/hooks/useApplicationChipData';
import { AppMenuItem } from '@/applications/components/AppMenuItem';
import { type LogicFunction } from '@/logic-functions/types/LogicFunction';
import { MenuItem } from 'twenty-ui/navigation';
type ToolMenuItemProps = {
logicFunction: LogicFunction;
@@ -9,24 +7,12 @@ type ToolMenuItemProps = {
};
export const ToolMenuItem = ({ logicFunction, onClick }: ToolMenuItemProps) => {
const { applicationChipData } = useApplicationChipData({
applicationId: logicFunction.applicationId,
});
return (
<MenuItem
withIconContainer={true}
LeftIcon={() => (
<AppChip
applicationId={logicFunction.applicationId}
size={'md'}
chipOnly
/>
)}
<AppMenuItem
applicationId={logicFunction.applicationId}
text={
logicFunction.workflowActionTriggerSettings?.label ?? logicFunction.name
}
contextualText={applicationChipData.name}
onClick={onClick}
/>
);
@@ -64,5 +64,8 @@ describe('defineCommandMenuItem', () => {
expect(result.success).toBe(true);
expect(result.config.icon).toBe('IconRocket');
expect(result.config.isPinned).toBe(true);
expect(result.warnings).toContain(
'CommandMenuItem icon will be ignored in favor of application icon, you should remove it',
);
});
});
@@ -6,6 +6,7 @@ export const defineCommandMenuItem: DefineEntity<CommandMenuItemConfig> = (
config,
) => {
const errors: string[] = [];
const warnings: string[] = [];
if (!config.universalIdentifier) {
errors.push('CommandMenuItem must have a universalIdentifier');
@@ -21,5 +22,11 @@ export const defineCommandMenuItem: DefineEntity<CommandMenuItemConfig> = (
);
}
return createValidationResult({ config, errors });
if (config.icon) {
warnings.push(
'CommandMenuItem icon will be ignored in favor of application icon, you should remove it',
);
}
return createValidationResult({ config, errors, warnings });
};
@@ -3,6 +3,7 @@ import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsT
export type CommandMenuItemManifest = SyncableEntityOptions & {
label: string;
shortLabel?: string;
/** @deprecated icon will be ignored in favor of application icon */
icon?: string;
isPinned?: boolean;
availabilityType?: