From f758a4bd05723b44acdf6786410c5da54a5936a2 Mon Sep 17 00:00:00 2001 From: Jan <3185243+JanOstrowka@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:51:58 +0200 Subject: [PATCH] feat(editor): Add N8nStatusDot component (#34963) Co-authored-by: Cursor --- .../N8nStatusDot/StatusDot.stories.ts | 83 +++++++++++++++++++ .../components/N8nStatusDot/StatusDot.test.ts | 36 ++++++++ .../N8nStatusDot/StatusDot.types.ts | 6 ++ .../src/components/N8nStatusDot/StatusDot.vue | 63 ++++++++++++++ .../__snapshots__/StatusDot.test.ts.snap | 5 ++ .../src/components/N8nStatusDot/index.ts | 5 ++ .../design-system/src/components/index.ts | 1 + 7 files changed, 199 insertions(+) create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.stories.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.test.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.types.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nStatusDot/__snapshots__/StatusDot.test.ts.snap create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nStatusDot/index.ts diff --git a/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.stories.ts b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.stories.ts new file mode 100644 index 00000000000..82ea6211ae9 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.stories.ts @@ -0,0 +1,83 @@ +import type { StoryObj } from '@storybook/vue3-vite'; + +import N8nStatusDot from './StatusDot.vue'; + +const meta = { + title: 'Core/StatusDot', + component: N8nStatusDot, + argTypes: { + variant: { + control: 'select', + options: ['success', 'warning', 'danger'], + }, + pulse: { + control: 'boolean', + }, + }, + parameters: { + docs: { + description: { + component: + 'A small decorative status indicator dot. Pair it with text that carries the meaning; the dot itself is hidden from assistive technology.', + }, + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Default = { + render: (args) => ({ + components: { N8nStatusDot }, + setup: () => ({ args }), + template: '', + }), + args: { + variant: 'success', + pulse: false, + }, +} satisfies Story; + +export const Variants = { + render: () => ({ + components: { N8nStatusDot }, + template: ` +
+
+ Success +
+
+ Warning +
+
+ Danger +
+
+ `, + }), +} satisfies Story; + +export const Pulsing = { + render: () => ({ + components: { N8nStatusDot }, + template: ` +
+
+ Enabled +
+
+ Disabled +
+
+ `, + }), + parameters: { + docs: { + description: { + story: + 'The pulse draws attention to a live or active state. The animation is disabled when the user prefers reduced motion.', + }, + }, + }, +} satisfies Story; diff --git a/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.test.ts b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.test.ts new file mode 100644 index 00000000000..1fbf19ad2c1 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.test.ts @@ -0,0 +1,36 @@ +import { render } from '@testing-library/vue'; + +import N8nStatusDot from './StatusDot.vue'; + +describe('components/N8nStatusDot', () => { + it('should render success variant without pulse by default', () => { + const wrapper = render(N8nStatusDot); + const dot = wrapper.container.firstElementChild; + expect(dot?.className).toContain('success'); + expect(dot?.className).not.toContain('pulse'); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should be hidden from assistive technology', () => { + const wrapper = render(N8nStatusDot); + expect(wrapper.container.firstElementChild).toHaveAttribute('aria-hidden', 'true'); + }); + + it.each(['success', 'warning', 'danger'] as const)( + 'should render %s variant class', + (variant) => { + const wrapper = render(N8nStatusDot, { + props: { variant }, + }); + expect(wrapper.container.firstElementChild?.className).toContain(variant); + }, + ); + + it('should render pulse class when pulse is enabled', () => { + const wrapper = render(N8nStatusDot, { + props: { pulse: true }, + }); + expect(wrapper.container.firstElementChild?.className).toContain('pulse'); + expect(wrapper.html()).toMatchSnapshot(); + }); +}); diff --git a/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.types.ts b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.types.ts new file mode 100644 index 00000000000..fb860017573 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.types.ts @@ -0,0 +1,6 @@ +export type StatusDotVariant = 'success' | 'warning' | 'danger'; + +export interface StatusDotProps { + variant?: StatusDotVariant; + pulse?: boolean; +} diff --git a/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.vue b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.vue new file mode 100644 index 00000000000..e5c5befe681 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/StatusDot.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/__snapshots__/StatusDot.test.ts.snap b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/__snapshots__/StatusDot.test.ts.snap new file mode 100644 index 00000000000..39a9e3cb5f8 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/__snapshots__/StatusDot.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`components/N8nStatusDot > should render pulse class when pulse is enabled 1`] = `""`; + +exports[`components/N8nStatusDot > should render success variant without pulse by default 1`] = `""`; diff --git a/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/index.ts b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/index.ts new file mode 100644 index 00000000000..6f5933e4683 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/N8nStatusDot/index.ts @@ -0,0 +1,5 @@ +import N8nStatusDot from './StatusDot.vue'; + +export default N8nStatusDot; + +export type * from './StatusDot.types'; diff --git a/packages/frontend/@n8n/design-system/src/components/index.ts b/packages/frontend/@n8n/design-system/src/components/index.ts index 018015ad2c0..75f6b99f8e2 100644 --- a/packages/frontend/@n8n/design-system/src/components/index.ts +++ b/packages/frontend/@n8n/design-system/src/components/index.ts @@ -109,6 +109,7 @@ export { default as N8nRecycleScroller } from './N8nRecycleScroller'; export { default as N8nResizeWrapper } from './N8nResizeWrapper'; export { default as N8nSelect } from './N8nSelect'; export { default as N8nSpinner } from './N8nSpinner'; +export { default as N8nStatusDot } from './N8nStatusDot'; export { default as N8nSticky } from './N8nSticky'; export { default as N8nResizeableSticky } from './N8nResizeableSticky'; export { default as N8nSuggestedActions } from './N8nSuggestedActions';