diff --git a/addons/docs/src/frameworks/svelte/config.ts b/addons/docs/src/frameworks/svelte/config.ts
index 4a4fd37a39fb..a5e41a726139 100644
--- a/addons/docs/src/frameworks/svelte/config.ts
+++ b/addons/docs/src/frameworks/svelte/config.ts
@@ -1,6 +1,7 @@
import { extractArgTypes } from './extractArgTypes';
import { extractComponentDescription } from '../../lib/docgen';
import { prepareForInline } from './prepareForInline';
+import { sourceDecorator } from './sourceDecorator';
export const parameters = {
docs: {
@@ -10,3 +11,5 @@ export const parameters = {
extractComponentDescription,
},
};
+
+export const decorators = [sourceDecorator];
diff --git a/addons/docs/src/frameworks/svelte/sourceDecorator.test.ts b/addons/docs/src/frameworks/svelte/sourceDecorator.test.ts
new file mode 100644
index 000000000000..e8e2dcc2be02
--- /dev/null
+++ b/addons/docs/src/frameworks/svelte/sourceDecorator.test.ts
@@ -0,0 +1,44 @@
+import { Args } from '@storybook/api';
+import { generateSvelteSource } from './sourceDecorator';
+
+expect.addSnapshotSerializer({
+ print: (val: any) => val,
+ test: (val) => typeof val === 'string',
+});
+
+function generateForArgs(args: Args, slotProperty: string = null) {
+ return generateSvelteSource({ name: 'Component' }, args, {}, slotProperty);
+}
+
+describe('generateSvelteSource', () => {
+ test('boolean true', () => {
+ expect(generateForArgs({ bool: true })).toMatchInlineSnapshot(``);
+ });
+ test('boolean false', () => {
+ expect(generateForArgs({ bool: false })).toMatchInlineSnapshot(``);
+ });
+ test('null property', () => {
+ expect(generateForArgs({ propnull: null })).toMatchInlineSnapshot(``);
+ });
+ test('string property', () => {
+ expect(generateForArgs({ str: 'mystr' })).toMatchInlineSnapshot(``);
+ });
+ test('number property', () => {
+ expect(generateForArgs({ count: 42 })).toMatchInlineSnapshot(``);
+ });
+ test('object property', () => {
+ expect(generateForArgs({ obj: { x: true } })).toMatchInlineSnapshot(
+ ``
+ );
+ });
+ test('multiple properties', () => {
+ expect(generateForArgs({ a: 1, b: 2 })).toMatchInlineSnapshot(``);
+ });
+ test('slot property', () => {
+ expect(generateForArgs({ content: 'xyz', myProp: 'abc' }, 'content')).toMatchInlineSnapshot(`
+
+ xyz
+
+ `);
+ });
+});
diff --git a/addons/docs/src/frameworks/svelte/sourceDecorator.ts b/addons/docs/src/frameworks/svelte/sourceDecorator.ts
new file mode 100644
index 000000000000..7b7998f18908
--- /dev/null
+++ b/addons/docs/src/frameworks/svelte/sourceDecorator.ts
@@ -0,0 +1,167 @@
+import { addons, StoryContext } from '@storybook/addons';
+import { ArgTypes, Args } from '@storybook/api';
+
+import { SourceType, SNIPPET_RENDERED } from '../../shared';
+
+/**
+ * Check if the sourcecode should be generated.
+ *
+ * @param context StoryContext
+ */
+const skipSourceRender = (context: StoryContext) => {
+ const sourceParams = context?.parameters.docs?.source;
+ const isArgsStory = context?.parameters.__isArgsStory;
+
+ // always render if the user forces it
+ if (sourceParams?.type === SourceType.DYNAMIC) {
+ return false;
+ }
+
+ // never render if the user is forcing the block to render code, or
+ // if the user provides code, or if it's not an args story.
+ return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE;
+};
+
+/**
+ * Transform a key/value to a svelte declaration as string.
+ *
+ * Default values are ommited
+ *
+ * @param key Key
+ * @param value Value
+ * @param argTypes Component ArgTypes
+ */
+function toSvelteProperty(key: string, value: any, argTypes: ArgTypes): string {
+ if (value === undefined || value === null) {
+ return null;
+ }
+
+ // default value ?
+ if (argTypes[key] && argTypes[key].defaultValue === value) {
+ return null;
+ }
+
+ if (value === true) {
+ return key;
+ }
+
+ if (typeof value === 'string') {
+ return `${key}=${JSON.stringify(value)}`;
+ }
+
+ return `${key}={${JSON.stringify(value)}}`;
+}
+
+/**
+ * Extract a component name.
+ *
+ * @param component Component
+ */
+function getComponentName(component: any): string {
+ const { __docgen = {} } = component;
+ let { name } = __docgen;
+
+ if (!name) {
+ return component.name;
+ }
+
+ if (name.endsWith('.svelte')) {
+ name = name.substring(0, name.length - 7);
+ }
+ return name;
+}
+
+/**
+ * Generate a svelte template.
+ *
+ * @param component Component
+ * @param args Args
+ * @param argTypes ArgTypes
+ * @param slotProperty Property used to simulate a slot
+ */
+export function generateSvelteSource(
+ component: any,
+ args: Args,
+ argTypes: ArgTypes,
+ slotProperty: string
+): string {
+ const name = getComponentName(component);
+
+ if (!name) {
+ return null;
+ }
+
+ const props = Object.entries(args)
+ .filter(([k]) => k !== slotProperty)
+ .map(([k, v]) => toSvelteProperty(k, v, argTypes))
+ .filter((p) => p)
+ .join(' ');
+
+ const slotValue = slotProperty ? args[slotProperty] : null;
+
+ if (slotValue) {
+ return `<${name} ${props}>\n ${slotValue}\n${name}>`;
+ }
+
+ return `<${name} ${props}/>`;
+}
+
+/**
+ * Check if the story component is a wrapper to the real component.
+ *
+ * A component can be annoted with @wrapper to indicate that
+ * it's just a wrapper for the real tested component. If it's the case
+ * then the code generated references the real component, not the wrapper.
+ *
+ * moreover, a wrapper can annotate a property with @slot : this property
+ * is then assumed to be an alias to the default slot.
+ *
+ * @param component Component
+ */
+function getWrapperProperties(component: any) {
+ const { __docgen } = component;
+ if (!__docgen) {
+ return { wrapper: false };
+ }
+
+ // the component should be declared as a wrapper
+ if (!__docgen.keywords.find((kw: any) => kw.name === 'wrapper')) {
+ return { wrapper: false };
+ }
+
+ const slotProp = __docgen.data.find((prop: any) =>
+ prop.keywords.find((kw: any) => kw.name === 'slot')
+ );
+ return { wrapper: true, slotProperty: slotProp?.name as string };
+}
+
+/**
+ * Svelte source decorator.
+ * @param storyFn Fn
+ * @param context StoryContext
+ */
+export const sourceDecorator = (storyFn: any, context: StoryContext) => {
+ const story = storyFn();
+
+ if (skipSourceRender(context)) {
+ return story;
+ }
+
+ const channel = addons.getChannel();
+
+ const { parameters = {}, args = {} } = context || {};
+ let { Component: component = {} } = story;
+
+ const { wrapper, slotProperty } = getWrapperProperties(component);
+ if (wrapper) {
+ component = parameters.component;
+ }
+
+ const source = generateSvelteSource(component, args, context?.argTypes, slotProperty);
+
+ if (source) {
+ channel.emit(SNIPPET_RENDERED, (context || {}).id, source);
+ }
+
+ return story;
+};
diff --git a/examples/svelte-kitchen-sink/src/stories/__snapshots__/button.stories.storyshot b/examples/svelte-kitchen-sink/src/stories/__snapshots__/button.stories.storyshot
index df6542344eef..33a9896cd07a 100644
--- a/examples/svelte-kitchen-sink/src/stories/__snapshots__/button.stories.storyshot
+++ b/examples/svelte-kitchen-sink/src/stories/__snapshots__/button.stories.storyshot
@@ -20,7 +20,7 @@ exports[`Storyshots Button Rounded 1`] = `
- You clicked
+ Rounded
:
0
@@ -63,7 +63,7 @@ exports[`Storyshots Button Square 1`] = `
- You clicked
+ Square
:
0
diff --git a/examples/svelte-kitchen-sink/src/stories/button.stories.js b/examples/svelte-kitchen-sink/src/stories/button.stories.js
index 92e5cfeb8526..51dd804077d5 100644
--- a/examples/svelte-kitchen-sink/src/stories/button.stories.js
+++ b/examples/svelte-kitchen-sink/src/stories/button.stories.js
@@ -1,8 +1,9 @@
import ButtonView from './views/ButtonView.svelte';
+import Button from '../components/Button.svelte';
export default {
title: 'Button',
- component: ButtonView,
+ component: Button,
};
const Template = (args) => ({
@@ -15,11 +16,11 @@ const Template = (args) => ({
export const Rounded = Template.bind({});
Rounded.args = {
rounded: true,
- message: 'Squared text',
+ text: 'Rounded',
};
export const Square = Template.bind({});
Square.args = {
rounded: false,
- message: 'Squared text',
+ text: 'Square',
};
diff --git a/examples/svelte-kitchen-sink/src/stories/views/ButtonView.svelte b/examples/svelte-kitchen-sink/src/stories/views/ButtonView.svelte
index 3d6da76ecdb2..7306df6a054a 100644
--- a/examples/svelte-kitchen-sink/src/stories/views/ButtonView.svelte
+++ b/examples/svelte-kitchen-sink/src/stories/views/ButtonView.svelte
@@ -1,9 +1,10 @@