Support template variables in command menu item labels (#18707)

- Adds template variable interpolation (`${...}`) to command menu item
labels and short labels, enabling dynamic text like `Create new
${capitalize(objectMetadataItem.labelSingular)}` instead of static
`Create new record`.
- Supports `capitalize` and `lowercase` transform functions within
template expressions.
This commit is contained in:
Raphaël Bosi
2026-03-18 11:26:58 +01:00
committed by GitHub
parent 928194cee4
commit dedcf4e9b9
25 changed files with 673 additions and 236 deletions
@@ -38,7 +38,7 @@ export const CommandListItem = ({
id={action.key}
Icon={action.Icon}
label={getCommandMenuItemLabel(action.label)}
description={getCommandMenuItemLabel(action.description ?? '')}
description={getCommandMenuItemLabel(action.description)}
to={to}
onClick={disabled ? undefined : onClick}
hotKeys={action.hotKeys}
@@ -13,7 +13,7 @@ import { type MenuItemAccent } from 'twenty-ui/navigation';
export type CommandMenuItemDisplayProps = {
key: string;
label: MessageDescriptor | string;
label: Nullable<MessageDescriptor | string>;
shortLabel?: Nullable<MessageDescriptor | string>;
description?: MessageDescriptor | string;
Icon: IconComponent;
@@ -42,9 +42,7 @@ export const Default: Story = {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(
addToFavoritesCommandMenuItem?.shortLabel ?? '',
),
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.shortLabel),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
@@ -59,7 +57,7 @@ export const WithLink: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const menuItem = await canvas.findByText(
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.shortLabel ?? ''),
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.shortLabel),
);
expect(menuItem).toBeVisible();
expect(canvas.getByRole('link')).toHaveAttribute('href', '/objects/people');
@@ -60,9 +60,7 @@ export const Default: Story = {
expect(
await canvas.findByText(
getCommandMenuItemLabel(
addToFavoritesCommandMenuItem?.shortLabel ?? '',
),
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.shortLabel),
),
).toBeVisible();
},
@@ -65,9 +65,7 @@ export const AsButton: Story = {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(
addToFavoritesCommandMenuItem?.shortLabel ?? '',
),
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.shortLabel),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
@@ -103,7 +101,7 @@ export const AsListItem: Story = {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
@@ -139,7 +137,7 @@ export const AsDropdownItem: Story = {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
@@ -53,7 +53,7 @@ export const Default: Story = {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
@@ -68,7 +68,7 @@ export const WithLink: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const dropdownItem = await canvas.findByText(
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.label ?? ''),
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.label),
);
expect(dropdownItem).toBeVisible();
},
@@ -1,7 +1,7 @@
import { CommandListItem } from '@/command-menu-item/display/components/CommandListItem';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { NoSelectionRecordCommandKeys } from '@/command-menu-item/record/no-selection/types/NoSelectionRecordCommandKeys';
import { SingleRecordCommandKeys } from '@/command-menu-item/record/single-record/types/SingleRecordCommandKeys';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { getCommandMenuItemLabel } from '@/command-menu-item/utils/getCommandMenuItemLabel';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { type Meta, type StoryObj } from '@storybook/react-vite';
@@ -53,7 +53,7 @@ export const Default: Story = {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label ?? ''),
getCommandMenuItemLabel(addToFavoritesCommandMenuItem?.label),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
@@ -68,7 +68,7 @@ export const WithLink: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const listItem = await canvas.findByText(
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.label ?? ''),
getCommandMenuItemLabel(goToPeopleCommandMenuItem?.label),
);
expect(listItem).toBeVisible();
},