Dynamic icon for command menu items (#19307)

Allow for dynamic icons in command menu items. For instance
`${objectMetadataItem.icon}` will resolve the object metadata item icon
This commit is contained in:
Raphaël Bosi
2026-04-03 15:41:56 +02:00
committed by GitHub
parent 119014f86d
commit 8543576dae
7 changed files with 82 additions and 45 deletions
@@ -24,7 +24,7 @@ jest.mock('twenty-shared/utils', () => {
return {
...actual,
interpolateCommandMenuItemLabel: jest.fn(
interpolateCommandMenuItemTemplate: jest.fn(
({ label }: { label?: string | null }) => label ?? null,
),
evaluateConditionalAvailabilityExpression: jest.fn(
@@ -2,14 +2,14 @@ import { FrontComponentCommandMenuItem } from '@/command-menu-item/display/compo
import { HeadlessCommandMenuItem } from '@/command-menu-item/display/components/HeadlessCommandMenuItem';
import { commandMenuItemsSelector } from '@/command-menu-item/server-items/common/states/commandMenuItemsSelector';
import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/server-items/common/utils/doesCommandMenuItemMatchObjectMetadataId';
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
import { type CommandMenuItemConfig } from '@/command-menu-item/types/CommandMenuItemConfig';
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
import { type CommandMenuContextApi } from 'twenty-shared/types';
import {
evaluateConditionalAvailabilityExpression,
interpolateCommandMenuItemLabel,
interpolateCommandMenuItemTemplate,
isDefined,
} from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
@@ -47,17 +47,22 @@ const buildCommandMenuItemFromFrontComponent = ({
getIcon,
commandMenuContextApi,
}: BuildCommandMenuItemFromFrontComponentParams): CommandMenuItemConfig => {
const displayLabel = interpolateCommandMenuItemLabel({
const displayLabel = interpolateCommandMenuItemTemplate({
label: item.label,
context: commandMenuContextApi,
});
const displayShortLabel = interpolateCommandMenuItemLabel({
const displayShortLabel = interpolateCommandMenuItemTemplate({
label: item.shortLabel,
context: commandMenuContextApi,
});
const Icon = getIcon(item.icon, COMMAND_MENU_DEFAULT_ICON);
const interpolatedIcon = interpolateCommandMenuItemTemplate({
label: item.icon,
context: commandMenuContextApi,
});
const Icon = getIcon(interpolatedIcon, COMMAND_MENU_DEFAULT_ICON);
const isHeadless = item.frontComponent?.isHeadless === true;
@@ -97,18 +102,22 @@ const buildCommandItemFromEngineKey = ({
getIcon,
commandMenuContextApi,
}: BuildCommandMenuItemFromStandardKeyParams): CommandMenuItemConfig => {
const Icon = getIcon(item.icon, COMMAND_MENU_DEFAULT_ICON);
const interpolatedIcon = interpolateCommandMenuItemTemplate({
label: item.icon,
context: commandMenuContextApi,
});
const Icon = getIcon(interpolatedIcon, COMMAND_MENU_DEFAULT_ICON);
return {
type,
key: `command-menu-item-engine-${item.id}`,
id: item.id,
scope,
label: interpolateCommandMenuItemLabel({
label: interpolateCommandMenuItemTemplate({
label: item.label,
context: commandMenuContextApi,
}),
shortLabel: interpolateCommandMenuItemLabel({
shortLabel: interpolateCommandMenuItemTemplate({
label: item.shortLabel,
context: commandMenuContextApi,
}),
@@ -15,7 +15,7 @@ import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants
import { styled } from '@linaria/react';
import { motion } from 'framer-motion';
import { useContext, useMemo } from 'react';
import { interpolateCommandMenuItemLabel } from 'twenty-shared/utils';
import { interpolateCommandMenuItemTemplate } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme-constants';
import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql';
@@ -73,7 +73,7 @@ export const PinnedCommandMenuItemButtonsEditMode = () => {
);
const interpolateLabel = (rawLabel: string | null | undefined) =>
interpolateCommandMenuItemLabel({
interpolateCommandMenuItemTemplate({
label: rawLabel,
context: commandMenuContextApi,
});
@@ -22,7 +22,7 @@ import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { CommandMenuContextApiPageType } from 'twenty-shared/types';
import {
interpolateCommandMenuItemLabel,
interpolateCommandMenuItemTemplate,
isDefined,
} from 'twenty-shared/utils';
import {
@@ -119,7 +119,7 @@ export const SidePanelCommandMenuItemEditPage = () => {
);
const getDisplayLabel = (item: CommandMenuItemFieldsFragment) =>
interpolateCommandMenuItemLabel({
interpolateCommandMenuItemTemplate({
label: item.label,
context: commandMenuContextApi,
}) ?? item.label;