mirror of
https://github.com/n8n-io/n8n.git
synced 2026-07-27 19:15:01 +02:00
feat(core): Allow Storybook components to be viewed in light and dark mode (#34361)
This commit is contained in:
parent
54db083953
commit
e592b210c1
|
|
@ -39,6 +39,23 @@ function findItemByLabel(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
function getProviderLogoStyle(logoUrl: string) {
|
||||
return {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '999px',
|
||||
backgroundColor: 'var(--icon-color)',
|
||||
maskImage: `url(${logoUrl})`,
|
||||
WebkitMaskImage: `url(${logoUrl})`,
|
||||
maskSize: 'contain',
|
||||
WebkitMaskSize: 'contain',
|
||||
maskRepeat: 'no-repeat',
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
maskPosition: 'center',
|
||||
WebkitMaskPosition: 'center',
|
||||
};
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'AI/AiModelSelectorDropdown',
|
||||
component: N8nAiModelSelectorDropdown,
|
||||
|
|
@ -94,25 +111,32 @@ const meta = {
|
|||
},
|
||||
);
|
||||
|
||||
return { storyArgs, selectedProviderLogo, getProviderLogo, handleSearch, handleSelect };
|
||||
return {
|
||||
storyArgs,
|
||||
selectedProviderLogo,
|
||||
getProviderLogo,
|
||||
getProviderLogoStyle,
|
||||
handleSearch,
|
||||
handleSelect,
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<N8nAiModelSelectorDropdown v-bind="storyArgs" @search="handleSearch" @select="handleSelect">
|
||||
<template #trigger-leading="{ ui }">
|
||||
<img
|
||||
<span
|
||||
v-if="selectedProviderLogo"
|
||||
:class="ui.class"
|
||||
:src="selectedProviderLogo"
|
||||
alt="OpenAI"
|
||||
style="width: 20px; height: 20px; border-radius: 999px;"
|
||||
role="img"
|
||||
aria-label="Selected provider"
|
||||
:style="getProviderLogoStyle(selectedProviderLogo)"
|
||||
/>
|
||||
</template>
|
||||
<template #item-leading="{ item, ui }">
|
||||
<img
|
||||
<span
|
||||
:class="ui.class"
|
||||
:src="getProviderLogo(item.id)"
|
||||
alt=""
|
||||
style="width: 20px; height: 20px; border-radius: 999px;"
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
:style="getProviderLogoStyle(getProviderLogo(item.id))"
|
||||
/>
|
||||
</template>
|
||||
</N8nAiModelSelectorDropdown>
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ const InteractiveTemplate: StoryFn = (args, { argTypes }) => ({
|
|||
},
|
||||
template: `
|
||||
<div>
|
||||
<div style="width: 500px; max-width: 100%; margin-bottom: 20px;">
|
||||
<div style="width: 500px; max-width: 100%; margin-bottom: var(--spacing--md);">
|
||||
<n8n-chat-input
|
||||
v-bind="args"
|
||||
:modelValue="val"
|
||||
|
|
@ -398,11 +398,30 @@ const InteractiveTemplate: StoryFn = (args, { argTypes }) => ({
|
|||
@blur="onBlur"
|
||||
/>
|
||||
</div>
|
||||
<div style="padding: 10px; background: #f0f0f0; border-radius: 4px;">
|
||||
<div
|
||||
style="
|
||||
padding: var(--spacing--xs);
|
||||
background: var(--background--subtle);
|
||||
color: var(--text-color);
|
||||
border: var(--border);
|
||||
border-radius: var(--radius);
|
||||
"
|
||||
>
|
||||
<p><strong>Current value:</strong> {{ val }}</p>
|
||||
<p><strong>Character count:</strong> {{ val.length }} / {{ args.maxLength }}</p>
|
||||
<p><strong>Streaming:</strong> {{ streaming }}</p>
|
||||
<button @click="streaming = !streaming" style="margin-top: 10px;">
|
||||
<button
|
||||
style="
|
||||
margin-top: var(--spacing--xs);
|
||||
padding: var(--spacing--3xs) var(--spacing--xs);
|
||||
color: var(--text-color);
|
||||
background: var(--background--surface);
|
||||
border: var(--border);
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
"
|
||||
@click="streaming = !streaming"
|
||||
>
|
||||
Toggle Streaming (Current: {{ streaming ? 'ON' : 'OFF' }})
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { StoryFn } from '@storybook/vue3-vite';
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||
import { action } from 'storybook/actions';
|
||||
|
||||
import N8nFloatingWindow from './FloatingWindow.vue';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
|
||||
export default {
|
||||
const meta: Meta<typeof N8nFloatingWindow> = {
|
||||
title: 'Core/FloatingWindow',
|
||||
component: N8nFloatingWindow,
|
||||
argTypes: {
|
||||
|
|
@ -31,40 +31,63 @@ export default {
|
|||
iframeHeight: 500,
|
||||
},
|
||||
},
|
||||
themePreview: {
|
||||
minHeight: 520,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template: StoryFn = (args, { argTypes }) => ({
|
||||
setup: () => ({
|
||||
args,
|
||||
onClose: action('close'),
|
||||
onResize: action('resize'),
|
||||
onMove: action('move'),
|
||||
render: (args) => ({
|
||||
components: {
|
||||
N8nFloatingWindow,
|
||||
N8nIcon,
|
||||
},
|
||||
setup: () => ({
|
||||
args,
|
||||
onClose: action('close'),
|
||||
onResize: action('resize'),
|
||||
onMove: action('move'),
|
||||
}),
|
||||
template: `
|
||||
<div
|
||||
style="
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
overflow: hidden;
|
||||
"
|
||||
>
|
||||
<n8n-floating-window
|
||||
v-bind="args"
|
||||
style="position: absolute;"
|
||||
@close="onClose"
|
||||
@resize="onResize"
|
||||
@move="onMove"
|
||||
>
|
||||
<template #header-icon>
|
||||
<n8n-icon icon="comment-dots" size="medium" />
|
||||
</template>
|
||||
<template #header>Floating Window</template>
|
||||
<template #header-actions>
|
||||
<n8n-icon icon="expand" size="small" style="cursor: pointer;" />
|
||||
</template>
|
||||
<div style="padding: 16px;">
|
||||
<p>This is the default slot content. The window can be dragged by its header and resized from any edge or corner.</p>
|
||||
</div>
|
||||
</n8n-floating-window>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nFloatingWindow,
|
||||
N8nIcon,
|
||||
},
|
||||
template: `<n8n-floating-window v-bind="args" @close="onClose" @resize="onResize" @move="onMove">
|
||||
<template #header-icon>
|
||||
<n8n-icon icon="comment-dots" size="medium" />
|
||||
</template>
|
||||
<template #header>Floating Window</template>
|
||||
<template #header-actions>
|
||||
<n8n-icon icon="expand" size="small" style="cursor: pointer;" />
|
||||
</template>
|
||||
<div style="padding: 16px;">
|
||||
<p>This is the default slot content. The window can be dragged by its header and resized from any edge or corner.</p>
|
||||
</div>
|
||||
</n8n-floating-window>`,
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
width: 560,
|
||||
height: 400,
|
||||
minWidth: 400,
|
||||
minHeight: 300,
|
||||
initialPosition: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
width: 360,
|
||||
height: 280,
|
||||
minWidth: 240,
|
||||
minHeight: 180,
|
||||
initialPosition: { x: 16, y: 16 },
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { StoryFn } from '@storybook/vue3-vite';
|
||||
|
||||
import N8nKeyboardShorcut from './N8nKeyboardShortcut.vue';
|
||||
import N8nKeyboardShortcut from './N8nKeyboardShortcut.vue';
|
||||
|
||||
export default {
|
||||
title: 'Core/KeyboardShortcut',
|
||||
component: N8nKeyboardShorcut,
|
||||
component: N8nKeyboardShortcut,
|
||||
|
||||
parameters: {
|
||||
docs: {
|
||||
|
|
@ -17,7 +17,7 @@ const template: StoryFn = (args, { argTypes }) => ({
|
|||
setup: () => ({ args }),
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nKeyboardShorcut,
|
||||
N8nKeyboardShortcut,
|
||||
},
|
||||
template: '<n8n-keyboard-shortcut v-bind="args" />',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { StoryFn } from '@storybook/vue3-vite';
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||
import { action } from 'storybook/actions';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import N8nMarkdownEditor from './MarkdownEditor.vue';
|
||||
|
||||
|
|
@ -63,15 +64,7 @@ Escalate to a human when:
|
|||
|
||||
When a workflow error is provided, identify the failing node, summarize the likely cause, and suggest the smallest next diagnostic step.`;
|
||||
|
||||
const methods = {
|
||||
onUpdateModelValue: action('update:modelValue'),
|
||||
onInput: action('input'),
|
||||
onFocus: action('focus'),
|
||||
onBlur: action('blur'),
|
||||
onReady: action('ready'),
|
||||
};
|
||||
|
||||
export default {
|
||||
const meta: Meta<typeof N8nMarkdownEditor> = {
|
||||
title: 'Core/Markdown Editor',
|
||||
component: N8nMarkdownEditor,
|
||||
argTypes: {
|
||||
|
|
@ -107,108 +100,123 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
render: (args) => ({
|
||||
components: { N8nMarkdownEditor },
|
||||
setup() {
|
||||
const value = ref(args.modelValue);
|
||||
const editorArgs = computed(() => {
|
||||
const { modelValue: _modelValue, ...rest } = args;
|
||||
return rest;
|
||||
});
|
||||
|
||||
const Template: StoryFn = (args, { argTypes }) => ({
|
||||
setup: () => ({ args }),
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nMarkdownEditor,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: args.modelValue,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'args.modelValue'(value: string) {
|
||||
this.value = value;
|
||||
return {
|
||||
value,
|
||||
editorArgs,
|
||||
onUpdateModelValue: action('update:modelValue'),
|
||||
onInput: action('input'),
|
||||
onFocus: action('focus'),
|
||||
onBlur: action('blur'),
|
||||
onReady: action('ready'),
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div style="max-width: 760px; display: flex; flex-direction: column; gap: 16px;">
|
||||
<n8n-markdown-editor
|
||||
v-bind="editorArgs"
|
||||
v-model="value"
|
||||
@update:modelValue="onUpdateModelValue"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@ready="onReady"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
modelValue: defaultMarkdown,
|
||||
variant: 'contained',
|
||||
placeholder: 'Write Markdown...',
|
||||
showToolbar: 'always',
|
||||
maxHeight: '480px',
|
||||
disabled: false,
|
||||
readonly: false,
|
||||
},
|
||||
template: `
|
||||
<div style="max-width: 760px; display: flex; flex-direction: column; gap: 16px;">
|
||||
<n8n-markdown-editor
|
||||
v-bind="args"
|
||||
v-model="value"
|
||||
@update:modelValue="onUpdateModelValue"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@ready="onReady"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
methods,
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
modelValue: defaultMarkdown,
|
||||
variant: 'contained',
|
||||
placeholder: 'Write Markdown...',
|
||||
showToolbar: 'always',
|
||||
maxHeight: '480px',
|
||||
disabled: false,
|
||||
readonly: false,
|
||||
};
|
||||
|
||||
export const Contained = Template.bind({});
|
||||
Contained.args = {
|
||||
...Default.args,
|
||||
variant: 'contained',
|
||||
export const Contained: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: 'contained',
|
||||
},
|
||||
};
|
||||
|
||||
export const Ghost = Template.bind({});
|
||||
Ghost.args = {
|
||||
...Default.args,
|
||||
variant: 'ghost',
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: 'ghost',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithoutToolbar = Template.bind({});
|
||||
WithoutToolbar.args = {
|
||||
...Default.args,
|
||||
showToolbar: 'never',
|
||||
export const WithoutToolbar: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
showToolbar: 'never',
|
||||
},
|
||||
};
|
||||
|
||||
export const AlwaysVisibleToolbar = Template.bind({});
|
||||
AlwaysVisibleToolbar.args = {
|
||||
...Default.args,
|
||||
showToolbar: 'always',
|
||||
export const AlwaysVisibleToolbar: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
showToolbar: 'always',
|
||||
},
|
||||
};
|
||||
|
||||
export const GfmContent = Template.bind({});
|
||||
GfmContent.args = {
|
||||
...Default.args,
|
||||
modelValue: gfmMarkdown,
|
||||
variant: 'contained',
|
||||
export const GfmContent: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
modelValue: gfmMarkdown,
|
||||
variant: 'contained',
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
...Default.args,
|
||||
disabled: true,
|
||||
variant: 'contained',
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
disabled: true,
|
||||
variant: 'contained',
|
||||
},
|
||||
};
|
||||
|
||||
export const Readonly = Template.bind({});
|
||||
Readonly.args = {
|
||||
...Default.args,
|
||||
readonly: true,
|
||||
variant: 'contained',
|
||||
export const Readonly: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
readonly: true,
|
||||
variant: 'contained',
|
||||
},
|
||||
};
|
||||
|
||||
export const LongInstructions = Template.bind({});
|
||||
LongInstructions.args = {
|
||||
...Default.args,
|
||||
modelValue: longInstructionsMarkdown,
|
||||
variant: 'contained',
|
||||
export const LongInstructions: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
modelValue: longInstructionsMarkdown,
|
||||
variant: 'contained',
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty = Template.bind({});
|
||||
Empty.args = {
|
||||
...Default.args,
|
||||
modelValue: '',
|
||||
showToolbar: 'never',
|
||||
variant: 'contained',
|
||||
placeholder: 'Write instructions...',
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
modelValue: '',
|
||||
showToolbar: 'never',
|
||||
variant: 'contained',
|
||||
placeholder: 'Write instructions...',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import type { StoryFn } from '@storybook/vue3-vite';
|
||||
import type { Component } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import N8nSelectableList from './SelectableList.vue';
|
||||
|
||||
|
|
@ -14,14 +16,14 @@ export default {
|
|||
},
|
||||
};
|
||||
|
||||
const Template: StoryFn = (args, { argTypes }) => ({
|
||||
setup: () => ({
|
||||
args: { ...args, modelValue: undefined },
|
||||
model: args.modelValue,
|
||||
}),
|
||||
props: Object.keys(argTypes),
|
||||
// Generics make this difficult to type
|
||||
components: N8nSelectableList as never,
|
||||
const Template: StoryFn = (args) => ({
|
||||
setup: () => {
|
||||
const model = ref(args.modelValue);
|
||||
return { args, model };
|
||||
},
|
||||
components: {
|
||||
N8nSelectableList: N8nSelectableList as unknown as Component,
|
||||
},
|
||||
template:
|
||||
'<n8n-selectable-list v-bind="args" v-model="model"><template #displayItem="{ name }">Slot content for {{name}}</template></n8n-selectable-list>',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { StoryFn } from '@storybook/vue3-vite';
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||
import { action } from 'storybook/actions';
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import N8nSticky from './Sticky.vue';
|
||||
import N8nResizeWrapper from '../N8nResizeWrapper/ResizeWrapper.vue';
|
||||
|
||||
export default {
|
||||
const meta: Meta<typeof N8nSticky> = {
|
||||
title: 'Core/Sticky',
|
||||
component: N8nSticky,
|
||||
argTypes: {
|
||||
|
|
@ -17,27 +17,27 @@ export default {
|
|||
},
|
||||
height: {
|
||||
control: {
|
||||
control: 'number',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
minHeight: {
|
||||
control: {
|
||||
control: 'number',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
minWidth: {
|
||||
control: {
|
||||
control: 'number',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
readOnly: {
|
||||
control: {
|
||||
control: 'Boolean',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
width: {
|
||||
control: {
|
||||
control: 'number',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -47,93 +47,144 @@ export default {
|
|||
component: 'An editable sticky-note component with markdown content and color variants.',
|
||||
},
|
||||
},
|
||||
themePreview: {
|
||||
minHeight: 240,
|
||||
},
|
||||
},
|
||||
};
|
||||
render: (args) => ({
|
||||
components: { N8nSticky },
|
||||
setup() {
|
||||
const value = ref(args.modelValue);
|
||||
const editMode = ref(false);
|
||||
const stickyArgs = computed(() => {
|
||||
const { modelValue: _modelValue, editMode: _editMode, ...rest } = args;
|
||||
return rest;
|
||||
});
|
||||
|
||||
const methods = {
|
||||
onEdit: action('edit'),
|
||||
onMarkdownClick: action('markdown-click'),
|
||||
onUpdateModelValue: action('update:modelValue'),
|
||||
onResize: action('resize'),
|
||||
onResizeEnd: action('resizeend'),
|
||||
onResizeStart: action('resizestart'),
|
||||
};
|
||||
const onEdit = (editing: boolean) => {
|
||||
editMode.value = editing;
|
||||
action('edit')(editing);
|
||||
};
|
||||
|
||||
const Template: StoryFn = (args, { argTypes }) => ({
|
||||
setup: () => ({ args }),
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nSticky,
|
||||
},
|
||||
template: `
|
||||
<n8n-sticky
|
||||
v-bind="args"
|
||||
@edit="onEdit"
|
||||
@markdown-click="onMarkdownClick"
|
||||
@update:modelValue="onUpdateModelValue"
|
||||
></n8n-sticky>
|
||||
`,
|
||||
methods,
|
||||
});
|
||||
|
||||
export const Sticky = Template.bind({});
|
||||
Sticky.args = {
|
||||
height: 180,
|
||||
width: 240,
|
||||
modelValue:
|
||||
"## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/components/sticky-notes/)",
|
||||
minHeight: 80,
|
||||
minWidth: 150,
|
||||
readOnly: false,
|
||||
backgroundColor: 1,
|
||||
};
|
||||
|
||||
const ResizableTemplate: StoryFn = (args) => ({
|
||||
setup: () => {
|
||||
const width = ref(args.width ?? 240);
|
||||
const height = ref(args.height ?? 180);
|
||||
|
||||
const onResize = (resizeData: { height: number; width: number }) => {
|
||||
action('resize')(resizeData);
|
||||
height.value = resizeData.height;
|
||||
width.value = resizeData.width;
|
||||
};
|
||||
|
||||
return { args, width, height, onResize };
|
||||
},
|
||||
components: {
|
||||
N8nResizeWrapper,
|
||||
N8nSticky,
|
||||
},
|
||||
template: `
|
||||
<div style="width: fit-content; height: fit-content; position: relative;">
|
||||
<n8n-resize-wrapper
|
||||
:is-resizing-enabled="!args.readOnly"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:min-width="args.minWidth"
|
||||
:min-height="args.minHeight"
|
||||
:grid-size="20"
|
||||
:scale="1"
|
||||
@resize="onResize"
|
||||
@resizeend="onResizeEnd"
|
||||
@resizestart="onResizeStart"
|
||||
>
|
||||
return {
|
||||
value,
|
||||
editMode,
|
||||
stickyArgs,
|
||||
onEdit,
|
||||
onMarkdownClick: action('markdown-click'),
|
||||
onUpdateModelValue: action('update:modelValue'),
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div :style="{ position: 'relative', width: stickyArgs.width + 'px', height: stickyArgs.height + 'px' }">
|
||||
<n8n-sticky
|
||||
v-bind="args"
|
||||
:width="width"
|
||||
:height="height"
|
||||
v-bind="stickyArgs"
|
||||
v-model="value"
|
||||
:edit-mode="editMode"
|
||||
@edit="onEdit"
|
||||
@markdown-click="onMarkdownClick"
|
||||
@update:modelValue="onUpdateModelValue"
|
||||
></n8n-sticky>
|
||||
</n8n-resize-wrapper>
|
||||
</div>
|
||||
`,
|
||||
methods,
|
||||
});
|
||||
|
||||
export const Resizable = ResizableTemplate.bind({});
|
||||
Resizable.args = {
|
||||
...Sticky.args,
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Sticky: Story = {
|
||||
args: {
|
||||
height: 180,
|
||||
width: 240,
|
||||
modelValue:
|
||||
"## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/components/sticky-notes/)",
|
||||
minHeight: 80,
|
||||
minWidth: 150,
|
||||
readOnly: false,
|
||||
backgroundColor: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const Resizable: Story = {
|
||||
args: {
|
||||
...Sticky.args,
|
||||
},
|
||||
render: (args) => ({
|
||||
components: {
|
||||
N8nResizeWrapper,
|
||||
N8nSticky,
|
||||
},
|
||||
setup() {
|
||||
const value = ref(args.modelValue);
|
||||
const editMode = ref(false);
|
||||
const width = ref(args.width ?? 240);
|
||||
const height = ref(args.height ?? 180);
|
||||
|
||||
const stickyArgs = computed(() => {
|
||||
const {
|
||||
modelValue: _modelValue,
|
||||
editMode: _editMode,
|
||||
width: _width,
|
||||
height: _height,
|
||||
...rest
|
||||
} = args;
|
||||
return rest;
|
||||
});
|
||||
|
||||
const onEdit = (editing: boolean) => {
|
||||
editMode.value = editing;
|
||||
action('edit')(editing);
|
||||
};
|
||||
|
||||
const onResize = (resizeData: { height: number; width: number }) => {
|
||||
action('resize')(resizeData);
|
||||
height.value = resizeData.height;
|
||||
width.value = resizeData.width;
|
||||
};
|
||||
|
||||
return {
|
||||
args,
|
||||
value,
|
||||
editMode,
|
||||
width,
|
||||
height,
|
||||
stickyArgs,
|
||||
onEdit,
|
||||
onResize,
|
||||
onMarkdownClick: action('markdown-click'),
|
||||
onUpdateModelValue: action('update:modelValue'),
|
||||
onResizeEnd: action('resizeend'),
|
||||
onResizeStart: action('resizestart'),
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div style="width: fit-content; height: fit-content; position: relative;">
|
||||
<n8n-resize-wrapper
|
||||
:is-resizing-enabled="!args.readOnly"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:min-width="args.minWidth"
|
||||
:min-height="args.minHeight"
|
||||
:grid-size="20"
|
||||
:scale="1"
|
||||
@resize="onResize"
|
||||
@resizeend="onResizeEnd"
|
||||
@resizestart="onResizeStart"
|
||||
>
|
||||
<n8n-sticky
|
||||
v-bind="stickyArgs"
|
||||
v-model="value"
|
||||
:edit-mode="editMode"
|
||||
:width="width"
|
||||
:height="height"
|
||||
@edit="onEdit"
|
||||
@markdown-click="onMarkdownClick"
|
||||
@update:modelValue="onUpdateModelValue"
|
||||
/>
|
||||
</n8n-resize-wrapper>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import type { StoryFn } from '@storybook/vue3-vite';
|
||||
|
||||
import UserStack from './UserStack.vue';
|
||||
import N8nUserStack from './UserStack.vue';
|
||||
|
||||
export default {
|
||||
title: 'Core/UserStack',
|
||||
component: UserStack,
|
||||
component: N8nUserStack,
|
||||
|
||||
parameters: {
|
||||
docs: {
|
||||
|
|
@ -16,9 +16,8 @@ export default {
|
|||
|
||||
const Template: StoryFn = (args) => ({
|
||||
setup: () => ({ args }),
|
||||
props: args,
|
||||
components: {
|
||||
UserStack,
|
||||
N8nUserStack,
|
||||
},
|
||||
template: '<n8n-user-stack v-bind="args" />',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1245,11 +1245,12 @@
|
|||
@include theme;
|
||||
}
|
||||
|
||||
body[data-theme='light'] {
|
||||
[data-theme='light'] {
|
||||
@include theme;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
body[data-theme='dark'] {
|
||||
[data-theme='dark'] {
|
||||
@include theme-dark;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
@include meta.load-css('highlight.js/styles/github-dark-dimmed.css');
|
||||
}
|
||||
|
||||
body[data-theme='dark'] {
|
||||
[data-theme='dark'] {
|
||||
@include hljs-dark-theme;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ const config: StorybookConfig = {
|
|||
},
|
||||
},
|
||||
},
|
||||
getAbsolutePath('@storybook/addon-themes'),
|
||||
getAbsolutePath('storybook-addon-vue-mdx'),
|
||||
],
|
||||
framework: getAbsolutePath('@storybook/vue3-vite'),
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
export const allModes = {
|
||||
dark: {
|
||||
theme: 'dark',
|
||||
},
|
||||
light: {
|
||||
theme: 'light',
|
||||
},
|
||||
} as const;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { IconBodyLoaderKey, N8nPlugin } from '@n8n/design-system';
|
||||
import { loadLucideIconBody } from '@n8n/design-system/icons/lucide';
|
||||
import { i18nInstance } from '@n8n/i18n';
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import { setup } from '@storybook/vue3';
|
||||
import ElementPlus from 'element-plus';
|
||||
// @ts-expect-error no types
|
||||
|
|
@ -10,7 +9,7 @@ import { createPinia } from 'pinia';
|
|||
import { createMemoryHistory, createRouter } from 'vue-router';
|
||||
|
||||
import './storybook.scss';
|
||||
import { allModes } from './modes';
|
||||
import { withThemePreview } from './withThemePreview';
|
||||
// import '../src/css/tailwind/index.css';
|
||||
|
||||
setup((app) => {
|
||||
|
|
@ -42,40 +41,10 @@ export const parameters = {
|
|||
},
|
||||
},
|
||||
backgrounds: {
|
||||
default: '--color--background--light-3',
|
||||
values: [
|
||||
{
|
||||
name: '--color--background--shade-2',
|
||||
value: 'var(--color--background--shade-2)',
|
||||
},
|
||||
{
|
||||
name: '--color--background',
|
||||
value: 'var(--color--background)',
|
||||
},
|
||||
{
|
||||
name: '--color--background--light-2',
|
||||
value: 'var(--color--background--light-2)',
|
||||
},
|
||||
{
|
||||
name: '--color--background--light-3',
|
||||
value: 'var(--color--background--light-3)',
|
||||
},
|
||||
],
|
||||
disable: true,
|
||||
},
|
||||
themes: {
|
||||
default: 'light',
|
||||
list: [
|
||||
{
|
||||
name: 'light',
|
||||
class: 'theme-light',
|
||||
color: '#fff',
|
||||
},
|
||||
{
|
||||
name: 'dark',
|
||||
class: 'theme-dark-beta',
|
||||
color: '#000',
|
||||
},
|
||||
],
|
||||
disable: true,
|
||||
},
|
||||
options: {
|
||||
storySort: {
|
||||
|
|
@ -93,24 +62,10 @@ export const parameters = {
|
|||
},
|
||||
},
|
||||
chromatic: {
|
||||
modes: {
|
||||
light: allModes['light'],
|
||||
dark: allModes['dark'],
|
||||
},
|
||||
disableSnapshot: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const decorators = [
|
||||
withThemeByDataAttribute({
|
||||
themes: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
defaultTheme: 'light',
|
||||
attributeName: 'data-theme',
|
||||
parentSelector: 'body',
|
||||
}),
|
||||
];
|
||||
export const decorators = [withThemePreview];
|
||||
|
||||
export const tags = ['autodocs'];
|
||||
|
|
|
|||
|
|
@ -21,6 +21,25 @@ body {
|
|||
gap: 1rem;
|
||||
}
|
||||
|
||||
.theme-side-by-side {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing--md);
|
||||
}
|
||||
|
||||
.theme-side-by-side__panel {
|
||||
position: relative;
|
||||
padding: var(--spacing--md);
|
||||
background: var(--background--surface);
|
||||
color: var(--text-color);
|
||||
border-radius: var(--radius--lg);
|
||||
overflow: auto;
|
||||
|
||||
&[data-theme='light'] {
|
||||
border: var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
.sbdocs .sbdocs-content table:not(.sbdocs-preview table, .docs-story table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
import type { Decorator } from '@storybook/vue3';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
function applyBodyTheme(theme: Theme) {
|
||||
document.body.dataset.theme = theme;
|
||||
document.body.style.colorScheme = theme;
|
||||
}
|
||||
|
||||
function panelMinHeight(value: unknown): string | undefined {
|
||||
if (typeof value === 'number') {
|
||||
return `${value}px`;
|
||||
}
|
||||
if (typeof value === 'string' && value.length > 0) {
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function themePreviewMinHeight(parameters: Record<string, unknown>): unknown {
|
||||
const themePreview = parameters.themePreview;
|
||||
if (typeof themePreview !== 'object' || themePreview === null || !('minHeight' in themePreview)) {
|
||||
return undefined;
|
||||
}
|
||||
return themePreview.minHeight;
|
||||
}
|
||||
|
||||
function isTheme(value: string | undefined): value is Theme {
|
||||
return value === 'light' || value === 'dark';
|
||||
}
|
||||
|
||||
export const withThemePreview: Decorator = (story, context) => {
|
||||
const minHeight = panelMinHeight(themePreviewMinHeight(context.parameters));
|
||||
|
||||
return {
|
||||
components: { storyComponent: story() },
|
||||
setup: () => ({
|
||||
panelStyle: minHeight ? { minHeight } : undefined,
|
||||
}),
|
||||
mounted() {
|
||||
applyBodyTheme('light');
|
||||
},
|
||||
unmounted() {
|
||||
document.body.removeAttribute('data-theme');
|
||||
document.body.style.removeProperty('color-scheme');
|
||||
},
|
||||
methods: {
|
||||
onPanelInteract(event: Event) {
|
||||
const target = event.currentTarget;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const theme = target.dataset.theme;
|
||||
if (isTheme(theme)) {
|
||||
applyBodyTheme(theme);
|
||||
}
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="theme-side-by-side">
|
||||
<section
|
||||
class="theme-side-by-side__panel"
|
||||
:style="panelStyle"
|
||||
data-theme="light"
|
||||
@pointerenter="onPanelInteract"
|
||||
@pointerdown.capture="onPanelInteract"
|
||||
>
|
||||
<storyComponent />
|
||||
</section>
|
||||
<section
|
||||
class="theme-side-by-side__panel"
|
||||
:style="panelStyle"
|
||||
data-theme="dark"
|
||||
@pointerenter="onPanelInteract"
|
||||
@pointerdown.capture="onPanelInteract"
|
||||
>
|
||||
<storyComponent />
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
|
@ -33,7 +33,6 @@
|
|||
"@n8n/typescript-config": "workspace:*",
|
||||
"@storybook/addon-a11y": "catalog:storybook",
|
||||
"@storybook/addon-docs": "catalog:storybook",
|
||||
"@storybook/addon-themes": "catalog:storybook",
|
||||
"@storybook/addon-vitest": "catalog:storybook",
|
||||
"@storybook/vue3": "catalog:storybook",
|
||||
"@storybook/vue3-vite": "catalog:storybook",
|
||||
|
|
|
|||
|
|
@ -643,9 +643,6 @@ catalogs:
|
|||
'@storybook/addon-docs':
|
||||
specifier: ^10.1.11
|
||||
version: 10.1.11
|
||||
'@storybook/addon-themes':
|
||||
specifier: ^10.1.11
|
||||
version: 10.1.11
|
||||
'@storybook/addon-vitest':
|
||||
specifier: ^10.1.11
|
||||
version: 10.1.11
|
||||
|
|
@ -5649,9 +5646,6 @@ importers:
|
|||
'@storybook/addon-docs':
|
||||
specifier: catalog:storybook
|
||||
version: 10.1.11(@types/react@18.0.27)(esbuild@0.28.1)(rollup@4.52.4)(storybook@10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))
|
||||
'@storybook/addon-themes':
|
||||
specifier: catalog:storybook
|
||||
version: 10.1.11(storybook@10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10))
|
||||
'@storybook/addon-vitest':
|
||||
specifier: catalog:storybook
|
||||
version: 10.1.11(@vitest/browser-playwright@4.1.9)(@vitest/browser@4.1.9)(@vitest/runner@4.1.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(storybook@10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10))(vitest@4.1.9)
|
||||
|
|
@ -11908,11 +11902,6 @@ packages:
|
|||
peerDependencies:
|
||||
storybook: ^10.1.11
|
||||
|
||||
'@storybook/addon-themes@10.1.11':
|
||||
resolution: {integrity: sha512-tUX5C1ms+W4GFK8UBWd3Fq4irkLc3h092BqW90tZghcoOmGY/sfKR+PlcLhoaTs/kkHQSSHPrz8HSFR1AXVbHA==}
|
||||
peerDependencies:
|
||||
storybook: ^10.1.11
|
||||
|
||||
'@storybook/addon-vitest@10.1.11':
|
||||
resolution: {integrity: sha512-YbZzeKO3v+Xr97/malT4DZIATkVZT5EHNYx3xzEfPVuk19dDETAVYXO+tzcqCQHsgdKQHkmd56vv8nN3J3/kvw==}
|
||||
peerDependencies:
|
||||
|
|
@ -29238,11 +29227,6 @@ snapshots:
|
|||
- vite
|
||||
- webpack
|
||||
|
||||
'@storybook/addon-themes@10.1.11(storybook@10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10))':
|
||||
dependencies:
|
||||
storybook: 10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10)
|
||||
ts-dedent: 2.2.0
|
||||
|
||||
'@storybook/addon-vitest@10.1.11(@vitest/browser-playwright@4.1.9)(@vitest/browser@4.1.9)(@vitest/runner@4.1.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(storybook@10.1.11(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(prettier@3.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10))(vitest@4.1.9)':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
|
|
|
|||
|
|
@ -262,7 +262,6 @@ catalogs:
|
|||
'@chromatic-com/storybook': ^4.1.3
|
||||
'@storybook/addon-a11y': ^10.1.11
|
||||
'@storybook/addon-docs': ^10.1.11
|
||||
'@storybook/addon-themes': ^10.1.11
|
||||
'@storybook/addon-vitest': ^10.1.11
|
||||
'@storybook/vue3': ^10.1.11
|
||||
'@storybook/vue3-vite': ^10.1.11
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user