diff --git a/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue b/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue
index b3ad9d1daf..6ad44fc7bb 100644
--- a/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue
+++ b/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue
@@ -5,13 +5,14 @@ import type { RouteLocationRaw } from 'vue-router';
import ConditionalRouterLink from '../ConditionalRouterLink';
import N8nIcon from '../N8nIcon';
-import { type IconName } from '../N8nIcon/icons';
+import type { IconName } from '../N8nIcon/icons';
+import N8nText from '../N8nText';
type BaseItem = {
id: string;
title: string;
disabled?: boolean;
- icon?: IconName;
+ icon?: IconName | { type: 'icon'; value: IconName } | { type: 'emoji'; value: string };
route?: RouteLocationRaw;
};
@@ -99,7 +100,21 @@ defineExpose({
:disabled="subitem.disabled"
@click="emit('itemClick', $event)"
>
-
+
+
+
+
+ {{ subitem.icon.value }}
+
+
+
{{ subitem.title }}
diff --git a/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts b/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts
index 43d4e2e362..39b7a07cd2 100644
--- a/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts
+++ b/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts
@@ -4,8 +4,8 @@ import { createComponentRenderer } from '@/__tests__/render';
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
import type { Resource } from '@/Interface';
import type router from 'vue-router';
-import type { ProjectSharingData } from 'n8n-workflow';
import { waitAllPromises } from '@/__tests__/utils';
+import type { ProjectSharingData } from '@/types/projects.types';
const TEST_HOME_PROJECT: ProjectSharingData = vi.hoisted(() => ({
id: '1',
diff --git a/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts b/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts
index 071d1220d4..46180ea830 100644
--- a/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts
+++ b/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts
@@ -12,13 +12,27 @@ import { getResourcePermissions } from '@n8n/permissions';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
import type { Scope } from '@n8n/permissions';
import type { RouteLocationRaw } from 'vue-router';
-import { type IconName } from '@n8n/design-system/components/N8nIcon/icons';
+import { updatedIconSet, type IconName } from '@n8n/design-system/components/N8nIcon/icons';
+
+type ProjectIcon = IconName | { type: 'icon'; value: IconName } | { type: 'emoji'; value: string };
+
+const isIconName = (icon: unknown): icon is IconName =>
+ typeof icon === 'string' && Object.keys(updatedIconSet).includes(icon);
+
+const isProjectIcon = (icon: unknown): icon is ProjectIcon =>
+ isIconName(icon) ||
+ (typeof icon === 'object' &&
+ icon !== null &&
+ 'value' in icon &&
+ typeof icon.value === 'string' &&
+ 'type' in icon &&
+ (icon.type === 'emoji' || (icon.type === 'icon' && isIconName(icon.value))));
type BaseItem = {
id: string;
title: string;
disabled?: boolean;
- icon?: IconName;
+ icon?: ProjectIcon;
route?: RouteLocationRaw;
};
@@ -30,6 +44,7 @@ export const useGlobalEntityCreation = () => {
const CREATE_PROJECT_ID = 'create-project';
const WORKFLOWS_MENU_ID = 'workflow';
const CREDENTIALS_MENU_ID = 'credential';
+ const DEFAULT_ICON: IconName = 'layers';
const settingsStore = useSettingsStore();
const cloudPlanStore = useCloudPlanStore();
@@ -117,7 +132,7 @@ export const useGlobalEntityCreation = () => {
...displayProjects.value.map((project) => ({
id: `workflow-${project.id}`,
title: project.name as string,
- icon: 'layers' as IconName,
+ icon: isProjectIcon(project.icon) ? project.icon : DEFAULT_ICON,
disabled: disabledWorkflow(project.scopes),
route: {
name: VIEWS.NEW_WORKFLOW,
@@ -141,7 +156,7 @@ export const useGlobalEntityCreation = () => {
{
id: 'credential-personal',
title: i18n.baseText('projects.menu.personal'),
- icon: 'user' as IconName,
+ icon: 'user',
disabled: disabledCredential(projectsStore.personalProject?.scopes),
route: {
name: VIEWS.PROJECTS_CREDENTIALS,
@@ -151,7 +166,7 @@ export const useGlobalEntityCreation = () => {
...displayProjects.value.map((project) => ({
id: `credential-${project.id}`,
title: project.name as string,
- icon: 'layers' as IconName,
+ icon: isProjectIcon(project.icon) ? project.icon : DEFAULT_ICON,
disabled: disabledCredential(project.scopes),
route: {
name: VIEWS.PROJECTS_CREDENTIALS,
@@ -166,7 +181,7 @@ export const useGlobalEntityCreation = () => {
title: 'Project',
disabled: !projectsStore.canCreateProjects || !projectsStore.hasPermissionToCreateProjects,
},
- ];
+ ] satisfies Item[];
});
const createProject = async () => {
@@ -175,7 +190,7 @@ export const useGlobalEntityCreation = () => {
try {
const newProject = await projectsStore.createProject({
name: i18n.baseText('projects.settings.newProjectName'),
- icon: { type: 'icon', value: 'layers' },
+ icon: { type: 'icon', value: DEFAULT_ICON },
});
await router.push({ name: VIEWS.PROJECT_SETTINGS, params: { projectId: newProject.id } });
toast.showMessage({