feat(editor): Update icons to Lucide icons (#16231)

Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
This commit is contained in:
Alex Grozav
2025-06-30 18:11:09 +03:00
committed by GitHub
parent 3e04566845
commit ed2cb3c701
262 changed files with 2872 additions and 3443 deletions

View File

@@ -92,7 +92,7 @@ export function getNodeByName(name: string) {
export function getNodesWithSpinner() {
return cy
.getByTestId('canvas-node')
.filter((_, el) => Cypress.$(el).find('[data-icon=sync-alt]').length > 0);
.filter((_, el) => Cypress.$(el).find('[data-icon=refresh-cw]').length > 0);
}
export function getWaitingNodes() {

View File

@@ -41,33 +41,33 @@ describe('Execution', () => {
// Check canvas nodes after 1st step (workflow passed the manual trigger node
workflowPage.getters
.canvasNodeByName('Manual')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-check').should('not.exist'));
.within(() => cy.get('svg[data-icon=check]').should('not.exist'));
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-sync-alt'))
.within(() => cy.get('svg[data-icon=refresh-cw]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check').should('not.exist'));
.within(() => cy.get('svg[data-icon=check]').should('not.exist'));
cy.wait(2000);
// Check canvas nodes after 2nd step (waiting node finished its execution and the http request node is about to start)
workflowPage.getters
.canvasNodeByName('Manual')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
successToast().should('be.visible');
@@ -101,18 +101,18 @@ describe('Execution', () => {
// Check canvas nodes after 1st step (workflow passed the manual trigger node
workflowPage.getters
.canvasNodeByName('Manual')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-check').should('not.exist'));
.within(() => cy.get('svg[data-icon=check]').should('not.exist'));
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-sync-alt'))
.within(() => cy.get('svg[data-icon=refresh-cw]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check').should('not.exist'));
.within(() => cy.get('svg[data-icon=check]').should('not.exist'));
successToast().should('be.visible');
clearNotifications();
@@ -123,16 +123,16 @@ describe('Execution', () => {
// Check canvas nodes after workflow stopped
workflowPage.getters
.canvasNodeByName('Manual')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-sync-alt').should('not.exist'));
.within(() => cy.get('svg[data-icon=refresh-cw]').should('not.exist'));
workflowPage.getters
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check').should('not.exist'));
.within(() => cy.get('svg[data-icon=check]').should('not.exist'));
successToast().should('be.visible');
@@ -181,29 +181,29 @@ describe('Execution', () => {
// Check canvas nodes after 1st step (workflow passed the manual trigger node
workflowPage.getters
.canvasNodeByName('Webhook')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-check').should('not.exist'));
.within(() => cy.get('svg[data-icon=check]').should('not.exist'));
workflowPage.getters
.canvasNodeByName('Wait')
.within(() => cy.get('.fa-sync-alt'))
.within(() => cy.get('svg[data-icon=refresh-cw]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check').should('not.exist'));
.within(() => cy.get('svg[data-icon=check]').should('not.exist'));
cy.wait(2000);
// Check canvas nodes after 2nd step (waiting node finished its execution and the http request node is about to start)
workflowPage.getters
.canvasNodeByName('Webhook')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
successToast().should('be.visible');
@@ -578,11 +578,11 @@ describe('Execution', () => {
// Check that the previous nodes executed successfully
workflowPage.getters
.canvasNodeByName('DebugHelper')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters
.canvasNodeByName('Filter')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
errorToast().should('contain', 'Problem in node Telegram');
@@ -596,7 +596,7 @@ describe('Execution', () => {
workflowPage.getters
.canvasNodeByName('Edit Fields')
.within(() => cy.get('.fa-check'))
.within(() => cy.get('svg[data-icon=check]'))
.should('exist');
workflowPage.getters.canvasNodeByName('Edit Fields').dblclick();

View File

@@ -342,14 +342,14 @@ describe('Projects', { disableAutoLogin: true }, () => {
});
it('should set and update project icon', () => {
const DEFAULT_ICON = 'fa-layer-group';
const DEFAULT_ICON = 'layers';
const NEW_PROJECT_NAME = 'Test Project';
cy.signinAsAdmin();
cy.visit(workflowsPage.url);
projects.createProject(NEW_PROJECT_NAME);
// New project should have default icon
projects.getIconPickerButton().find('svg').should('have.class', DEFAULT_ICON);
projects.getIconPickerButton().find('svg').should('have.attr', 'data-icon', DEFAULT_ICON);
// Choose another icon
projects.getIconPickerButton().click();
projects.getIconPickerTab('Emojis').click();

View File

@@ -36,7 +36,7 @@ watch(
class="empty-container"
>
<div class="empty" data-test-id="chat-messages-empty">
<N8nIcon icon="comment" size="large" class="emptyIcon" />
<N8nIcon icon="message-circle" size="large" class="emptyIcon" />
<N8nText tag="p" size="medium" color="text-base">
{{ emptyText }}
</N8nText>

View File

@@ -15,3 +15,10 @@ declare module 'markdown-it-task-lists' {
export = markdownItTaskLists;
}
declare module '~icons/*' {
import type { FunctionalComponent, SVGAttributes } from 'vue';
const component: FunctionalComponent<SVGAttributes>;
export default component;
}

View File

@@ -39,8 +39,8 @@
"postcss": "^8.4.38",
"sass": "^1.64.1",
"tailwindcss": "^3.4.3",
"unplugin-icons": "^0.19.0",
"unplugin-vue-components": "^0.27.2",
"unplugin-icons": "catalog:frontend",
"unplugin-vue-components": "catalog:frontend",
"vite": "catalog:",
"vitest": "catalog:",
"vitest-mock-extended": "catalog:",

View File

@@ -294,7 +294,7 @@ function onSubmitFeedback(feedback: string) {
/>
<N8nIconButton
:class="{ [$style.sendButton]: true }"
icon="paper-plane"
icon="send"
:text="true"
size="large"
data-test-id="send-message-button"

View File

@@ -69,7 +69,6 @@ exports[`AskAssistantChat > does not render retry button if no error is present
<n8n-icon-stub
color="text-base"
icon="arrow-right"
size="medium"
spin="false"
/>
</div>
@@ -181,7 +180,7 @@ exports[`AskAssistantChat > does not render retry button if no error is present
class="sendButton"
data-test-id="send-message-button"
disabled="true"
icon="paper-plane"
icon="send"
loading="false"
outline="false"
size="large"
@@ -263,7 +262,6 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
<n8n-icon-stub
color="text-base"
icon="arrow-right"
size="medium"
spin="false"
/>
</div>
@@ -614,7 +612,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
data-test-id="replace-code-button"
disabled="false"
element="button"
icon="refresh"
icon="refresh-cw"
label=""
loading="false"
outline="false"
@@ -918,7 +916,7 @@ Testing more code
data-test-id="replace-code-button"
disabled="false"
element="button"
icon="refresh"
icon="refresh-cw"
label=""
loading="false"
outline="false"
@@ -999,7 +997,7 @@ Testing more code
class="sendButton"
data-test-id="send-message-button"
disabled="true"
icon="paper-plane"
icon="send"
loading="false"
outline="false"
size="large"
@@ -1081,7 +1079,6 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = `
<n8n-icon-stub
color="text-base"
icon="arrow-right"
size="medium"
spin="false"
/>
</div>
@@ -1186,7 +1183,7 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = `
class="sendButton"
data-test-id="send-message-button"
disabled="true"
icon="paper-plane"
icon="send"
loading="false"
outline="false"
size="large"
@@ -1268,7 +1265,6 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
<n8n-icon-stub
color="text-base"
icon="arrow-right"
size="medium"
spin="false"
/>
</div>
@@ -1457,7 +1453,7 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
class="sendButton"
data-test-id="send-message-button"
disabled="true"
icon="paper-plane"
icon="send"
loading="false"
outline="false"
size="large"
@@ -1539,7 +1535,6 @@ exports[`AskAssistantChat > renders error message correctly with retry button 1`
<n8n-icon-stub
color="text-base"
icon="arrow-right"
size="medium"
spin="false"
/>
</div>
@@ -1615,7 +1610,7 @@ exports[`AskAssistantChat > renders error message correctly with retry button 1`
>
<n8n-icon-stub
class="errorIcon"
icon="exclamation-triangle"
icon="triangle-alert"
size="small"
spin="false"
/>
@@ -1663,7 +1658,7 @@ exports[`AskAssistantChat > renders error message correctly with retry button 1`
class="sendButton"
data-test-id="send-message-button"
disabled="true"
icon="paper-plane"
icon="send"
loading="false"
outline="false"
size="large"
@@ -1745,7 +1740,6 @@ exports[`AskAssistantChat > renders message with code snippet 1`] = `
<n8n-icon-stub
color="text-base"
icon="arrow-right"
size="medium"
spin="false"
/>
</div>
@@ -1927,7 +1921,7 @@ catch(e) {
class="sendButton"
data-test-id="send-message-button"
disabled="true"
icon="paper-plane"
icon="send"
loading="false"
outline="false"
size="large"
@@ -2009,7 +2003,6 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
<n8n-icon-stub
color="text-base"
icon="arrow-right"
size="medium"
spin="false"
/>
</div>
@@ -2124,7 +2117,7 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
class="sendButton"
data-test-id="send-message-button"
disabled="true"
icon="paper-plane"
icon="send"
loading="false"
outline="false"
size="large"

View File

@@ -22,7 +22,7 @@ const { t } = useI18n();
<BaseMessage :message="message" :is-first-of-role="isFirstOfRole" :user="user">
<div :class="$style.error" data-test-id="chat-message-system">
<p :class="$style.errorText">
<N8nIcon icon="exclamation-triangle" size="small" :class="$style.errorIcon" />
<N8nIcon icon="triangle-alert" size="small" :class="$style.errorIcon" />
{{ message.content }}
</p>
<N8nButton

View File

@@ -109,14 +109,14 @@ const diffs = computed(() => {
</div>
<div :class="$style.actions">
<div v-if="error">
<N8nIcon icon="exclamation-triangle" color="danger" class="mr-5xs" />
<N8nIcon icon="triangle-alert" color="danger" class="mr-5xs" />
<span :class="$style.infoText">{{ t('codeDiff.couldNotReplace') }}</span>
</div>
<div v-else-if="replaced">
<N8nButton
type="secondary"
size="mini"
icon="undo"
icon="undo-2"
data-test-id="undo-replace-button"
@click="() => emit('undo')"
>
@@ -131,7 +131,7 @@ const diffs = computed(() => {
v-else
:type="replacing ? 'secondary' : 'primary'"
size="mini"
icon="refresh"
icon="refresh-cw"
data-test-id="replace-code-button"
:disabled="!content || streaming"
:loading="replacing"

View File

@@ -276,7 +276,7 @@ exports[`CodeDiff > renders code diff correctly 1`] = `
data-test-id="replace-code-button"
disabled="false"
element="button"
icon="refresh"
icon="refresh-cw"
label=""
loading="false"
outline="false"
@@ -543,8 +543,7 @@ exports[`CodeDiff > renders error state correctly 1`] = `
<n8n-icon-stub
class="mr-5xs"
color="danger"
icon="exclamation-triangle"
size="medium"
icon="triangle-alert"
spin="false"
/>
<span
@@ -814,7 +813,7 @@ exports[`CodeDiff > renders replaced code diff correctly 1`] = `
data-test-id="undo-replace-button"
disabled="false"
element="button"
icon="undo"
icon="undo-2"
label=""
loading="false"
outline="false"
@@ -827,7 +826,6 @@ exports[`CodeDiff > renders replaced code diff correctly 1`] = `
class="ml-xs"
color="success"
icon="check"
size="medium"
spin="false"
/>
<span
@@ -1097,7 +1095,7 @@ exports[`CodeDiff > renders replacing code diff correctly 1`] = `
data-test-id="replace-code-button"
disabled="false"
element="button"
icon="refresh"
icon="refresh-cw"
label=""
loading="true"
outline="false"

View File

@@ -5,6 +5,7 @@ import type { ButtonType } from '@n8n/design-system/types/button';
import N8nButton from '../N8nButton';
import N8nCallout, { type CalloutTheme } from '../N8nCallout';
import N8nHeading from '../N8nHeading';
import { type IconName } from '../N8nIcon/icons';
import N8nText from '../N8nText';
interface ActionBoxProps {
@@ -13,11 +14,11 @@ interface ActionBoxProps {
buttonText?: string;
buttonType?: ButtonType;
buttonDisabled?: boolean;
buttonIcon?: string;
buttonIcon?: IconName;
description?: string;
calloutText?: string;
calloutTheme?: CalloutTheme;
calloutIcon?: string;
calloutIcon?: IconName;
}
defineOptions({ name: 'N8nActionBox' });

View File

@@ -51,7 +51,7 @@ defaultActionDropdown.args = {
export const customStyling = template.bind({});
customStyling.args = {
activatorIcon: 'bars',
activatorIcon: 'menu',
items: [
{
id: 'item1',
@@ -67,7 +67,7 @@ customStyling.args = {
{
id: 'item3',
label: 'Action 3',
icon: 'heart',
icon: 'home',
divided: true,
},
],

View File

@@ -43,7 +43,7 @@ describe('components', () => {
{
id: 'item3',
label: 'Action 3',
icon: 'heart',
icon: 'house',
divided: true,
},
],

View File

@@ -12,6 +12,7 @@ import type { ActionDropdownItem, IconSize, ButtonSize } from '@n8n/design-syste
import N8nBadge from '../N8nBadge';
import N8nIcon from '../N8nIcon';
import { type IconName } from '../N8nIcon/icons';
import N8nIconButton from '../N8nIconButton';
import { N8nKeyboardShortcut } from '../N8nKeyboardShortcut';
@@ -20,7 +21,7 @@ const TRIGGER = ['click', 'hover'] as const;
interface ActionDropdownProps {
items: ActionDropdownItem[];
placement?: Placement;
activatorIcon?: string;
activatorIcon?: IconName;
activatorSize?: ButtonSize;
iconSize?: IconSize;
trigger?: (typeof TRIGGER)[number];
@@ -31,7 +32,7 @@ interface ActionDropdownProps {
const props = withDefaults(defineProps<ActionDropdownProps>(), {
placement: 'bottom',
activatorIcon: 'ellipsis-h',
activatorIcon: 'ellipsis',
activatorSize: 'medium',
iconSize: 'medium',
trigger: 'click',

View File

@@ -89,7 +89,7 @@ defineExpose({
<slot>
<span :class="{ [$style.button]: true, [$style[theme]]: !!theme }">
<N8nIcon
:icon="iconOrientation === 'horizontal' ? 'ellipsis-h' : 'ellipsis-v'"
:icon="iconOrientation === 'horizontal' ? 'ellipsis' : 'ellipsis-vertical'"
:size="iconSize"
/>
</span>
@@ -120,7 +120,7 @@ defineExpose({
<div :class="$style.iconContainer">
<N8nIcon
v-if="action.type === 'external-link'"
icon="external-link-alt"
icon="external-link"
size="xsmall"
color="text-base"
/>

View File

@@ -1,7 +1,6 @@
import { render, screen } from '@testing-library/vue';
import N8nAlert from './Alert.vue';
import N8nIcon from '../N8nIcon';
describe('components', () => {
describe('N8nAlert', () => {
@@ -21,11 +20,14 @@ describe('components', () => {
title: 'Title',
default: 'Message',
aside: '<button>Click me</button>',
icon: '<n8n-icon icon="plus-circle" />',
icon: '<n8n-icon icon="circle-plus" />',
},
global: {
components: {
'n8n-icon': N8nIcon,
'n8n-icon': {
template: '<span class="n8n-icon" />',
props: ['icon'],
},
},
},
});

View File

@@ -23,13 +23,13 @@ const props = withDefaults(defineProps<AlertProps>(), {
const icon = computed(() => {
switch (props.type) {
case 'success':
return 'check-circle';
return 'circle-check';
case 'warning':
return 'exclamation-triangle';
return 'triangle-alert';
case 'error':
return 'times-circle';
return 'circle-x';
default:
return 'info-circle';
return 'info';
}
});

View File

@@ -141,7 +141,7 @@ const withSlotsTemplate: StoryFn = (args, { argTypes }) => ({
template: `<Breadcrumbs v-bind="args">
<template #prepend>
<div style="display: flex; align-items: center; gap: 8px;">
<n8n-icon icon="layer-group"/>
<n8n-icon icon="layers"/>
<n8n-text>My Project</n8n-text>
</div>
</template>

View File

@@ -149,7 +149,7 @@ Text.args = {
export const WithIcon = AllSizesTemplate.bind({});
WithIcon.args = {
label: 'Button',
icon: 'plus-circle',
icon: 'circle-plus',
};
export const Square = AllColorsAndSizesTemplate.bind({});

View File

@@ -39,7 +39,7 @@ describe('components', () => {
it('should render icon button', () => {
const wrapper = render(N8nButton, {
props: {
icon: 'plus-circle',
icon: 'circle-plus',
},
slots,
global: {

View File

@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button class="button button primary medium withIcon" aria-live="polite"><span class="icon"><n8n-icon-stub icon="plus-circle" size="medium" spin="false"></n8n-icon-stub></span>Button</button>"`;
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button class="button button primary medium withIcon" aria-live="polite"><span class="icon"><n8n-icon-stub icon="circle-plus" size="medium" spin="false"></n8n-icon-stub></span>Button</button>"`;
exports[`components > N8nButton > props > loading > should render loading spinner 1`] = `"<button class="button button primary medium loading withIcon" disabled="" aria-busy="true" aria-live="polite"><span class="icon"><n8n-spinner-stub size="medium" type="dots"></n8n-spinner-stub></span>Button</button>"`;

View File

@@ -73,7 +73,7 @@ defaultCallout.args = {
export const customCallout = template.bind({});
customCallout.args = {
theme: 'custom',
icon: 'code-branch',
icon: 'git-branch',
default: `
This is a custom callout.
`,
@@ -87,7 +87,7 @@ customCallout.args = {
export const secondaryCallout = template.bind({});
secondaryCallout.args = {
theme: 'secondary',
icon: 'thumbtack',
icon: 'pin',
default: `
This data is pinned.
`,

View File

@@ -78,7 +78,7 @@ describe('components', () => {
const wrapper = render(N8nCallout, {
props: {
theme: 'custom',
icon: 'code-branch',
icon: 'git-branch',
},
global: {
stubs: ['n8n-icon', 'n8n-text'],
@@ -93,7 +93,7 @@ describe('components', () => {
const wrapper = render(N8nCallout, {
props: {
theme: 'custom',
icon: 'code-branch',
icon: 'git-branch',
},
global: {
stubs: ['n8n-icon', 'n8n-text', 'n8n-link'],

View File

@@ -4,18 +4,19 @@ import { computed, useCssModule } from 'vue';
import type { IconSize, CalloutTheme } from '@n8n/design-system/types';
import N8nIcon from '../N8nIcon';
import { type IconName } from '../N8nIcon/icons';
import N8nText from '../N8nText';
const CALLOUT_DEFAULT_ICONS: Record<string, string> = {
info: 'info-circle',
success: 'check-circle',
warning: 'exclamation-triangle',
danger: 'exclamation-triangle',
const CALLOUT_DEFAULT_ICONS: Record<string, IconName> = {
info: 'info',
success: 'circle-check',
warning: 'triangle-alert',
danger: 'triangle-alert',
};
interface CalloutProps {
theme: CalloutTheme;
icon?: string;
icon?: IconName;
iconSize?: IconSize;
iconless?: boolean;
slim?: boolean;

View File

@@ -4,7 +4,7 @@ exports[`components > N8nCallout > should render additional slots correctly 1`]
"<div class="n8n-callout callout custom round" role="alert">
<div class="messageSection">
<div class="icon">
<n8n-icon-stub icon="code-branch" size="medium" spin="false"></n8n-icon-stub>
<n8n-icon-stub icon="git-branch" size="medium" spin="false"></n8n-icon-stub>
</div>
<n8n-text-stub bold="false" size="small" compact="false" tag="span"></n8n-text-stub> &nbsp; <n8n-link-stub size="small"></n8n-link-stub>
</div>
@@ -16,7 +16,7 @@ exports[`components > N8nCallout > should render custom theme correctly 1`] = `
"<div class="n8n-callout callout custom round" role="alert">
<div class="messageSection">
<div class="icon">
<n8n-icon-stub icon="code-branch" size="medium" spin="false"></n8n-icon-stub>
<n8n-icon-stub icon="git-branch" size="medium" spin="false"></n8n-icon-stub>
</div>
<n8n-text-stub bold="false" size="small" compact="false" tag="span"></n8n-text-stub> &nbsp;
</div>
@@ -27,7 +27,7 @@ exports[`components > N8nCallout > should render danger theme correctly 1`] = `
"<div class="n8n-callout callout danger round" role="alert">
<div class="messageSection">
<div class="icon">
<n8n-icon-stub icon="exclamation-triangle" size="medium" spin="false"></n8n-icon-stub>
<n8n-icon-stub icon="triangle-alert" size="medium" spin="false"></n8n-icon-stub>
</div>
<n8n-text-stub bold="false" size="small" compact="false" tag="span"></n8n-text-stub> &nbsp;
</div>
@@ -38,7 +38,7 @@ exports[`components > N8nCallout > should render info theme correctly 1`] = `
"<div class="n8n-callout callout info round" role="alert">
<div class="messageSection">
<div class="icon">
<n8n-icon-stub icon="info-circle" size="medium" spin="false"></n8n-icon-stub>
<n8n-icon-stub icon="info" size="medium" spin="false"></n8n-icon-stub>
</div>
<n8n-text-stub bold="false" size="small" compact="false" tag="span"></n8n-text-stub> &nbsp;
</div>
@@ -49,7 +49,7 @@ exports[`components > N8nCallout > should render secondary theme correctly 1`] =
"<div class="n8n-callout callout secondary round" role="alert">
<div class="messageSection">
<div class="icon">
<n8n-icon-stub icon="info-circle" size="medium" spin="false"></n8n-icon-stub>
<n8n-icon-stub icon="info" size="medium" spin="false"></n8n-icon-stub>
</div>
<n8n-text-stub bold="false" size="small" compact="false" tag="span"></n8n-text-stub> &nbsp;
</div>
@@ -60,7 +60,7 @@ exports[`components > N8nCallout > should render success theme correctly 1`] = `
"<div class="n8n-callout callout success round" role="alert">
<div class="messageSection">
<div class="icon">
<n8n-icon-stub icon="check-circle" size="medium" spin="false"></n8n-icon-stub>
<n8n-icon-stub icon="circle-check" size="medium" spin="false"></n8n-icon-stub>
</div>
<n8n-text-stub bold="false" size="small" compact="false" tag="span"></n8n-text-stub> &nbsp;
</div>
@@ -71,7 +71,7 @@ exports[`components > N8nCallout > should render warning theme correctly 1`] = `
"<div class="n8n-callout callout warning round" role="alert">
<div class="messageSection">
<div class="icon">
<n8n-icon-stub icon="exclamation-triangle" size="medium" spin="false"></n8n-icon-stub>
<n8n-icon-stub icon="triangle-alert" size="medium" spin="false"></n8n-icon-stub>
</div>
<n8n-text-stub bold="false" size="small" compact="false" tag="span"></n8n-text-stub> &nbsp;
</div>

View File

@@ -0,0 +1,64 @@
import { render } from '@testing-library/vue';
import Icon from './Icon.vue';
import { deprecatedIconSet, type IconName } from './icons';
describe('Icon', () => {
it('should render correctly with default props', () => {
const wrapper = render(Icon, {
props: {
icon: 'check',
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should render correctly with a custom size', () => {
const wrapper = render(Icon, {
props: {
icon: 'check',
size: 24,
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should render correctly with predefined size', () => {
const wrapper = render(Icon, {
props: {
icon: 'check',
size: 'large',
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should render correctly with spin enabled', () => {
const wrapper = render(Icon, {
props: {
icon: 'check',
spin: true,
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should render correctly with a custom color', () => {
const wrapper = render(Icon, {
props: {
icon: 'check',
color: 'primary',
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should render correctly with a deprecated icon', () => {
const wrapper = render(Icon, {
props: {
icon: Object.keys(deprecatedIconSet)[0] as IconName,
},
});
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -1,49 +1,116 @@
<script lang="ts" setup>
import type { FontAwesomeIconProps } from '@fortawesome/vue-fontawesome';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { computed, useCssModule } from 'vue';
import type { IconSize, IconColor } from '@n8n/design-system/types/icon';
import N8nText from '../N8nText';
import type { IconName } from './icons';
import { deprecatedIconSet, updatedIconSet } from './icons';
interface IconProps {
icon: FontAwesomeIconProps['icon'];
size?: IconSize;
spin?: FontAwesomeIconProps['spin'];
// component supports both deprecated and updated icon set to support project icons
// but only allow new icon names to be used in the future
icon: IconName;
size?: IconSize | number;
spin?: boolean;
color?: IconColor;
strokeWidth?: number | undefined;
}
defineOptions({ name: 'N8nIcon' });
withDefaults(defineProps<IconProps>(), {
size: 'medium',
const props = withDefaults(defineProps<IconProps>(), {
spin: false,
size: undefined,
color: undefined,
});
const $style = useCssModule();
const classes = computed(() => {
const applied: string[] = [];
if (props.spin) {
applied.push('spin');
}
if (props.strokeWidth) {
applied.push('strokeWidth');
}
return ['n8n-icon', ...applied.map((c) => $style[c])];
});
const sizesInPixels: Record<IconSize, number> = {
xsmall: 10,
small: 12,
medium: 14,
large: 16,
xlarge: 20,
};
const size = computed((): { height: string; width: string } => {
let sizeToApply = '1em';
if (props.size) {
sizeToApply = `${typeof props.size === 'number' ? props.size : sizesInPixels[props.size]}px`;
}
return {
height: sizeToApply,
width: sizeToApply,
};
});
const styles = computed(() => {
const stylesToApply: Record<string, string> = {};
if (props.color) {
stylesToApply.color = `var(--color-${props.color})`;
}
if (props.strokeWidth) {
stylesToApply['--n8n-icon-stroke-width'] = `${props.strokeWidth}px`;
}
return stylesToApply;
});
</script>
<template>
<N8nText :size="size" :color="color" :compact="true" class="n8n-icon" v-bind="$attrs">
<FontAwesomeIcon :icon="icon" :spin="spin" :class="$style[size]" />
</N8nText>
<Component
:is="
updatedIconSet[icon as keyof typeof updatedIconSet] ??
deprecatedIconSet[icon as keyof typeof deprecatedIconSet]
"
v-if="
updatedIconSet[icon as keyof typeof updatedIconSet] ??
deprecatedIconSet[icon as keyof typeof deprecatedIconSet]
"
:class="classes"
aria-hidden="true"
focusable="false"
role="img"
:height="size.height"
:width="size.width"
:data-icon="props.icon"
:style="styles"
/>
</template>
<style lang="scss" module>
.xlarge {
width: var(--font-size-xl) !important;
.strokeWidth {
path {
stroke-width: var(--n8n-icon-stroke-width);
}
}
.large {
width: var(--font-size-m) !important;
.spin {
animation: spin 1s linear infinite;
}
.medium {
width: var(--font-size-s) !important;
}
.small {
width: var(--font-size-2xs) !important;
}
.xsmall {
width: var(--font-size-3xs) !important;
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,43 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Icon > should render correctly with a custom color 1`] = `
"<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="check">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5"></path>
</svg>"
`;
exports[`Icon > should render correctly with a custom size 1`] = `
"<svg viewBox="0 0 24 24" width="24px" height="24px" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="check">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5"></path>
</svg>"
`;
exports[`Icon > should render correctly with a deprecated icon 1`] = `
"<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="variable">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 21s-4-3-4-9s4-9 4-9m8 0s4 3 4 9s-4 9-4 9M15 9l-6 6m0-6l6 6"></path>
</svg>"
`;
exports[`Icon > should render correctly with all props combined 1`] = `
"<svg viewBox="0 0 24 24" width="14px" height="14px" class="n8n-icon spin" aria-hidden="true" focusable="false" role="img" data-icon="check">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5"></path>
</svg>"
`;
exports[`Icon > should render correctly with default props 1`] = `
"<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="check">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5"></path>
</svg>"
`;
exports[`Icon > should render correctly with predefined size 1`] = `
"<svg viewBox="0 0 24 24" width="16px" height="16px" class="n8n-icon" aria-hidden="true" focusable="false" role="img" data-icon="check">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5"></path>
</svg>"
`;
exports[`Icon > should render correctly with spin enabled 1`] = `
"<svg viewBox="0 0 24 24" width="1em" height="1em" class="n8n-icon spin" aria-hidden="true" focusable="false" role="img" data-icon="check">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5"></path>
</svg>"
`;

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M1 0.642857C1 0.287817 1.27473 0 1.61364 0H4.06818C4.40708 0 4.68182 0.287817 4.68182 0.642857V4.5C4.68182 4.85504 4.40708 5.14286 4.06818 5.14286H1.61364C1.27473 5.14286 1 4.85504 1 4.5V0.642857ZM2.22727 1.28571V3.85714H3.45455V1.28571H2.22727ZM6.31818 0.642857C6.31818 0.287817 6.59292 0 6.93182 0H8.15909C8.49799 0 8.77273 0.287817 8.77273 0.642857V3.85714H9.38636C9.72527 3.85714 10 4.14496 10 4.5C10 4.85504 9.72527 5.14286 9.38636 5.14286H6.93182C6.59292 5.14286 6.31818 4.85504 6.31818 4.5C6.31818 4.14496 6.59292 3.85714 6.93182 3.85714H7.54545V1.28571H6.93182C6.59292 1.28571 6.31818 0.997897 6.31818 0.642857ZM1 7.5C1 7.14496 1.27473 6.85714 1.61364 6.85714H2.84091C3.17981 6.85714 3.45455 7.14496 3.45455 7.5V10.7143H4.06818C4.40708 10.7143 4.68182 11.0021 4.68182 11.3571C4.68182 11.7122 4.40708 12 4.06818 12H1.61364C1.27473 12 1 11.7122 1 11.3571C1 11.0021 1.27473 10.7143 1.61364 10.7143H2.22727V8.14286H1.61364C1.27473 8.14286 1 7.85504 1 7.5ZM6.31818 7.5C6.31818 7.14496 6.59292 6.85714 6.93182 6.85714H9.38636C9.72527 6.85714 10 7.14496 10 7.5V11.3571C10 11.7122 9.72527 12 9.38636 12H6.93182C6.59292 12 6.31818 11.7122 6.31818 11.3571V7.5ZM7.54545 8.14286V10.7143H8.77273V8.14286H7.54545Z" /></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 3L9 21" stroke="currentColor" style="stroke-opacity:1;" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 3L15 21" stroke="currentColor" style="stroke-opacity:1;" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 352 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M13.3333 12.5525V12.4489C14.2278 12.0756 14.8571 11.1925 14.8571 10.1632V3.61924C14.8571 2.96252 14.5962 2.3327 14.1318 1.86832C13.6675 1.40395 13.0376 1.14307 12.3809 1.14307H5.90473C5.38113 1.14296 4.87098 1.30883 4.44756 1.61684C4.02414 1.92485 3.70926 2.35915 3.54816 2.85734H3.39501C2.70016 2.85734 2.10892 3.10191 1.70206 3.5842C1.30739 4.05124 1.14282 4.67372 1.14282 5.33352V12.0002C1.14282 12.8078 1.43463 13.5346 1.98854 14.0573C2.54168 14.5777 3.30892 14.8535 4.19044 14.8535H7.17711L10.2826 14.8573H10.2842C11.0278 14.8611 11.7645 14.7049 12.336 14.3392C12.9303 13.9582 13.3333 13.3525 13.3333 12.5525ZM3.39501 4.0002H3.42854V10.1625C3.42854 10.8192 3.68942 11.449 4.1538 11.9134C4.61817 12.3777 5.248 12.6386 5.90473 12.6386H12.1874C12.163 12.9571 12.003 13.1948 11.7196 13.3761C11.3897 13.588 10.8891 13.7175 10.2887 13.7144H10.2857L7.17558 13.7106H4.19044C3.54816 13.7106 3.07806 13.5125 2.7733 13.2253C2.47006 12.9403 2.28568 12.5259 2.28568 12.0002V5.33352C2.28568 4.84971 2.40758 4.52057 2.5752 4.32096C2.73139 4.13658 2.98054 4.0002 3.39501 4.0002ZM8.01673 3.80972H11.619C11.7706 3.80972 11.9159 3.86992 12.0231 3.97709C12.1302 4.08425 12.1904 4.22959 12.1904 4.38115V7.98418C12.1904 8.13573 12.1302 8.28107 12.0231 8.38823C11.9159 8.4954 11.7706 8.5556 11.619 8.5556C11.4675 8.5556 11.3221 8.4954 11.215 8.38823C11.1078 8.28107 11.0476 8.13573 11.0476 7.98418V5.76019L7.07044 9.73731C7.0177 9.79186 6.95463 9.83536 6.8849 9.86528C6.81517 9.89519 6.74018 9.91092 6.6643 9.91154C6.58843 9.91217 6.51319 9.89767 6.44298 9.86891C6.37277 9.84014 6.30899 9.79768 6.25536 9.74401C6.20173 9.69033 6.15933 9.62651 6.13063 9.55627C6.10193 9.48603 6.08751 9.41078 6.0882 9.3349C6.0889 9.25903 6.1047 9.18406 6.13468 9.11435C6.16466 9.04465 6.20822 8.98162 6.26282 8.92893L10.24 4.95257H8.01673C7.86517 4.95257 7.71983 4.89237 7.61267 4.7852C7.5055 4.67804 7.4453 4.5327 7.4453 4.38115C7.4453 4.22959 7.5055 4.08425 7.61267 3.97709C7.71983 3.86992 7.86517 3.80972 8.01673 3.80972Z" /></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M1.63636 0H8.18182C9.08556 0 9.81818 0.732625 9.81818 1.63636C9.81818 2.5401 9.08556 3.27273 8.18182 3.27273H1.63636C0.732626 3.27273 0 2.5401 0 1.63636C0 0.732625 0.732625 0 1.63636 0ZM1.63636 1.09091C1.33512 1.09091 1.09091 1.33512 1.09091 1.63636C1.09091 1.93761 1.33512 2.18182 1.63636 2.18182H8.18182C8.48306 2.18182 8.72727 1.93761 8.72727 1.63636C8.72727 1.33512 8.48306 1.09091 8.18182 1.09091H1.63636Z M7.09091 4.36353H11.4545C12.3583 4.36353 13.0909 5.09615 13.0909 5.99989C13.0909 6.90363 12.3583 7.63625 11.4545 7.63625H7.09091C6.18717 7.63625 5.45454 6.90363 5.45454 5.99989C5.45454 5.09615 6.18717 4.36353 7.09091 4.36353ZM7.09091 5.45443C6.78966 5.45443 6.54545 5.69864 6.54545 5.99989C6.54545 6.30114 6.78966 6.54534 7.09091 6.54534H11.4545C11.7558 6.54534 12 6.30114 12 5.99989C12 5.69864 11.7558 5.45443 11.4545 5.45443H7.09091Z M7.09091 8.72729H11.4545C12.3583 8.72729 13.0909 9.45992 13.0909 10.3637C13.0909 11.2674 12.3583 12 11.4545 12H7.09091C6.18717 12 5.45454 11.2674 5.45454 10.3637C5.45454 9.45992 6.18717 8.72729 7.09091 8.72729ZM7.09091 9.8182C6.78966 9.8182 6.54545 10.0624 6.54545 10.3637C6.54545 10.6649 6.78966 10.9091 7.09091 10.9091H11.4545C11.7558 10.9091 12 10.6649 12 10.3637C12 10.0624 11.7558 9.8182 11.4545 9.8182H7.09091Z" /></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,12 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2V5" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M12 19V22" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M12 2V5" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M12 19V22" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M22.005 11.9951L19.005 11.9951" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M5.005 11.9951L2.005 11.9951" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M19.0796 19.0676L16.9583 16.9463" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M7.05884 7.04688L4.93752 4.92555" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M4.9375 19.0676L7.05882 16.9463" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
<path d="M16.9583 7.04688L19.0796 4.92556" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M 14 7 C 14 10.866 10.866 14 7 14 C 3.134 14 0 10.866 0 7 C 0 3.134 3.134 0 7 0 C 10.866 0 14 3.134 14 7 Z M 11.243 6 L 2.758 6 L 2.758 8 L 11.243 8 L 11.243 6 Z" /></svg>

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M 14 7 C 14 10.866 10.866 14 7 14 C 3.134 14 0 10.866 0 7 C 0 3.134 3.134 0 7 0 C 10.866 0 14 3.134 14 7 Z M 2.575 7.728 L 5.782 10.935 L 11.489 5.228 L 10.075 3.814 L 5.782 8.107 L 3.989 6.314 L 2.575 7.728 Z" /></svg>

After

Width:  |  Height:  |  Size: 312 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M 4.207 2.793 L 7 5.586 L 9.793 2.793 L 11.207 4.207 L 8.414 7 L 11.207 9.793 L 9.793 11.207 L 7 8.414 L 4.207 11.207 L 2.793 9.793 L 5.586 7 L 2.793 4.207 L 4.207 2.793 Z M 7 0 C 3.134 0 0 3.134 0 7 C 0 10.866 3.134 14 7 14 C 10.866 14 14 10.866 14 7 C 14 3.134 10.866 0 7 0 Z" /></svg>

After

Width:  |  Height:  |  Size: 380 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M 14 7.006 C 14 8.867 13.162 10.744 11.95 11.956 C 10.738 13.168 8.861 14.006 7 14.006 C 5.139 14.006 3.262 13.168 2.05 11.956 C 0.838 10.744 0 8.867 0 7.006 C 0 5.145 0.838 3.268 2.05 2.056 C 3.262 0.844 5.139 0.006 7 0.006 C 8.861 0.006 10.738 0.844 11.95 2.056 C 13.162 3.268 14 5.145 14 7.006 Z M 10.536 3.47 C 9.576 2.511 8.453 2.006 7 2.006 C 5.547 2.006 4.424 2.511 3.464 3.47 C 2.505 4.43 2 5.553 2 7.006 C 2 8.459 2.505 9.582 3.464 10.542 C 4.424 11.501 5.547 12.006 7 12.006 C 8.453 12.006 9.576 11.501 10.536 10.542 C 11.495 9.582 12 8.459 12 7.006 C 12 5.553 11.495 4.43 10.536 3.47 Z" /></svg>

After

Width:  |  Height:  |  Size: 699 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M13.8668 8.36613L11.9048 7.978C11.967 7.66329 12 7.33649 12 7C12 6.66351 11.967 6.3367 11.9048 6.022L13.8668 5.63387C13.9542 6.07571 14 6.5325 14 7C14 7.4675 13.9542 7.92429 13.8668 8.36613ZM12.821 3.11069L11.159 4.22333C10.7934 3.67721 10.3228 3.2066 9.77667 2.84098L10.8893 1.17904C11.6527 1.6901 12.3099 2.34733 12.821 3.11069ZM8.36613 0.133238L7.978 2.09521C7.66329 2.03296 7.33649 2 7 2C6.66351 2 6.3367 2.03296 6.022 2.09521L5.63387 0.133238C6.07571 0.0458286 6.5325 0 7 0C7.4675 0 7.92429 0.0458285 8.36613 0.133238ZM3.11069 1.17904L4.22333 2.84098C3.67721 3.2066 3.2066 3.67721 2.84098 4.22333L1.17904 3.11069C1.6901 2.34733 2.34733 1.6901 3.11069 1.17904ZM0.133238 5.63387C0.0458285 6.07571 0 6.5325 0 7C0 7.4675 0.0458286 7.92429 0.133238 8.36613L2.09521 7.978C2.03296 7.6633 2 7.33649 2 7C2 6.66351 2.03296 6.33671 2.09521 6.022L0.133238 5.63387ZM1.17904 10.8893L2.84098 9.77667C3.2066 10.3228 3.67721 10.7934 4.22333 11.159L3.11069 12.821C2.34733 12.3099 1.6901 11.6527 1.17904 10.8893ZM5.63387 13.8668L6.022 11.9048C6.33671 11.967 6.66351 12 7 12C7.33649 12 7.6633 11.967 7.978 11.9048L8.36613 13.8668C7.92429 13.9542 7.4675 14 7 14C6.5325 14 6.07571 13.9542 5.63387 13.8668ZM10.8893 12.821L9.77667 11.159C10.3228 10.7934 10.7934 10.3228 11.159 9.77667L12.821 10.8893C12.3099 11.6527 11.6527 12.3099 10.8893 12.821Z" /></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M7 14C10.866 14 14 10.866 14 7C14 3.13401 10.866 0 7 0C3.13401 0 0 3.13401 0 7C0 10.866 3.13401 14 7 14ZM7 12C4.23858 12 2 9.76142 2 7C2 4.23858 4.23858 2 7 2C9.76142 2 12 4.23858 12 7C12 9.76142 9.76142 12 7 12ZM6 3V8H11C11 5.23858 8.76142 3 6 3Z" /></svg>

After

Width:  |  Height:  |  Size: 350 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M 14 7 C 14 10.866 10.866 14 7 14 C 3.134 14 0 10.866 0 7 C 0 3.134 3.134 0 7 0 C 10.866 0 14 3.134 14 7 Z M 6.5 9 C 6.224 9 6 9.224 6 9.5 L 6 10.5 C 6 10.776 6.224 11 6.5 11 L 7.5 11 C 7.776 11 8 10.776 8 10.5 L 8 9.5 C 8 9.224 7.776 9 7.5 9 L 6.5 9 Z M 6.5 3 C 6.224 3 6 3.224 6 3.5 L 6 7.5 C 6 7.776 6.224 8 6.5 8 L 7.5 8 C 7.776 8 8 7.776 8 7.5 L 8 3.5 C 8 3.224 7.776 3 7.5 3 L 6.5 3 Z" /></svg>

After

Width:  |  Height:  |  Size: 493 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M1.78814e-07 0.666667C1.78814e-07 0.298477 0.298477 0 0.666667 0H11.3333C11.7015 0 12 0.298477 12 0.666667C12 1.03486 11.7015 1.33333 11.3333 1.33333H0.666667C0.298477 1.33333 1.78814e-07 1.03486 1.78814e-07 0.666667ZM1.78814e-07 3.62963C1.78814e-07 3.26144 0.298477 2.96296 0.666667 2.96296H11.3333C11.7015 2.96296 12 3.26144 12 3.62963C12 3.99782 11.7015 4.2963 11.3333 4.2963H0.666667C0.298477 4.2963 1.78814e-07 3.99782 1.78814e-07 3.62963ZM0 6.59259C0 6.2244 0.298477 5.92593 0.666667 5.92593H11.3333C11.7015 5.92593 12 6.2244 12 6.59259C12 6.96078 11.7015 7.25926 11.3333 7.25926H0.666667C0.298477 7.25926 0 6.96078 0 6.59259ZM0 9.55556C0 9.18737 0.298477 8.88889 0.666667 8.88889H8.66667C9.03486 8.88889 9.33333 9.18737 9.33333 9.55556C9.33333 9.92375 9.03486 10.2222 8.66667 10.2222H0.666667C0.298477 10.2222 0 9.92375 0 9.55556Z" /></svg>

After

Width:  |  Height:  |  Size: 940 B

View File

@@ -0,0 +1,3 @@
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path style="stroke:currentColor;stroke-opacity: 1;" d="M8 8V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v4m6 12V10a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2ZM8 13v4m8-4v4M2 15h20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 355 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M214.433 56C232.908 23.9999 279.096 24.0001 297.571 56L477.704 368C496.18 400 473.085 440 436.135 440H75.8685C38.918 440 15.8241 400 34.2993 368L214.433 56ZM256.002 144L131.294 360H380.709L256.002 144Z" /></svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@@ -0,0 +1,10 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="5" height="5" rx="1" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2"/>
<rect x="17" y="2" width="5" height="5" rx="1" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2"/>
<rect x="17" y="17" width="5" height="5" rx="1" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2"/>
<rect x="2" y="17" width="5" height="5" rx="1" stroke="currentColor" style="stroke:currentColor;stroke-opacity:1;" stroke-width="2"/>
<rect x="7" y="3" width="10" height="2" fill="currentColor" style="fill:currentColor;fill-opacity:1;"/>
<rect x="7" y="19" width="10" height="2" fill="currentColor" style="fill:currentColor;fill-opacity:1;"/>
<rect x="3" y="7" width="2" height="10" fill="currentColor" style="fill:currentColor;fill-opacity:1;"/>
<rect x="19" y="7" width="2" height="10" fill="currentColor" style="fill:currentColor;fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,576 @@
import Binary from './custom/binary.svg';
import GripLinesVertical from './custom/grip-lines-vertical.svg';
import Json from './custom/json.svg';
import PopOut from './custom/pop-out.svg';
import Schema from './custom/schema.svg';
import Spinner from './custom/spinner.svg';
import StatusCanceled from './custom/status-canceled.svg';
import StatusCompleted from './custom/status-completed.svg';
import StatusError from './custom/status-error.svg';
import StatusNew from './custom/status-new.svg';
import StatusUnknown from './custom/status-unknown.svg';
import StatusWaiting from './custom/status-waiting.svg';
import StatusWarning from './custom/status-warning.svg';
import Text from './custom/text.svg';
import Toolbox from './custom/toolbox.svg';
import Triangle from './custom/triangle.svg';
import VectorSquare from './custom/vector-square.svg';
import IconLucideAlignRight from '~icons/lucide/align-right';
import IconLucideArchive from '~icons/lucide/archive';
import IconLucideArrowDown from '~icons/lucide/arrow-down';
import IconLucideArrowLeft from '~icons/lucide/arrow-left';
import IconLucideArrowLeftRight from '~icons/lucide/arrow-left-right';
import IconLucideArrowRight from '~icons/lucide/arrow-right';
import IconLucideArrowUp from '~icons/lucide/arrow-up';
import IconLucideAtSign from '~icons/lucide/at-sign';
import IconLucideBan from '~icons/lucide/ban';
import IconLucideBell from '~icons/lucide/bell';
import IconLucideBook from '~icons/lucide/book';
import IconLucideBot from '~icons/lucide/bot';
import IconLucideBox from '~icons/lucide/box';
import IconLucideBrain from '~icons/lucide/brain';
import IconLucideBug from '~icons/lucide/bug';
import IconLucideCalculator from '~icons/lucide/calculator';
import IconLucideCalendar from '~icons/lucide/calendar';
import IconLucideCaseUpper from '~icons/lucide/case-upper';
import IconLucideChartColumnDecreasing from '~icons/lucide/chart-column-decreasing';
import IconLucideCheck from '~icons/lucide/check';
import IconLucideCheckCheck from '~icons/lucide/check-check';
import IconLucideChevronDown from '~icons/lucide/chevron-down';
import IconLucideChevronLeft from '~icons/lucide/chevron-left';
import IconLucideChevronRight from '~icons/lucide/chevron-right';
import IconLucideChevronUp from '~icons/lucide/chevron-up';
import IconLucideChevronsLeft from '~icons/lucide/chevrons-left';
import IconLucideChevronsUpDown from '~icons/lucide/chevrons-up-down';
import IconLucideCircle from '~icons/lucide/circle';
import IconLucideCircleAlert from '~icons/lucide/circle-alert';
import IconLucideCircleCheck from '~icons/lucide/circle-check';
import IconLucideCircleDot from '~icons/lucide/circle-dot';
import IconLucideCircleHelp from '~icons/lucide/circle-help';
import IconLucideCircleMinus from '~icons/lucide/circle-minus';
import IconLucideCirclePause from '~icons/lucide/circle-pause';
import IconLucideCirclePlay from '~icons/lucide/circle-play';
import IconLucideCirclePlus from '~icons/lucide/circle-plus';
import IconLucideCircleUserRound from '~icons/lucide/circle-user-round';
import IconLucideCircleX from '~icons/lucide/circle-x';
import IconLucideClipboardList from '~icons/lucide/clipboard-list';
import IconLucideClock from '~icons/lucide/clock';
import IconLucideCloud from '~icons/lucide/cloud';
import IconLucideCloudDownload from '~icons/lucide/cloud-download';
import IconLucideCode from '~icons/lucide/code';
import IconLucideCog from '~icons/lucide/cog';
import IconLucideContrast from '~icons/lucide/contrast';
import IconLucideCopy from '~icons/lucide/copy';
import IconLucideDatabase from '~icons/lucide/database';
import IconLucideEarth from '~icons/lucide/earth';
import IconLucideEllipsis from '~icons/lucide/ellipsis';
import IconLucideEllipsisVertical from '~icons/lucide/ellipsis-vertical';
import IconLucideEqual from '~icons/lucide/equal';
import IconLucideExternalLink from '~icons/lucide/external-link';
import IconLucideEye from '~icons/lucide/eye';
import IconLucideEyeOff from '~icons/lucide/eye-off';
import IconLucideFile from '~icons/lucide/file';
import IconLucideFileArchive from '~icons/lucide/file-archive';
import IconLucideFileCode from '~icons/lucide/file-code';
import IconLucideFileDown from '~icons/lucide/file-down';
import IconLucideFileInput from '~icons/lucide/file-input';
import IconLucideFileOutput from '~icons/lucide/file-output';
import IconLucideFileText from '~icons/lucide/file-text';
import IconLucideFiles from '~icons/lucide/files';
import IconLucideFingerprint from '~icons/lucide/fingerprint';
import IconLucideFlaskConical from '~icons/lucide/flask-conical';
import IconLucideFolder from '~icons/lucide/folder';
import IconLucideFolderOpen from '~icons/lucide/folder-open';
import IconLucideFolderPlus from '~icons/lucide/folder-plus';
import IconLucideFunnel from '~icons/lucide/funnel';
import IconLucideGem from '~icons/lucide/gem';
import IconLucideGift from '~icons/lucide/gift';
import IconLucideGitBranch from '~icons/lucide/git-branch';
import IconLucideGlobe from '~icons/lucide/globe';
import IconLucideGraduationCap from '~icons/lucide/graduation-cap';
import IconLucideGrid2x2 from '~icons/lucide/grid-2x2';
import IconLucideGripVertical from '~icons/lucide/grip-vertical';
import IconLucideHandCoins from '~icons/lucide/hand-coins';
import IconLucideHandshake from '~icons/lucide/handshake';
import IconLucideHardDrive from '~icons/lucide/hard-drive';
import IconLucideHardDriveDownload from '~icons/lucide/hard-drive-download';
import IconLucideHash from '~icons/lucide/hash';
import IconLucideHistory from '~icons/lucide/history';
import IconLucideHourglass from '~icons/lucide/hourglass';
import IconLucideHouse from '~icons/lucide/house';
import IconLucideImage from '~icons/lucide/image';
import IconLucideInbox from '~icons/lucide/inbox';
import IconLucideInfo from '~icons/lucide/info';
import IconLucideKeyRound from '~icons/lucide/key-round';
import IconLucideLanguages from '~icons/lucide/languages';
import IconLucideLayers from '~icons/lucide/layers';
import IconLucideLightbulb from '~icons/lucide/lightbulb';
import IconLucideLink from '~icons/lucide/link';
import IconLucideList from '~icons/lucide/list';
import IconLucideListChecks from '~icons/lucide/list-checks';
import IconLucideLock from '~icons/lucide/lock';
import IconLucideLogIn from '~icons/lucide/log-in';
import IconLucideLogOut from '~icons/lucide/log-out';
import IconLucideMail from '~icons/lucide/mail';
import IconLucideMaximize from '~icons/lucide/maximize';
import IconLucideMaximize2 from '~icons/lucide/maximize-2';
import IconLucideMenu from '~icons/lucide/menu';
import IconLucideMessageCircle from '~icons/lucide/message-circle';
import IconLucideMessagesSquare from '~icons/lucide/messages-square';
import IconLucideMilestone from '~icons/lucide/milestone';
import IconLucideMinimize2 from '~icons/lucide/minimize-2';
import IconLucideMousePointer from '~icons/lucide/mouse-pointer';
import IconLucideNetwork from '~icons/lucide/network';
import IconLucidePackageOpen from '~icons/lucide/package-open';
import IconLucidePalette from '~icons/lucide/palette';
import IconLucidePause from '~icons/lucide/pause';
import IconLucidePen from '~icons/lucide/pen';
import IconLucidePencil from '~icons/lucide/pencil';
import IconLucidePin from '~icons/lucide/pin';
import IconLucidePlay from '~icons/lucide/play';
import IconLucidePlug from '~icons/lucide/plug';
import IconLucidePlus from '~icons/lucide/plus';
import IconLucidePocketKnife from '~icons/lucide/pocket-knife';
import IconLucidePower from '~icons/lucide/power';
import IconLucideRedo2 from '~icons/lucide/redo-2';
import IconLucideRefreshCw from '~icons/lucide/refresh-cw';
import IconLucideRemoveFormatting from '~icons/lucide/remove-formatting';
import IconLucideRss from '~icons/lucide/rss';
import IconLucideSatelliteDish from '~icons/lucide/satellite-dish';
import IconLucideSave from '~icons/lucide/save';
import IconLucideScale from '~icons/lucide/scale';
import IconLucideScissors from '~icons/lucide/scissors';
import IconLucideSearch from '~icons/lucide/search';
import IconLucideSend from '~icons/lucide/send';
import IconLucideServer from '~icons/lucide/server';
import IconLucideShare from '~icons/lucide/share';
import IconLucideSlidersHorizontal from '~icons/lucide/sliders-horizontal';
import IconLucideSmile from '~icons/lucide/smile';
import IconLucideSquare from '~icons/lucide/square';
import IconLucideSquareCheck from '~icons/lucide/square-check';
import IconLucideSquarePen from '~icons/lucide/square-pen';
import IconLucideSquarePlus from '~icons/lucide/square-plus';
import IconLucideStickyNote from '~icons/lucide/sticky-note';
import IconLucideSun from '~icons/lucide/sun';
import IconLucideTable from '~icons/lucide/table';
import IconLucideTags from '~icons/lucide/tags';
import IconLucideTerminal from '~icons/lucide/terminal';
import IconLucideThumbsDown from '~icons/lucide/thumbs-down';
import IconLucideThumbsUp from '~icons/lucide/thumbs-up';
import IconLucideTrash2 from '~icons/lucide/trash-2';
import IconLucideTreePine from '~icons/lucide/tree-pine';
import IconLucideTriangleAlert from '~icons/lucide/triangle-alert';
import IconLucideUndo2 from '~icons/lucide/undo-2';
import IconLucideUnlink from '~icons/lucide/unlink';
import IconLucideUser from '~icons/lucide/user';
import IconLucideUserCheck from '~icons/lucide/user-check';
import IconLucideUserLock from '~icons/lucide/user-lock';
import IconLucideUserRound from '~icons/lucide/user-round';
import IconLucideUsers from '~icons/lucide/users';
import IconLucideVariable from '~icons/lucide/variable';
import IconLucideVault from '~icons/lucide/vault';
import IconLucideVideo from '~icons/lucide/video';
import IconLucideWaypoints from '~icons/lucide/waypoints';
import IconLucideWrench from '~icons/lucide/wrench';
import IconLucideX from '~icons/lucide/x';
import IconLucideZap from '~icons/lucide/zap';
import IconLucideZoomIn from '~icons/lucide/zoom-in';
import IconLucideZoomOut from '~icons/lucide/zoom-out';
/**
* Need to keep old icon names
* To support old project icons
* Which used to include all icons in instance
*/
export const deprecatedIconSet = {
// customIcons
variable: IconLucideVariable,
'pop-out': PopOut,
triangle: Triangle,
'status-completed': StatusCompleted,
'status-waiting': StatusWaiting,
'status-error': StatusError,
'status-canceled': StatusCanceled,
'status-new': StatusNew,
'status-unknown': StatusUnknown,
'status-warning': StatusWarning,
'vector-square': VectorSquare,
schema: Schema,
json: Json,
binary: Binary,
text: Text,
toolbox: Toolbox,
spinner: Spinner,
xmark: IconLucideX,
// fontAwesomeIcons
'caret-up': IconLucideChevronUp,
'caret-down': IconLucideChevronDown,
'caret-right': IconLucideChevronRight,
'caret-left': IconLucideChevronLeft,
'folder-plus': IconLucideFolderPlus,
share: IconLucideShare,
'user-check': IconLucideUserCheck,
'check-double': IconLucideCheckCheck,
'exclamation-circle': IconLucideCircleAlert,
circle: IconLucideCircle,
'eye-slash': IconLucideEyeOff,
folder: IconLucideFolder,
'minus-circle': IconLucideCircleMinus,
adjust: IconLucideContrast,
refresh: IconLucideRefreshCw,
vault: IconLucideVault,
'angle-double-left': IconLucideChevronsLeft,
'angle-down': IconLucideChevronDown,
'angle-left': IconLucideChevronLeft,
'angle-right': IconLucideChevronRight,
'angle-up': IconLucideChevronUp,
archive: IconLucideArchive,
'arrow-left': IconLucideArrowLeft,
'arrow-right': IconLucideArrowRight,
'arrow-up': IconLucideArrowUp,
'arrow-down': IconLucideArrowDown,
at: IconLucideAtSign,
ban: IconLucideBan,
'balance-scale-left': IconLucideScale,
bars: IconLucideMenu,
bolt: IconLucideZap,
book: IconLucideBook,
'box-open': IconLucidePackageOpen,
bug: IconLucideBug,
brain: IconLucideBrain,
calculator: IconLucideCalculator,
calendar: IconLucideCalendar,
'chart-bar': IconLucideChartColumnDecreasing,
check: IconLucideCheck,
'check-circle': IconLucideCircleCheck,
'check-square': IconLucideSquareCheck,
'chevron-left': IconLucideChevronLeft,
'chevron-right': IconLucideChevronRight,
'chevron-down': IconLucideChevronDown,
'chevron-up': IconLucideChevronUp,
code: IconLucideCode,
'code-branch': IconLucideGitBranch,
cog: IconLucideCog,
cogs: IconLucideCog,
comment: IconLucideMessageCircle,
comments: IconLucideMessagesSquare,
'clipboard-list': IconLucideClipboardList,
clock: IconLucideClock,
clone: IconLucideCopy,
cloud: IconLucideCloud,
'cloud-download-alt': IconLucideCloudDownload,
compress: IconLucideChevronsUpDown,
copy: IconLucideFiles,
cube: IconLucideBox,
cut: IconLucideScissors,
database: IconLucideDatabase,
'dot-circle': IconLucideCircleDot,
'grip-lines-vertical': GripLinesVertical,
'grip-vertical': IconLucideGripVertical,
edit: IconLucideSquarePen,
'ellipsis-h': IconLucideEllipsis,
'ellipsis-v': IconLucideEllipsisVertical,
envelope: IconLucideMail,
equals: IconLucideEqual,
eye: IconLucideEye,
'exclamation-triangle': IconLucideTriangleAlert,
expand: IconLucideMaximize,
'expand-alt': IconLucideMaximize2,
'external-link-alt': IconLucideExternalLink,
'exchange-alt': IconLucideArrowLeftRight,
file: IconLucideFile,
'file-alt': IconLucideFileText,
'file-archive': IconLucideFileArchive,
'file-code': IconLucideFileCode,
'file-download': IconLucideFileDown,
'file-export': IconLucideFileOutput,
'file-import': IconLucideFileInput,
'file-pdf': IconLucideFileText,
filter: IconLucideFunnel,
fingerprint: IconLucideFingerprint,
flask: IconLucideFlaskConical,
'folder-open': IconLucideFolderOpen,
font: IconLucideCaseUpper,
gift: IconLucideGift,
globe: IconLucideGlobe,
'globe-americas': IconLucideEarth,
'graduation-cap': IconLucideGraduationCap,
'hand-holding-usd': IconLucideHandCoins,
'hand-scissors': IconLucideScissors,
handshake: IconLucideHandshake,
'hand-point-left': IconLucideArrowLeft,
hashtag: IconLucideHash,
hdd: IconLucideHardDrive,
history: IconLucideHistory,
home: IconLucideHouse,
hourglass: IconLucideHourglass,
image: IconLucideImage,
inbox: IconLucideInbox,
info: IconLucideInfo,
'info-circle': IconLucideInfo,
key: IconLucideKeyRound,
language: IconLucideLanguages,
'layer-group': IconLucideLayers,
link: IconLucideLink,
list: IconLucideList,
lightbulb: IconLucideLightbulb,
lock: IconLucideLock,
'map-signs': IconLucideMilestone,
'mouse-pointer': IconLucideMousePointer,
'network-wired': IconLucideNetwork,
palette: IconLucidePalette,
pause: IconLucidePause,
'pause-circle': IconLucideCirclePause,
pen: IconLucidePen,
'pencil-alt': IconLucidePencil,
play: IconLucidePlay,
'play-circle': IconLucideCirclePlay,
plug: IconLucidePlug,
plus: IconLucidePlus,
'plus-circle': IconLucideCirclePlus,
'plus-square': IconLucideSquarePlus,
'project-diagram': IconLucideWaypoints,
question: IconLucideCircleHelp,
'question-circle': IconLucideCircleHelp,
redo: IconLucideRedo2,
'remove-format': IconLucideRemoveFormatting,
robot: IconLucideBot,
rss: IconLucideRss,
save: IconLucideSave,
'satellite-dish': IconLucideSatelliteDish,
search: IconLucideSearch,
'search-minus': IconLucideZoomOut,
'search-plus': IconLucideZoomIn,
server: IconLucideServer,
screwdriver: IconLucidePocketKnife,
smile: IconLucideSmile,
'sign-in-alt': IconLucideLogIn,
'sign-out-alt': IconLucideLogOut,
'sliders-h': IconLucideSlidersHorizontal,
'sticky-note': IconLucideStickyNote,
stop: IconLucideSquare,
stream: IconLucideAlignRight,
sun: IconLucideSun,
sync: IconLucideRefreshCw,
'sync-alt': IconLucideRefreshCw,
table: IconLucideTable,
tags: IconLucideTags,
tasks: IconLucideListChecks,
terminal: IconLucideTerminal,
'th-large': IconLucideGrid2x2,
thumbtack: IconLucidePin,
'thumbs-down': IconLucideThumbsDown,
'thumbs-up': IconLucideThumbsUp,
times: IconLucideX,
'times-circle': IconLucideCircleX,
tools: IconLucideWrench,
trash: IconLucideTrash2,
undo: IconLucideUndo2,
unlink: IconLucideUnlink,
user: IconLucideUser,
'user-circle': IconLucideCircleUserRound,
'user-friends': IconLucideUserRound,
users: IconLucideUsers,
video: IconLucideVideo,
tree: IconLucideTreePine,
'user-lock': IconLucideUserLock,
gem: IconLucideGem,
download: IconLucideHardDriveDownload,
'power-off': IconLucidePower,
'paper-plane': IconLucideSend,
bell: IconLucideBell,
} as const;
export const updatedIconSet = {
// custom icons
'grip-lines-vertical': GripLinesVertical,
variable: IconLucideVariable,
'pop-out': PopOut,
triangle: Triangle,
'status-completed': StatusCompleted,
'status-waiting': StatusWaiting,
'status-error': StatusError,
'status-canceled': StatusCanceled,
'status-new': StatusNew,
'status-unknown': StatusUnknown,
'status-warning': StatusWarning,
'vector-square': VectorSquare,
schema: Schema,
json: Json,
binary: Binary,
text: Text,
toolbox: Toolbox,
spinner: Spinner,
// lucide
'align-right': IconLucideAlignRight,
archive: IconLucideArchive,
'arrow-down': IconLucideArrowDown,
'arrow-left': IconLucideArrowLeft,
'arrow-left-right': IconLucideArrowLeftRight,
'arrow-right': IconLucideArrowRight,
'arrow-up': IconLucideArrowUp,
'at-sign': IconLucideAtSign,
ban: IconLucideBan,
bell: IconLucideBell,
book: IconLucideBook,
bot: IconLucideBot,
box: IconLucideBox,
brain: IconLucideBrain,
bug: IconLucideBug,
calculator: IconLucideCalculator,
calendar: IconLucideCalendar,
'case-upper': IconLucideCaseUpper,
'chart-column-decreasing': IconLucideChartColumnDecreasing,
check: IconLucideCheck,
'check-check': IconLucideCheckCheck,
'chevron-down': IconLucideChevronDown,
'chevron-left': IconLucideChevronLeft,
'chevron-right': IconLucideChevronRight,
'chevron-up': IconLucideChevronUp,
'chevrons-left': IconLucideChevronsLeft,
circle: IconLucideCircle,
'circle-alert': IconLucideCircleAlert,
'circle-check': IconLucideCircleCheck,
'circle-dot': IconLucideCircleDot,
'circle-help': IconLucideCircleHelp,
'circle-minus': IconLucideCircleMinus,
'circle-pause': IconLucideCirclePause,
'circle-play': IconLucideCirclePlay,
'circle-plus': IconLucideCirclePlus,
'circle-user-round': IconLucideCircleUserRound,
'circle-x': IconLucideCircleX,
'clipboard-list': IconLucideClipboardList,
clock: IconLucideClock,
cloud: IconLucideCloud,
'cloud-download': IconLucideCloudDownload,
code: IconLucideCode,
cog: IconLucideCog,
contrast: IconLucideContrast,
copy: IconLucideCopy,
database: IconLucideDatabase,
earth: IconLucideEarth,
ellipsis: IconLucideEllipsis,
'ellipsis-vertical': IconLucideEllipsisVertical,
equal: IconLucideEqual,
'external-link': IconLucideExternalLink,
eye: IconLucideEye,
'eye-off': IconLucideEyeOff,
file: IconLucideFile,
'file-archive': IconLucideFileArchive,
'file-code': IconLucideFileCode,
'file-down': IconLucideFileDown,
'file-input': IconLucideFileInput,
'file-output': IconLucideFileOutput,
'file-text': IconLucideFileText,
files: IconLucideFiles,
fingerprint: IconLucideFingerprint,
'flask-conical': IconLucideFlaskConical,
folder: IconLucideFolder,
'folder-open': IconLucideFolderOpen,
'folder-plus': IconLucideFolderPlus,
funnel: IconLucideFunnel,
gem: IconLucideGem,
gift: IconLucideGift,
'git-branch': IconLucideGitBranch,
globe: IconLucideGlobe,
'graduation-cap': IconLucideGraduationCap,
'grid-2x2': IconLucideGrid2x2,
'grip-vertical': IconLucideGripVertical,
'hand-coins': IconLucideHandCoins,
handshake: IconLucideHandshake,
'hard-drive': IconLucideHardDrive,
'hard-drive-download': IconLucideHardDriveDownload,
hash: IconLucideHash,
history: IconLucideHistory,
hourglass: IconLucideHourglass,
house: IconLucideHouse,
image: IconLucideImage,
inbox: IconLucideInbox,
info: IconLucideInfo,
'key-round': IconLucideKeyRound,
languages: IconLucideLanguages,
layers: IconLucideLayers,
lightbulb: IconLucideLightbulb,
link: IconLucideLink,
list: IconLucideList,
'list-checks': IconLucideListChecks,
lock: IconLucideLock,
'log-in': IconLucideLogIn,
'log-out': IconLucideLogOut,
mail: IconLucideMail,
'minimize-2': IconLucideMinimize2,
maximize: IconLucideMaximize,
'maximize-2': IconLucideMaximize2,
menu: IconLucideMenu,
'message-circle': IconLucideMessageCircle,
'messages-square': IconLucideMessagesSquare,
milestone: IconLucideMilestone,
'mouse-pointer': IconLucideMousePointer,
network: IconLucideNetwork,
'package-open': IconLucidePackageOpen,
palette: IconLucidePalette,
pause: IconLucidePause,
pen: IconLucidePen,
pencil: IconLucidePencil,
pin: IconLucidePin,
play: IconLucidePlay,
plug: IconLucidePlug,
plus: IconLucidePlus,
'pocket-knife': IconLucidePocketKnife,
power: IconLucidePower,
'redo-2': IconLucideRedo2,
'refresh-cw': IconLucideRefreshCw,
'remove-formatting': IconLucideRemoveFormatting,
rss: IconLucideRss,
'satellite-dish': IconLucideSatelliteDish,
save: IconLucideSave,
scale: IconLucideScale,
scissors: IconLucideScissors,
search: IconLucideSearch,
send: IconLucideSend,
server: IconLucideServer,
share: IconLucideShare,
'sliders-horizontal': IconLucideSlidersHorizontal,
smile: IconLucideSmile,
square: IconLucideSquare,
'square-check': IconLucideSquareCheck,
'square-pen': IconLucideSquarePen,
'square-plus': IconLucideSquarePlus,
'sticky-note': IconLucideStickyNote,
sun: IconLucideSun,
table: IconLucideTable,
tags: IconLucideTags,
terminal: IconLucideTerminal,
'thumbs-down': IconLucideThumbsDown,
'thumbs-up': IconLucideThumbsUp,
'trash-2': IconLucideTrash2,
'tree-pine': IconLucideTreePine,
'triangle-alert': IconLucideTriangleAlert,
'undo-2': IconLucideUndo2,
unlink: IconLucideUnlink,
user: IconLucideUser,
'user-check': IconLucideUserCheck,
'user-lock': IconLucideUserLock,
'user-round': IconLucideUserRound,
users: IconLucideUsers,
vault: IconLucideVault,
video: IconLucideVideo,
waypoints: IconLucideWaypoints,
wrench: IconLucideWrench,
x: IconLucideX,
zap: IconLucideZap,
'zoom-in': IconLucideZoomIn,
'zoom-out': IconLucideZoomOut,
} as const;
export type IconName = keyof typeof updatedIconSet; // only new icon names should be used moving forward
export function isSupportedIconName(iconName?: string): iconName is IconName {
// support both deprecated and updated icon names
return (
typeof iconName === 'string' && (iconName in updatedIconSet || iconName in deprecatedIconSet)
);
}

View File

@@ -1,9 +1,8 @@
import { action } from '@storybook/addon-actions';
import type { StoryFn } from '@storybook/vue3';
import { TEST_ICONS } from './constants';
import type { Icon } from './IconPicker.vue';
import N8nIconPicker from './IconPicker.vue';
import { type IconOrEmoji } from './types';
export default {
title: 'Atoms/Icon Picker',
@@ -19,7 +18,7 @@ export default {
},
};
function createTemplate(icon: Icon): StoryFn {
function createTemplate(icon: IconOrEmoji): StoryFn {
return (args, { argTypes }) => ({
components: { N8nIconPicker },
props: Object.keys(argTypes),
@@ -39,13 +38,11 @@ const DefaultTemplate = createTemplate({ type: 'icon', value: 'smile' });
export const Default = DefaultTemplate.bind({});
Default.args = {
buttonTooltip: 'Select an icon',
availableIcons: TEST_ICONS,
};
const CustomTooltipTemplate = createTemplate({ type: 'icon', value: 'layer-group' });
const CustomTooltipTemplate = createTemplate({ type: 'icon', value: 'layers' });
export const WithCustomIconAndTooltip = CustomTooltipTemplate.bind({});
WithCustomIconAndTooltip.args = {
availableIcons: [...TEST_ICONS],
buttonTooltip: 'Select something...',
};

View File

@@ -3,7 +3,7 @@ import { fireEvent, render } from '@testing-library/vue';
import { createRouter, createWebHistory } from 'vue-router';
import IconPicker from '.';
import { TEST_ICONS } from './constants';
import { ALL_ICON_PICKER_ICONS } from './constants';
// Create a proxy handler that returns a mock icon object for any icon name
// and mock the entire icon library with the proxy
@@ -42,11 +42,6 @@ const components = {
template: '<button :data-icon="icon" data-testid="icon-picker-button" />',
props: ['icon'],
},
N8nIcon: {
template:
'<div class="mock-icon" :data-icon="typeof icon === \'string\' ? icon : icon.iconName" />',
props: ['icon'],
},
};
describe('IconPicker', () => {
@@ -55,12 +50,11 @@ describe('IconPicker', () => {
props: {
modelValue: { type: 'icon', value: 'smile' },
buttonTooltip: 'Select an icon',
availableIcons: TEST_ICONS,
},
global: {
plugins: [router],
components,
stubs: ['N8nButton'],
stubs: ['N8nButton', 'N8nIcon'],
},
});
const TEST_EMOJI_COUNT = 1962;
@@ -71,7 +65,8 @@ describe('IconPicker', () => {
expect(getByTestId('tab-icons').className).toContain('activeTab');
expect(getByTestId('icon-picker-popup')).toBeVisible();
// All icons should be rendered
expect(getAllByTestId('icon-picker-icon')).toHaveLength(TEST_ICONS.length);
expect(getAllByTestId('icon-picker-icon')).toHaveLength(ALL_ICON_PICKER_ICONS.length);
// Click on emojis tab
await fireEvent.click(getByTestId('tab-emojis'));
// Emojis tab should be active
@@ -79,13 +74,13 @@ describe('IconPicker', () => {
// All emojis should be rendered
expect(getAllByTestId('icon-picker-emoji')).toHaveLength(TEST_EMOJI_COUNT);
});
it('renders icon picker with custom icon and tooltip', async () => {
const ICON = 'layer-group';
const ICON = 'layers';
const TOOLTIP = 'Select something...';
const { getByTestId, getByRole } = render(IconPicker, {
props: {
modelValue: { type: 'icon', value: ICON },
availableIcons: [...TEST_ICONS],
buttonTooltip: TOOLTIP,
},
global: {
@@ -98,13 +93,13 @@ describe('IconPicker', () => {
expect(getByRole('tooltip').textContent).toBe(TOOLTIP);
expect(getByTestId('icon-picker-button')).toHaveAttribute('icon', ICON);
});
it('renders emoji as default icon correctly', async () => {
const ICON = '🔥';
const TOOLTIP = 'Select something...';
const { getByTestId, getByRole } = render(IconPicker, {
props: {
modelValue: { type: 'emoji', value: ICON },
availableIcons: [...TEST_ICONS],
buttonTooltip: TOOLTIP,
},
global: {
@@ -116,12 +111,12 @@ describe('IconPicker', () => {
expect(getByRole('tooltip').textContent).toBe(TOOLTIP);
expect(getByTestId('icon-picker-button')).toHaveTextContent(ICON);
});
it('renders icon picker with only emojis', () => {
const { queryByTestId } = render(IconPicker, {
props: {
modelValue: { type: 'icon', value: 'smile' },
buttonTooltip: 'Select an emoji',
availableIcons: [],
},
global: {
plugins: [router],
@@ -131,42 +126,43 @@ describe('IconPicker', () => {
});
expect(queryByTestId('tab-icons')).not.toBeInTheDocument();
});
it('is able to select an icon', async () => {
const { getByTestId, getAllByTestId, queryByTestId, emitted } = render(IconPicker, {
props: {
modelValue: { type: 'icon', value: 'smile' },
buttonTooltip: 'Select an icon',
availableIcons: TEST_ICONS,
},
global: {
plugins: [router],
components,
stubs: ['N8nButton'],
stubs: ['N8nIcon', 'N8nButton'],
},
});
await fireEvent.click(getByTestId('icon-picker-button'));
// Select the first icon
await fireEvent.click(getAllByTestId('icon-picker-icon')[0]);
// Icon should be selected and popup should be closed
expect(getByTestId('icon-picker-button')).toHaveAttribute('icon', TEST_ICONS[0]);
expect(getByTestId('icon-picker-button')).toHaveAttribute('icon', ALL_ICON_PICKER_ICONS[0]);
expect(queryByTestId('icon-picker-popup')).toBeNull();
expect(emitted()).toHaveProperty('update:modelValue');
// Should emit the selected icon
expect((emitted()['update:modelValue'] as unknown[][])[0][0]).toEqual({
type: 'icon',
value: TEST_ICONS[0],
value: ALL_ICON_PICKER_ICONS[0],
});
});
it('is able to select an emoji', async () => {
const { getByTestId, getAllByTestId, queryByTestId, emitted } = render(IconPicker, {
props: {
modelValue: { type: 'emoji', value: '🔥' },
buttonTooltip: 'Select an emoji',
availableIcons: TEST_ICONS,
},
global: {
plugins: [router],
components,
stubs: ['N8nIcon'],
},
});
await fireEvent.click(getByTestId('icon-picker-button'));
@@ -174,6 +170,7 @@ describe('IconPicker', () => {
expect(getByTestId('icon-picker-popup')).toBeVisible();
// Select the first emoji
await fireEvent.click(getAllByTestId('icon-picker-emoji')[0]);
// Emoji should be selected and popup should be closed
expect(getByTestId('icon-picker-button')).toHaveTextContent('😀');
expect(queryByTestId('icon-picker-popup')).toBeNull();

View File

@@ -5,6 +5,8 @@ import { onClickOutside } from '@vueuse/core';
import { isEmojiSupported } from 'is-emoji-supported';
import { ref, computed } from 'vue';
import { ALL_ICON_PICKER_ICONS } from './constants';
import type { IconOrEmoji } from './types';
import { useI18n } from '../../composables/useI18n';
import N8nButton from '../N8nButton';
import N8nIcon from '../N8nIcon';
@@ -31,27 +33,18 @@ const emojiRanges = [
[0x1f400, 0x1f4ff], // Additional pictographs
];
export type Icon = {
type: 'icon' | 'emoji';
value: string;
};
type Props = {
buttonTooltip: string;
availableIcons: string[];
buttonSize?: 'small' | 'large';
};
const { t } = useI18n();
const props = withDefaults(defineProps<Props>(), {
availableIcons: () => [],
buttonSize: 'large',
});
const model = defineModel<Icon>({ default: { type: 'icon', value: 'smile' } });
const hasAvailableIcons = computed(() => props.availableIcons.length > 0);
const model = defineModel<IconOrEmoji>({ default: { type: 'icon', value: 'smile' } });
const emojis = computed(() => {
const emojisArray: string[] = [];
@@ -67,15 +60,11 @@ const emojis = computed(() => {
});
const popupVisible = ref(false);
const tabs = ref<Array<{ value: string; label: string }>>(
hasAvailableIcons.value
? [
{ value: 'icons', label: t('iconPicker.tabs.icons') },
{ value: 'emojis', label: t('iconPicker.tabs.emojis') },
]
: [{ value: 'emojis', label: t('iconPicker.tabs.emojis') }],
);
const selectedTab = ref<string>(tabs.value[0].value);
const tabs: Array<{ value: string; label: string }> = [
{ value: 'icons', label: t('iconPicker.tabs.icons') },
{ value: 'emojis', label: t('iconPicker.tabs.emojis') },
];
const selectedTab = ref<string>(tabs[0].value);
const container = ref<HTMLDivElement>();
@@ -83,7 +72,7 @@ onClickOutside(container, () => {
popupVisible.value = false;
});
const selectIcon = (value: Icon) => {
const selectIcon = (value: IconOrEmoji) => {
model.value = value;
popupVisible.value = false;
};
@@ -91,7 +80,7 @@ const selectIcon = (value: Icon) => {
const togglePopup = () => {
popupVisible.value = !popupVisible.value;
if (popupVisible.value) {
selectedTab.value = tabs.value[0].value;
selectedTab.value = tabs[0].value;
}
};
</script>
@@ -112,7 +101,7 @@ const togglePopup = () => {
<N8nIconButton
v-if="model.type === 'icon'"
:class="$style['icon-button']"
:icon="model.value ?? 'smile'"
:icon="model.value"
:size="buttonSize"
:square="true"
type="tertiary"
@@ -138,11 +127,11 @@ const togglePopup = () => {
</div>
<div v-if="selectedTab === 'icons'" :class="$style.content">
<N8nIcon
v-for="icon in availableIcons"
v-for="icon in ALL_ICON_PICKER_ICONS"
:key="icon"
:icon="icon"
:class="$style.icon"
size="large"
:size="24"
data-test-id="icon-picker-icon"
@click="selectIcon({ type: 'icon', value: icon })"
/>

View File

@@ -1,163 +1,184 @@
export const TEST_ICONS = [
'angle-double-left',
'angle-down',
'angle-left',
'angle-right',
'angle-up',
import type { IconName } from '../N8nIcon/icons';
export const ALL_ICON_PICKER_ICONS: IconName[] = [
'folder-plus',
'share',
'user-check',
'check-check',
'circle',
'eye-off',
'folder',
'circle-minus',
'contrast',
'refresh-cw',
'vault',
'chevrons-left',
'archive',
'arrow-left',
'arrow-right',
'arrow-up',
'arrow-down',
'at',
'at-sign',
'ban',
'balance-scale-left',
'bars',
'bolt',
'scale',
'menu',
'zap',
'book',
'box-open',
'package-open',
'bug',
'brain',
'calculator',
'calendar',
'chart-bar',
'chart-column-decreasing',
'check',
'check-circle',
'check-square',
'circle-check',
'square-check',
'chevron-left',
'chevron-right',
'chevron-down',
'chevron-up',
'code',
'code-branch',
'git-branch',
'cog',
'cogs',
'comment',
'comments',
'message-circle',
'messages-square',
'clipboard-list',
'clock',
'clone',
'cloud',
'cloud-download-alt',
'copy',
'cube',
'cut',
'cloud',
'cloud-download',
'files',
'box',
'scissors',
'database',
'dot-circle',
'circle-dot',
'grip-lines-vertical',
'grip-vertical',
'edit',
'ellipsis-h',
'ellipsis-v',
'envelope',
'equals',
'square-pen',
'ellipsis',
'ellipsis-vertical',
'mail',
'equal',
'eye',
'exclamation-triangle',
'expand',
'expand-alt',
'external-link-alt',
'exchange-alt',
'triangle-alert',
'maximize',
'maximize-2',
'external-link',
'arrow-left-right',
'file',
'file-alt',
'file-text',
'file-archive',
'file-code',
'file-download',
'file-export',
'file-import',
'file-pdf',
'filter',
'file-down',
'file-output',
'file-input',
'file-text',
'funnel',
'fingerprint',
'flask',
'flask-conical',
'folder-open',
'font',
'case-upper',
'gift',
'globe',
'globe-americas',
'earth',
'graduation-cap',
'hand-holding-usd',
'hand-scissors',
'hand-coins',
'scissors',
'handshake',
'hand-point-left',
'hashtag',
'hdd',
'arrow-left',
'hash',
'hard-drive',
'history',
'home',
'house',
'hourglass',
'image',
'inbox',
'info',
'info-circle',
'key',
'language',
'layer-group',
'key-round',
'languages',
'layers',
'link',
'list',
'lightbulb',
'lock',
'map-signs',
'milestone',
'mouse-pointer',
'network-wired',
'network',
'palette',
'pause',
'pause-circle',
'circle-pause',
'pen',
'pencil-alt',
'pencil',
'play',
'play-circle',
'circle-play',
'plug',
'plus',
'plus-circle',
'plus-square',
'project-diagram',
'question',
'question-circle',
'redo',
'remove-format',
'robot',
'circle-plus',
'square-plus',
'waypoints',
'circle-help',
'circle-help',
'redo-2',
'remove-formatting',
'bot',
'rss',
'save',
'satellite-dish',
'search',
'search-minus',
'search-plus',
'zoom-out',
'zoom-in',
'server',
'screwdriver',
'pocket-knife',
'smile',
'sign-in-alt',
'sign-out-alt',
'sliders-h',
'spinner',
'log-in',
'log-out',
'sliders-horizontal',
'sticky-note',
'stop',
'stream',
'square',
'align-right',
'sun',
'sync',
'sync-alt',
'refresh-cw',
'table',
'tags',
'tasks',
'list-checks',
'terminal',
'th-large',
'thumbtack',
'grid-2x2',
'pin',
'thumbs-down',
'thumbs-up',
'times',
'times-circle',
'toolbox',
'tools',
'trash',
'undo',
'x',
'circle-x',
'wrench',
'trash-2',
'undo-2',
'unlink',
'user',
'user-circle',
'user-friends',
'circle-user-round',
'user-round',
'users',
'vector-square',
'video',
'tree',
'tree-pine',
'user-lock',
'gem',
'download',
'power-off',
'paper-plane',
'hard-drive-download',
'power',
'send',
'bell',
'variable',
'pop-out',
'triangle',
'status-completed',
'status-waiting',
'status-error',
'status-canceled',
'status-new',
'status-unknown',
'status-warning',
'vector-square',
'schema',
'json',
'binary',
'text',
'toolbox',
'spinner',
];

View File

@@ -0,0 +1,22 @@
import { type IconName } from '../N8nIcon/icons';
export type IconOrEmoji =
| {
type: 'icon';
value: IconName;
}
| {
type: 'emoji';
value: string;
};
export function isIconOrEmoji(icon: unknown): icon is IconOrEmoji {
return (
typeof icon === 'object' &&
icon !== null &&
'type' in icon &&
(icon.type === 'icon' || icon.type === 'emoji') &&
'value' in icon &&
typeof icon.value === 'string'
);
}

View File

@@ -5,13 +5,14 @@ import { onMounted, ref } from 'vue';
import type { IconColor } from '@n8n/design-system/types/icon';
import N8nIcon from '../N8nIcon';
import { type IconName } from '../N8nIcon/icons';
import N8nText from '../N8nText';
import N8nTooltip from '../N8nTooltip';
export interface IAccordionItem {
id: string;
label: string;
icon: string;
icon: IconName;
iconColor?: IconColor;
tooltip?: string | null;
}
@@ -21,7 +22,7 @@ interface InfoAccordionProps {
description?: string;
items?: IAccordionItem[];
initiallyExpanded?: boolean;
headerIcon?: { icon: string; color: IconColor };
headerIcon?: { icon: IconName; color: IconColor };
eventBus?: EventBus;
}

View File

@@ -5,7 +5,7 @@ import N8nInfoTip from './InfoTip.vue';
const slots = {
default: ['Need help doing something?', '<a href="/docs" target="_blank">Open docs</a>'],
};
const stubs = ['n8n-tooltip'];
const stubs = ['n8n-tooltip', 'n8n-icon'];
describe('N8nInfoTip', () => {
it('should render correctly as note', () => {
@@ -30,4 +30,17 @@ describe('N8nInfoTip', () => {
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should render correctly with a specific size', () => {
const wrapper = render(N8nInfoTip, {
slots,
props: {
size: 'large',
},
global: {
stubs,
},
});
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -2,23 +2,25 @@
import type { Placement } from 'element-plus';
import { computed } from 'vue';
import type { IconSize } from '@n8n/design-system/types';
import type { IconColor } from '@n8n/design-system/types/icon';
import N8nIcon from '../N8nIcon';
import { type IconName } from '../N8nIcon/icons';
import N8nTooltip from '../N8nTooltip';
const THEME = ['info', 'info-light', 'warning', 'warning-light', 'danger', 'success'] as const;
const TYPE = ['note', 'tooltip'] as const;
const ICON_MAP = {
info: 'info-circle',
const ICON_MAP: { [name: string]: IconName } = {
info: 'info',
// eslint-disable-next-line @typescript-eslint/naming-convention
'info-light': 'info-circle',
warning: 'exclamation-triangle',
'info-light': 'info',
warning: 'triangle-alert',
// eslint-disable-next-line @typescript-eslint/naming-convention
'warning-light': 'triangle', // NOTE: This requires a custom icon
danger: 'exclamation-triangle',
success: 'check-circle',
danger: 'triangle-alert',
success: 'circle-check',
} as const;
const COLOR_MAP: Record<keyof IconMap, IconColor> = {
@@ -40,6 +42,7 @@ interface InfoTipProps {
bold?: boolean;
tooltipPlacement?: Placement;
enterable?: boolean;
size?: IconSize;
}
defineOptions({ name: 'N8nInfoTip' });
@@ -49,9 +52,10 @@ const props = withDefaults(defineProps<InfoTipProps>(), {
bold: true,
tooltipPlacement: 'top',
enterable: true,
size: undefined,
});
const iconData = computed<{ icon: IconMap[keyof IconMap]; color: IconColor }>(() => {
const iconData = computed<{ icon: IconName; color: IconColor }>(() => {
return {
icon: ICON_MAP[props.theme],
color: COLOR_MAP[props.theme],
@@ -79,7 +83,7 @@ const iconData = computed<{ icon: IconMap[keyof IconMap]; color: IconColor }>(()
:enterable
>
<span :class="$style.iconText">
<N8nIcon :icon="iconData.icon" :color="iconData.color" />
<N8nIcon :icon="iconData.icon" :color="iconData.color" :size="size" />
</span>
<template #content>
<span>
@@ -88,7 +92,7 @@ const iconData = computed<{ icon: IconMap[keyof IconMap]; color: IconColor }>(()
</template>
</N8nTooltip>
<span v-else :class="$style.iconText">
<N8nIcon :icon="iconData.icon" :color="iconData.color" />
<N8nIcon :icon="iconData.icon" :color="iconData.color" :size="size" />
<span>
<slot />
</span>

View File

@@ -3,15 +3,22 @@
exports[`N8nInfoTip > should render correctly as note 1`] = `
"<div class="n8n-info-tip infoTip info note bold">
<!-- Note that the branching is required to support displaying
the slot either in the tooltip of the icon or following it --><span class="iconText"><span class="n8n-text text-base compact size-medium regular n8n-icon n8n-icon"><!----></span><span>Need help doing something?<a href="/docs" target="_blank">Open docs</a></span></span>
the slot either in the tooltip of the icon or following it --><span class="iconText"><n8n-icon-stub icon="info" spin="false" color="text-base"></n8n-icon-stub><span>Need help doing something?<a href="/docs" target="_blank">Open docs</a></span></span>
</div>"
`;
exports[`N8nInfoTip > should render correctly as tooltip 1`] = `
"<div class="n8n-info-tip infoTip info tooltip bold">
<!-- Note that the branching is required to support displaying
the slot either in the tooltip of the icon or following it --><span class="iconText el-tooltip__trigger el-tooltip__trigger"><span class="n8n-text text-base compact size-medium regular n8n-icon n8n-icon"><!----></span></span>
the slot either in the tooltip of the icon or following it --><span class="iconText el-tooltip__trigger el-tooltip__trigger"><n8n-icon-stub icon="info" spin="false" color="text-base"></n8n-icon-stub></span>
<!--teleport start-->
<!--teleport end-->
</div>"
`;
exports[`N8nInfoTip > should render correctly with a specific size 1`] = `
"<div class="n8n-info-tip infoTip info note bold">
<!-- Note that the branching is required to support displaying
the slot either in the tooltip of the icon or following it --><span class="iconText"><n8n-icon-stub icon="info" size="large" spin="false" color="text-base"></n8n-icon-stub><span>Need help doing something?<a href="/docs" target="_blank">Open docs</a></span></span>
</div>"
`;

View File

@@ -74,7 +74,7 @@ const addTargetBlank = (html: string) =>
:class="[$style.infoIcon, showTooltip ? $style.visible : $style.hidden]"
>
<N8nTooltip placement="top" :popper-class="$style.tooltipPopper" :show-after="300">
<N8nIcon :class="$style.icon" icon="question-circle" size="small" />
<N8nIcon :class="$style.icon" icon="circle-help" size="small" />
<template #content>
<div v-n8n-html="addTargetBlank(tooltipText)" />
</template>

View File

@@ -48,7 +48,7 @@ const templateWithHeaderAndFooter: StoryFn = (args, { argTypes }) => ({
</template>
<template #footer>
<div class="p-m hideme">
<n8n-icon icon="user-circle" size="xlarge"/>&nbsp;&nbsp;
<n8n-icon icon="circle-user-round" size="xlarge"/>&nbsp;&nbsp;
<n8n-text>John Smithson</n8n-text>
</div>
</template>
@@ -82,7 +82,7 @@ const templateWithAllSlots: StoryFn = (args, { argTypes }) => ({
</template>
<template #footer>
<div class="p-m hideme">
<n8n-icon icon="user-circle" size="xlarge"/>&nbsp;&nbsp;
<n8n-icon icon="circle-user-round" size="xlarge"/>&nbsp;&nbsp;
<n8n-text>John Smithson</n8n-text>
</div>
</template>
@@ -95,7 +95,7 @@ const templateWithAllSlots: StoryFn = (args, { argTypes }) => ({
const menuItems = [
{
id: 'workflows',
icon: 'network-wired',
icon: 'network',
label: 'Workflows',
position: 'top',
},
@@ -107,7 +107,7 @@ const menuItems = [
},
{
id: 'disabled-item',
icon: 'times',
icon: 'x',
label: 'Not Available',
available: false,
position: 'top',
@@ -132,7 +132,7 @@ const menuItems = [
{ icon: 'book', label: 'Documentation', id: 'docs' },
{
id: 'disabled-submenu-item',
icon: 'times',
icon: 'x',
label: 'Not Available',
available: false,
position: 'top',

View File

@@ -28,7 +28,7 @@ export const defaultMenuItem = template.bind({});
defaultMenuItem.args = {
item: {
id: 'workflows',
icon: 'heart',
icon: 'home',
label: 'Workflows',
},
};
@@ -37,7 +37,7 @@ export const withSecondaryIcon = template.bind({});
withSecondaryIcon.args = {
item: {
id: 'workflows',
icon: 'heart',
icon: 'home',
label: 'Workflows',
secondaryIcon: { name: 'lock', size: 'small' },
},
@@ -47,7 +47,7 @@ export const withSecondaryIconTooltip = template.bind({});
withSecondaryIconTooltip.args = {
item: {
id: 'workflows',
icon: 'heart',
icon: 'home',
label: 'Workflows',
secondaryIcon: {
name: 'lock',

View File

@@ -5,12 +5,13 @@ import type { RouteLocationRaw } from 'vue-router';
import ConditionalRouterLink from '../ConditionalRouterLink';
import N8nIcon from '../N8nIcon';
import { type IconName } from '../N8nIcon/icons';
type BaseItem = {
id: string;
title: string;
disabled?: boolean;
icon?: string;
icon?: IconName;
route?: RouteLocationRaw;
};

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { ElTag } from 'element-plus';
import { useI18n } from '../../composables/useI18n';
import type { NodeCreatorTag } from '../../types/node-creator-node';
import N8nIcon from '../N8nIcon';
export interface Props {
active?: boolean;
@@ -44,10 +44,10 @@ const { t } = useI18n();
<ElTag v-if="tag" :class="$style.tag" size="small" round :type="tag.type ?? 'success'">
{{ tag.text }}
</ElTag>
<FontAwesomeIcon
<N8nIcon
v-if="isTrigger"
icon="bolt"
size="xs"
icon="zap"
size="xsmall"
:title="t('nodeCreator.nodeItem.triggerIconTitle')"
:class="$style.triggerIcon"
/>
@@ -63,7 +63,7 @@ const { t } = useI18n();
</div>
<slot name="dragContent" />
<button v-if="showActionArrow" :class="$style.panelIcon">
<FontAwesomeIcon :class="$style.panelArrow" icon="arrow-right" />
<N8nIcon icon="arrow-right" size="large" />
</button>
</div>
</template>
@@ -110,10 +110,6 @@ const { t } = useI18n();
color: var(--color-text-base);
font-size: var(--font-size-2xs);
}
.panelArrow {
font-size: var(--font-size-2xs);
width: 12px;
}
.details {
display: flex;
align-items: center;

View File

@@ -26,14 +26,14 @@ FileIcon.args = {
export const FontIcon = DefaultTemplate.bind({});
FontIcon.args = {
type: 'icon',
name: 'cogs',
name: 'cog',
size: 200,
};
export const Hoverable = DefaultTemplate.bind({});
Hoverable.args = {
type: 'icon',
name: 'heart',
name: 'home',
color: 'red',
size: 200,
nodeTypeName: 'We ❤️ n8n',

View File

@@ -3,6 +3,9 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import type { Placement } from 'element-plus';
import { computed, getCurrentInstance } from 'vue';
import N8nIcon from '../N8nIcon';
import type { IconName } from '../N8nIcon/icons';
import { isSupportedIconName } from '../N8nIcon/icons';
import N8nTooltip from '../N8nTooltip';
type IconType = 'file' | 'icon' | 'unknown';
@@ -19,6 +22,8 @@ interface NodeIconProps {
showTooltip?: boolean;
tooltipPosition?: Placement;
badge?: { src: string; type: IconType };
// temporarily until we roll out FA icons for all nodes
useUpdatedIcons?: boolean;
}
const props = withDefaults(defineProps<NodeIconProps>(), {
@@ -72,6 +77,10 @@ const badgeStyleData = computed((): Record<string, string> => {
};
});
const updatedIconName = computed((): IconName | undefined => {
return props.useUpdatedIcons && isSupportedIconName(props.name) ? props.name : undefined;
});
// Get self component to avoid dependency cycle
const N8nNodeIcon = getCurrentInstance()?.type;
</script>
@@ -88,7 +97,9 @@ const N8nNodeIcon = getCurrentInstance()?.type;
>
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
<N8nTooltip v-if="showTooltip" :placement="tooltipPosition" :disabled="!showTooltip">
<template #content>{{ nodeTypeName }}</template>
<template #content>
{{ nodeTypeName }}
</template>
<div v-if="type !== 'unknown'" :class="$style.icon">
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
<FontAwesomeIcon v-else :icon="`${name}`" :class="$style.iconFa" :style="fontStyleData" />
@@ -100,6 +111,12 @@ const N8nNodeIcon = getCurrentInstance()?.type;
<template v-else>
<div v-if="type !== 'unknown'" :class="$style.icon">
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
<N8nIcon
v-else-if="updatedIconName"
:icon="updatedIconName"
:style="fontStyleData"
size="xlarge"
/>
<FontAwesomeIcon v-else :icon="`${name}`" :style="fontStyleData" />
<div v-if="badge" :class="$style.badge" :style="badgeStyleData">
<N8nNodeIcon :type="badge.type" :src="badge.src" :size="badgeSize" />

View File

@@ -19,6 +19,9 @@ describe('N8nSelectableList', () => {
modelValue: {},
inputs: [{ name: 'propA', initialValue: '' }],
},
global: {
stubs: ['n8n-icon'],
},
});
expect(wrapper.getByTestId('selectable-list-selectable-propA')).toBeInTheDocument();
@@ -49,6 +52,9 @@ describe('N8nSelectableList', () => {
{ name: 'propA', initialValue: '' },
],
},
global: {
stubs: ['n8n-icon'],
},
});
expect(wrapper.queryByTestId('selectable-list-selectable-propA')).not.toBeInTheDocument();
@@ -87,6 +93,9 @@ describe('N8nSelectableList', () => {
{ name: 'propC', initialValue: '' },
],
},
global: {
stubs: ['n8n-icon'],
},
});
expect(wrapper.queryByTestId('selectable-list-selectable-propA')).not.toBeInTheDocument();

View File

@@ -87,9 +87,10 @@ function itemComparator(a: Item, b: Item) {
:data-test-id="`selectable-list-slot-${item.name}`"
>
<N8nIcon
v-if="!disabled"
:class="$style.slotRemoveIcon"
size="xsmall"
:icon="disabled ? 'none' : 'trash'"
icon="trash-2"
:data-test-id="`selectable-list-remove-slot-${item.name}`"
@click="!disabled && removeFromSelectedItems(item.name)"
/>

View File

@@ -3,7 +3,8 @@
exports[`N8nSelectableList > renders disabled collection without selectables 1`] = `
"<div>
<!--v-if-->
<div class="slotComboContainer" data-test-id="selectable-list-slot-propB"><span class="n8n-text compact size-xsmall regular n8n-icon slotRemoveIcon slotRemoveIcon n8n-icon slotRemoveIcon slotRemoveIcon" data-test-id="selectable-list-remove-slot-propB"><!----></span>
<div class="slotComboContainer" data-test-id="selectable-list-slot-propB">
<!--v-if-->
<div class="slotContainer"></div>
</div>
</div>"
@@ -12,10 +13,12 @@ exports[`N8nSelectableList > renders disabled collection without selectables 1`]
exports[`N8nSelectableList > renders multiple elements with some pre-selected 1`] = `
"<div>
<div class="selectableContainer"><span class="selectableCell" data-test-id="selectable-list-selectable-propB"><div class="selectableTextSize">+ Add a propB</div></span><span class="selectableCell" data-test-id="selectable-list-selectable-propD"><div class="selectableTextSize">+ Add a propD</div></span></div>
<div class="slotComboContainer" data-test-id="selectable-list-slot-propA"><span class="n8n-text compact size-xsmall regular n8n-icon slotRemoveIcon slotRemoveIcon n8n-icon slotRemoveIcon slotRemoveIcon" data-test-id="selectable-list-remove-slot-propA"><!----></span>
<div class="slotComboContainer" data-test-id="selectable-list-slot-propA">
<n8n-icon-stub icon="trash-2" size="xsmall" spin="false" class="slotRemoveIcon" data-test-id="selectable-list-remove-slot-propA"></n8n-icon-stub>
<div class="slotContainer"></div>
</div>
<div class="slotComboContainer" data-test-id="selectable-list-slot-propC"><span class="n8n-text compact size-xsmall regular n8n-icon slotRemoveIcon slotRemoveIcon n8n-icon slotRemoveIcon slotRemoveIcon" data-test-id="selectable-list-remove-slot-propC"><!----></span>
<div class="slotComboContainer" data-test-id="selectable-list-slot-propC">
<n8n-icon-stub icon="trash-2" size="xsmall" spin="false" class="slotRemoveIcon" data-test-id="selectable-list-remove-slot-propC"></n8n-icon-stub>
<div class="slotContainer"></div>
</div>
</div>"

View File

@@ -97,7 +97,7 @@ const scrollRight = () => scroll(50);
<div>
{{ option.label }}
<span :class="$style.external">
<N8nIcon icon="external-link-alt" size="xsmall" />
<N8nIcon icon="external-link" size="small" />
</span>
</div>
</a>

View File

@@ -1,11 +1,13 @@
import type { KeyboardShortcut } from '@n8n/design-system/types/keyboardshortcut';
import type { IconName } from '../components/N8nIcon/icons';
export interface ActionDropdownItem {
id: string;
label: string;
badge?: string;
badgeProps?: Record<string, unknown>;
icon?: string;
icon?: IconName;
divided?: boolean;
disabled?: boolean;
shortcut?: KeyboardShortcut;

View File

@@ -1,5 +1,6 @@
import { type IconSize } from './icon';
import type { TextFloat } from './text';
import type { IconName } from '../components/N8nIcon/icons';
const BUTTON_ELEMENT = ['button', 'a'] as const;
export type ButtonElement = (typeof BUTTON_ELEMENT)[number];
@@ -17,7 +18,7 @@ export interface IconButtonProps {
active?: boolean;
disabled?: boolean;
float?: TextFloat;
icon?: string | string[];
icon?: IconName;
loading?: boolean;
outline?: boolean;
size?: ButtonSize;

View File

@@ -3,13 +3,17 @@ import type { AnchorHTMLAttributes, Component } from 'vue';
import type { RouteLocationRaw, RouterLinkProps } from 'vue-router';
import type { IconColor } from './icon';
import type { IconName } from '../components/N8nIcon/icons';
export type IMenuItem = {
id: string;
label: string;
icon?: string | { type: 'icon' | 'emoji'; value: string; color?: IconColor };
icon?:
| IconName
| { type: 'icon'; value: IconName; color?: IconColor }
| { type: 'emoji'; value: string; color?: IconColor };
secondaryIcon?: {
name: string;
name: IconName;
size?: 'xsmall' | 'small' | 'medium' | 'large';
tooltip?: Partial<ElTooltipProps>;
};

View File

@@ -1,9 +1,11 @@
import type { RouteLocationRaw } from 'vue-router';
import type { IconName } from '../components/N8nIcon/icons';
export interface TabOptions<Value extends string | number> {
value: Value;
label?: string;
icon?: string;
icon?: IconName;
href?: string;
tooltip?: string;
align?: 'left' | 'right';

View File

@@ -4,7 +4,7 @@
"baseUrl": ".",
"rootDirs": [".", "../composables/src"],
"outDir": "dist",
"types": ["vite/client", "vitest/globals"],
"types": ["vite/client", "unplugin-icons/types/vue", "vitest/globals"],
"typeRoots": [
"./node_modules/@testing-library",
"./node_modules/@types",

View File

@@ -21,7 +21,7 @@ export default mergeConfig(
dts: false,
resolvers: [
iconsResolver({
prefix: 'icon',
prefix: 'Icon',
}),
],
}),

View File

@@ -103,7 +103,7 @@
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
"@iconify/json": "^2.2.228",
"@iconify/json": "^2.2.349",
"@n8n/eslint-config": "workspace:*",
"@n8n/typescript-config": "workspace:*",
"@n8n/vitest-config": "workspace:*",
@@ -121,8 +121,8 @@
"browserslist-to-esbuild": "^2.1.1",
"fake-indexeddb": "^6.0.0",
"miragejs": "^0.1.48",
"unplugin-icons": "^0.19.0",
"unplugin-vue-components": "^0.27.2",
"unplugin-icons": "catalog:frontend",
"unplugin-vue-components": "catalog:frontend",
"vite": "catalog:",
"vite-plugin-static-copy": "2.2.0",
"vite-svg-loader": "5.1.0",

View File

@@ -70,6 +70,7 @@ import type { BulkCommand, Undoable } from '@/models/history';
import type { ProjectSharingData } from '@/types/projects.types';
import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
import { type IconName } from '@n8n/design-system/src/components/N8nIcon/icons';
export * from '@n8n/design-system/types';
@@ -668,7 +669,7 @@ export type SimplifiedNodeType = Pick<
export interface SubcategoryItemProps {
description?: string;
iconType?: string;
icon?: string;
icon?: IconName;
iconProps?: {
color?: string;
};
@@ -1072,7 +1073,7 @@ export interface ITab<Value extends string | number = string | number> {
value: Value;
label?: string;
href?: string;
icon?: string;
icon?: IconName;
align?: 'right';
tooltip?: string;
}

View File

@@ -137,7 +137,7 @@ const onBlur = (): void => {
type="tertiary"
text
size="mini"
icon="trash"
icon="trash-2"
data-test-id="assignment-remove"
:class="[$style.iconButton, $style.extraTopPadding]"
@click="onRemove"

View File

@@ -3,6 +3,7 @@ import { useI18n } from '@n8n/i18n';
import type { BaseTextKey } from '@n8n/i18n';
import { ASSIGNMENT_TYPES } from './constants';
import { computed } from 'vue';
import { type IconName } from '@n8n/design-system/components/N8nIcon/icons';
interface Props {
modelValue: string;
@@ -19,7 +20,9 @@ const i18n = useI18n();
const types = ASSIGNMENT_TYPES;
const icon = computed(() => types.find((type) => type.type === props.modelValue)?.icon ?? 'cube');
const icon = computed(
(): IconName => types.find((type) => type.type === props.modelValue)?.icon ?? 'box',
);
const onTypeChange = (type: string): void => {
emit('update:model-value', type);

View File

@@ -1,7 +1,9 @@
export const ASSIGNMENT_TYPES = [
{ type: 'string', icon: 'font' },
{ type: 'number', icon: 'hashtag' },
{ type: 'boolean', icon: 'check-square' },
import { type IconName } from '@n8n/design-system/components/N8nIcon/icons';
export const ASSIGNMENT_TYPES: Array<{ type: string; icon: IconName }> = [
{ type: 'string', icon: 'case-upper' },
{ type: 'number', icon: 'hash' },
{ type: 'boolean', icon: 'square-check' },
{ type: 'array', icon: 'list' },
{ type: 'object', icon: 'cube' },
{ type: 'object', icon: 'box' },
];

View File

@@ -37,8 +37,8 @@ const onClick = () => {
<template>
<el-tag :type="theme" :disable-transitions="true" :class="$style.container">
<font-awesome-icon
:icon="theme === 'success' ? 'check-circle' : 'exclamation-triangle'"
<n8n-icon
:icon="theme === 'success' ? 'circle-check' : 'triangle-alert'"
:class="theme === 'success' ? $style.icon : $style.dangerIcon"
/>
<div :class="$style.banner">

View File

@@ -28,7 +28,7 @@ const onClick = () => {
data-test-id="close-become-template-creator-cta"
@click="store.dismissCta()"
>
<n8n-icon icon="times" size="xsmall" :title="i18n.baseText('generic.close')" />
<n8n-icon icon="x" size="xsmall" :title="i18n.baseText('generic.close')" />
</button>
</div>

View File

@@ -256,7 +256,7 @@ onMounted(() => {
v-text="`${prompt.length} / ${ASK_AI_MAX_PROMPT_LENGTH}`"
/>
<a href="https://docs.n8n.io/code-examples/ai-code" target="_blank" :class="$style.help">
<n8n-icon icon="question-circle" color="text-light" size="large" />{{
<n8n-icon icon="circle-help" color="text-light" size="large" />{{
i18n.baseText('codeNodeEditor.askAi.help')
}}
</a>

View File

@@ -133,7 +133,7 @@ watch(
{{ i18n.baseText('settings.communityNodes.failedToLoad.tooltip') }}
</div>
</template>
<n8n-icon icon="exclamation-triangle" color="danger" size="large" />
<n8n-icon icon="triangle-alert" color="danger" size="large" />
</n8n-tooltip>
<n8n-tooltip
v-else-if="hasUnverifiedPackagesUpdate || hasVerifiedPackageUpdate"
@@ -152,7 +152,7 @@ watch(
{{ i18n.baseText('settings.communityNodes.upToDate.tooltip') }}
</div>
</template>
<n8n-icon icon="check-circle" color="text-light" size="large" />
<n8n-icon icon="circle-check" color="text-light" size="large" />
</n8n-tooltip>
<div :class="$style.cardActions">
<n8n-action-toggle :actions="packageActions" @action="onAction"></n8n-action-toggle>

View File

@@ -110,7 +110,7 @@ const onLearnMoreLinkClick = () => {
</div>
<n8n-button
:label="i18n.baseText('settings.communityNodes.browseButton.label')"
icon="external-link-alt"
icon="external-link"
:class="$style.browseButton"
@click="openNPMPage"
/>

View File

@@ -225,7 +225,7 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
</script>
<template>
<n8n-callout v-if="isManaged" theme="warning" icon="exclamation-triangle">
<n8n-callout v-if="isManaged" theme="warning" icon="triangle-alert">
{{ i18n.baseText('freeAi.credits.credentials.edit') }}
</n8n-callout>
<div v-else>

View File

@@ -1106,7 +1106,7 @@ const { width } = useElementSize(credNameRef);
<n8n-icon-button
v-if="currentCredential && credentialPermissions.delete"
:title="i18n.baseText('credentialEdit.credentialEdit.delete')"
icon="trash"
icon="trash-2"
type="tertiary"
:disabled="isSaving"
:loading="isDeleting"

View File

@@ -23,7 +23,7 @@ export const TEST_CREDENTIALS: ICredentialMap = {
id: '2',
type: 'team',
name: 'Test Project',
icon: { type: 'icon', value: 'exchange-alt' },
icon: { type: 'icon', value: 'arrow-left-right' },
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
@@ -51,7 +51,7 @@ export const TEST_CREDENTIALS: ICredentialMap = {
id: '2',
type: 'team',
name: 'Test Project',
icon: { type: 'icon', value: 'exchange-alt' },
icon: { type: 'icon', value: 'arrow-left-right' },
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
@@ -107,7 +107,7 @@ export const TEST_CREDENTIALS: ICredentialMap = {
id: '2',
type: 'team',
name: 'Test Project',
icon: { type: 'icon', value: 'exchange-alt' },
icon: { type: 'icon', value: 'arrow-left-right' },
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},

View File

@@ -89,7 +89,7 @@ function openCredentialType() {
@update:model-value="onSelect"
>
<template #prefix>
<font-awesome-icon icon="search" />
<n8n-icon icon="search" />
</template>
<N8nOption
v-for="credential in credentialsStore.allCredentialTypes"

View File

@@ -462,7 +462,7 @@ async function onAskAssistantClick() {
>
<div class="copy-button">
<N8nIconButton
icon="copy"
icon="files"
type="secondary"
size="mini"
:text="true"
@@ -484,7 +484,7 @@ async function onAskAssistantClick() {
class="node-error-view__details"
>
<summary class="node-error-view__details-summary">
<font-awesome-icon class="node-error-view__details-icon" icon="angle-right" />
<n8n-icon class="node-error-view__details-icon" icon="chevron-right" />
{{
i18n.baseText('nodeErrorView.details.from', {
interpolate: { node: `${nodeDefaultName}` },
@@ -539,7 +539,7 @@ async function onAskAssistantClick() {
<details class="node-error-view__details">
<summary class="node-error-view__details-summary">
<font-awesome-icon class="node-error-view__details-icon" icon="angle-right" />
<n8n-icon class="node-error-view__details-icon" icon="chevron-right" />
{{ i18n.baseText('nodeErrorView.details.info') }}
</summary>
<div class="node-error-view__details-content">

View File

@@ -1,34 +1,37 @@
import type { TestRunRecord } from '@/api/evaluation.ee';
import { type IconName } from '@n8n/design-system/components/N8nIcon/icons';
import type { IconColor } from '@n8n/design-system/types/icon';
export const statusDictionary: Record<TestRunRecord['status'], { icon: string; color: IconColor }> =
{
new: {
icon: 'status-new',
color: 'foreground-xdark',
},
running: {
icon: 'spinner',
color: 'secondary',
},
completed: {
icon: 'status-completed',
color: 'success',
},
error: {
icon: 'exclamation-triangle',
color: 'danger',
},
cancelled: {
icon: 'status-canceled',
color: 'foreground-xdark',
},
warning: {
icon: 'status-warning',
color: 'warning',
},
success: {
icon: 'status-completed',
color: 'success',
},
};
export const statusDictionary: Record<
TestRunRecord['status'],
{ icon: IconName; color: IconColor }
> = {
new: {
icon: 'status-new',
color: 'foreground-xdark',
},
running: {
icon: 'spinner',
color: 'secondary',
},
completed: {
icon: 'status-completed',
color: 'success',
},
error: {
icon: 'triangle-alert',
color: 'danger',
},
cancelled: {
icon: 'status-canceled',
color: 'foreground-xdark',
},
warning: {
icon: 'status-warning',
color: 'warning',
},
success: {
icon: 'status-completed',
color: 'success',
},
};

View File

@@ -205,7 +205,7 @@ defineExpose({ focus, select });
square
outline
type="tertiary"
icon="external-link-alt"
icon="external-link"
size="mini"
:class="$style['expression-editor-modal-opener']"
data-test-id="expander"

View File

@@ -133,11 +133,8 @@ async function onActionDropdownClick(id: string) {
</span>
</n8n-text>
</div>
<div v-if="provider.name === 'infisical'">
<font-awesome-icon
:class="$style['warningTriangle']"
icon="exclamation-triangle"
></font-awesome-icon>
<div :class="$style.deprecationWarning" v-if="provider.name === 'infisical'">
<n8n-icon :class="$style['warningTriangle']" icon="triangle-alert" />
<N8nBadge class="mr-xs" theme="tertiary" bold data-test-id="card-badge">
{{ i18n.baseText('settings.externalSecrets.card.deprecated') }}
</N8nBadge>
@@ -193,6 +190,10 @@ async function onActionDropdownClick(id: string) {
margin-left: var(--spacing-s);
}
.deprecationWarning {
display: flex;
}
.warningTriangle {
color: var(--color-warning);
margin-right: var(--spacing-2xs);

View File

@@ -68,7 +68,7 @@ async function onUpdateConnected(value: boolean) {
<n8n-icon
v-if="provider.state === 'error'"
color="danger"
icon="exclamation-triangle"
icon="triangle-alert"
class="mr-2xs"
/>
<n8n-text :color="connectedTextColor" bold class="mr-2xs">

View File

@@ -21,10 +21,7 @@ function onFeedback(feedback: 'positive' | 'negative') {
{{ i18n.baseText('feedback.title') }}
</N8nText>
<N8nText v-else :color="modelValue === 'positive' ? 'success' : 'danger'">
<FontAwesomeIcon
:icon="modelValue === 'positive' ? 'thumbs-up' : 'thumbs-down'"
class="mr-2xs"
/>
<N8nIcon :icon="modelValue === 'positive' ? 'thumbs-up' : 'thumbs-down'" class="mr-2xs" />
{{ i18n.baseText(`feedback.${modelValue}`) }}
</N8nText>
<N8nTooltip v-if="!modelValue" :content="i18n.baseText('feedback.positive')">
@@ -33,7 +30,7 @@ function onFeedback(feedback: 'positive' | 'negative') {
data-test-id="feedback-button-positive"
@click="onFeedback('positive')"
>
<FontAwesomeIcon icon="thumbs-up" />
<N8nIcon icon="thumbs-up" />
</span>
</N8nTooltip>
<N8nTooltip v-if="!modelValue" :content="i18n.baseText('feedback.negative')">
@@ -42,7 +39,7 @@ function onFeedback(feedback: 'positive' | 'negative') {
data-test-id="feedback-button-negative"
@click="onFeedback('negative')"
>
<FontAwesomeIcon icon="thumbs-down" />
<N8nIcon icon="thumbs-down" />
</span>
</N8nTooltip>
</div>

View File

@@ -168,7 +168,7 @@ const onBlur = (): void => {
type="tertiary"
text
size="mini"
icon="trash"
icon="trash-2"
data-test-id="filter-remove-condition"
:title="i18n.baseText('filter.removeCondition')"
:class="[$style.iconButton, $style.extraTopPadding]"
@@ -231,7 +231,7 @@ const onBlur = (): void => {
<template #content>
{{ i18n.baseText('filter.condition.resolvedTrue') }}
</template>
<n8n-icon :class="$style.statusIcon" icon="check-circle" size="medium" color="text-light" />
<n8n-icon :class="$style.statusIcon" icon="circle-check" size="medium" color="text-light" />
</n8n-tooltip>
<n8n-tooltip
@@ -241,7 +241,7 @@ const onBlur = (): void => {
<template #content>
{{ i18n.baseText('filter.condition.resolvedFalse') }}
</template>
<n8n-icon :class="$style.statusIcon" icon="times-circle" size="medium" color="text-light" />
<n8n-icon :class="$style.statusIcon" icon="circle-x" size="medium" color="text-light" />
</n8n-tooltip>
</div>
</div>

View File

@@ -285,13 +285,13 @@ export const OPERATOR_GROUPS: FilterOperatorGroup[] = [
{
id: 'string',
name: 'type.string',
icon: 'font',
icon: 'case-upper',
children: OPERATORS.filter((operator) => operator.type === 'string'),
},
{
id: 'number',
name: 'type.number',
icon: 'hashtag',
icon: 'hash',
children: OPERATORS.filter((operator) => operator.type === 'number'),
},
{
@@ -303,7 +303,7 @@ export const OPERATOR_GROUPS: FilterOperatorGroup[] = [
{
id: 'boolean',
name: 'type.boolean',
icon: 'check-square',
icon: 'square-check',
children: OPERATORS.filter((operator) => operator.type === 'boolean'),
},
{
@@ -315,7 +315,7 @@ export const OPERATOR_GROUPS: FilterOperatorGroup[] = [
{
id: 'object',
name: 'type.object',
icon: 'cube',
icon: 'box',
children: OPERATORS.filter((operator) => operator.type === 'object'),
},
];

View File

@@ -1,3 +1,4 @@
import type { IconName } from '@n8n/design-system/components/N8nIcon/icons';
import type { BaseTextKey } from '@n8n/i18n';
import type { FilterConditionValue, FilterOperatorValue } from 'n8n-workflow';
@@ -8,7 +9,7 @@ export interface FilterOperator extends FilterOperatorValue {
export interface FilterOperatorGroup {
id: string;
name: BaseTextKey;
icon?: string;
icon?: IconName;
children: FilterOperator[];
}

View File

@@ -287,7 +287,7 @@ const trackWorkflowInputFieldAdded = () => {
type="tertiary"
text
size="mini"
icon="trash"
icon="trash-2"
data-test-id="fixed-collection-delete"
:title="locale.baseText('fixedCollectionParameter.deleteItem')"
@click="deleteOption(property.name, index)"
@@ -315,7 +315,7 @@ const trackWorkflowInputFieldAdded = () => {
type="tertiary"
text
size="mini"
icon="trash"
icon="trash-2"
data-test-id="fixed-collection-delete"
:title="locale.baseText('fixedCollectionParameter.deleteItem')"
@click="deleteOption(property.name)"

View File

@@ -130,6 +130,7 @@ const onBreadcrumbItemClick = async (item: PathItem) => {
:class="$style['folder-icon']"
icon="folder"
size="xlarge"
:strokeWidth="1"
/>
</template>
<template #header>

View File

@@ -1,11 +1,8 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useI18n } from '@n8n/i18n';
import {
type Project,
type ProjectIcon as ProjectIconType,
ProjectTypes,
} from '@/types/projects.types';
import { type Project, ProjectTypes } from '@/types/projects.types';
import { isIconOrEmoji, type IconOrEmoji } from '@n8n/design-system/components/N8nIconPicker/types';
type Props = {
currentProject: Project;
@@ -23,13 +20,15 @@ const emit = defineEmits<{
const i18n = useI18n();
const projectIcon = computed((): ProjectIconType => {
const projectIcon = computed((): IconOrEmoji => {
if (props.currentProject?.type === ProjectTypes.Personal) {
return { type: 'icon', value: 'user' };
} else if (props.currentProject?.name) {
return props.currentProject.icon ?? { type: 'icon', value: 'layer-group' };
return isIconOrEmoji(props.currentProject.icon)
? props.currentProject.icon
: { type: 'icon', value: 'layers' };
} else {
return { type: 'icon', value: 'home' };
return { type: 'icon', value: 'house' };
}
});

View File

@@ -93,7 +93,7 @@ const onClaimCreditsClicked = async () => {
<n8n-callout
v-if="userCanClaimOpenAiCredits && !showSuccessCallout"
theme="secondary"
icon="exclamation-circle"
icon="circle-alert"
>
{{
i18n.baseText('freeAi.credits.callout.claim.title', {
@@ -110,7 +110,7 @@ const onClaimCreditsClicked = async () => {
/>
</template>
</n8n-callout>
<n8n-callout v-else-if="showSuccessCallout" theme="success" icon="check-circle">
<n8n-callout v-else-if="showSuccessCallout" theme="success" icon="circle-check">
<n8n-text size="small">
{{
i18n.baseText('freeAi.credits.callout.success.title.part1', {

Some files were not shown because too many files have changed in this diff Show More