feat!(vue3): remove args-as-props & replace with returning args from setup

This commit is contained in:
Blaine Bublitz 2021-02-20 17:21:33 -07:00
parent bbade6137e
commit 64a8885681
14 changed files with 88 additions and 68 deletions

View File

@ -14,8 +14,6 @@ import { IStorybookSection, StoryFnVueReturnType } from './types';
import render, { storybookApp } from './render';
const PROPS = 'STORYBOOK_PROPS';
/*
This normalizes a functional component into a render method in ComponentOptions.
@ -36,31 +34,12 @@ function prepare(story: StoryFnVueReturnType, innerStory?: ConcreteComponent): C
// Normalize so we can always spread an object
...normalizeFunctionalComponent(story),
components: { story: innerStory },
props: innerStory.props,
inject: {
props: {
from: PROPS,
default: null,
},
},
provide() {
return {
[PROPS]: this.props || this.$props,
};
},
};
}
return {
props: story.props,
inject: {
props: {
from: PROPS,
default: null,
},
},
render() {
return h(story, this.props || this.$props);
return h(story);
},
};
}

View File

@ -1,10 +1,8 @@
import type { Args } from '@storybook/addons';
import dedent from 'ts-dedent';
import { createApp, h, shallowRef, ComponentPublicInstance } from 'vue';
import { RenderContext, StoryFnVueReturnType } from './types';
const activeStoryComponent = shallowRef<StoryFnVueReturnType | null>(null);
const activeProps = shallowRef<Args>({});
let root: ComponentPublicInstance | null = null;
@ -18,7 +16,7 @@ export const storybookApp = createApp({
return () => {
if (!activeStoryComponent.value)
throw new Error('No Vue 3 Story available. Was it set correctly?');
return h(activeStoryComponent.value, activeProps.value);
return h(activeStoryComponent.value);
};
},
});
@ -51,7 +49,6 @@ export default function render({
showMain();
activeStoryComponent.value = element;
activeProps.value = args;
if (!root) {
root = storybookApp.mount('#root');

View File

@ -10,10 +10,15 @@ export default {
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { MyButton },
template: '<my-button @click="onClick" v-bind="$props" />',
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
return { args };
},
// And then the `args` are bound to your component with `v-bind="args"`
template: '<my-button v-bind="args" />',
});
export const Primary = Template.bind({});

View File

@ -20,7 +20,6 @@ export default {
},
size: {
type: String,
default: 'medium',
validator: function (value) {
return ['small', 'medium', 'large'].indexOf(value) !== -1;
},
@ -40,7 +39,7 @@ export default {
'storybook-button': true,
'storybook-button--primary': props.primary,
'storybook-button--secondary': !props.primary,
[`storybook-button--${props.size}`]: true,
[`storybook-button--${props.size || 'medium'}`]: true,
})),
style: computed(() => ({
backgroundColor: props.backgroundColor,

View File

@ -24,10 +24,11 @@ export default {
/*
You can return a Vue 3 functional component from a Story.
Make sure to specify the `props` the component expects to receive!
Make sure to pass the `args` the component expects to receive as the props!
*/
const Template: Story = (args, { argTypes }) => {
const component: FunctionalComponent<Props> = (props) => h(DynamicHeading, props, 'Hello World!');
const component: FunctionalComponent<Props> = () =>
h(DynamicHeading, args as Props, 'Hello World!');
component.props = Object.keys(argTypes);
return component;
};
@ -38,12 +39,12 @@ One.args = {
};
One.decorators = [
// Vue 3 "ComponentOptions" component as decorator
() => ({
// Story Args can be destructured from the 2nd argument (`context`) to a decorator
(storyFn, { args }) => ({
// The `story` component is always injected into a decorator
template: '<div :style="{ color: activeColor }"><story /></div>',
data() {
// Story Args can be accessed on `this.props`
switch (this.props.level) {
switch (args.level) {
case 1:
return { activeColor: 'purple' };
case 2:

View File

@ -6,10 +6,15 @@ export default {
argTypes: {},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { GlobalUsage },
template: '<global-usage v-bind="$props" />',
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
return { args };
},
// And then the `args` are bound to your component with `v-bind="args"`
template: '<global-usage v-bind="args" />',
});
export const Primary = Template.bind({});

View File

@ -5,9 +5,15 @@ export default {
component: MyHeader,
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { MyHeader },
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
// Story args can be spread into the returned object
return { ...args };
},
// Then, the spread values can be accessed directly in the template
template: '<my-header :user="user" />',
});

View File

@ -26,16 +26,19 @@ export default {
},
};
const Template = (args, { argTypes }) => {
const Template = (args) => {
// Individual properties can be overridden by spreading the args
// and the replacing the key-values that need to be updated
args = { ...args, icon: icons[args.icon] }; // eslint-disable-line no-param-reassign
return {
props: Object.keys(argTypes),
// Components used in your story `template` are defined in the `components` object
components: { OverrideArgs },
template: '<override-args v-bind="$props" :icon="icon" />',
setup(props) {
return {
icon: icons[props.icon],
};
// Updated `args` need to be mapped into the template through the `setup()` method
setup() {
return { args };
},
// And then the `args` are bound to your component with `v-bind="args"`
template: '<override-args v-bind="args" />',
};
};

View File

@ -6,11 +6,15 @@ export default {
component: MyPage,
};
// If your props are optional and don't always exist in argTypes,
// you can specify them explicitly as you normally specify props
const Template = () => ({
props: ['user'],
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { MyPage },
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
// Story args can be mapped to keys in the returned object
return { user: args.user };
},
// Then, those values can be accessed directly in the template
template: '<my-page :user="user" />',
});

View File

@ -23,10 +23,15 @@ export default {
decorators: [withTheme],
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { MyButton },
template: '<my-button @click="onClick" v-bind="$props" />',
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
return { args };
},
// And then the `args` are bound to your component with `v-bind="args"`
template: '<my-button v-bind="args" />',
});
export const ButtonWithTheme = Template.bind({});

View File

@ -10,10 +10,15 @@ export default {
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { MyButton },
template: '<my-button @click="onClick" v-bind="$props" />',
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
return { args };
},
// And then the `args` are bound to your component with `v-bind="args"`
template: '<my-button v-bind="args" />',
});
export const Primary = Template.bind({});

View File

@ -20,7 +20,6 @@ export default {
},
size: {
type: String,
default: 'medium',
validator: function (value) {
return ['small', 'medium', 'large'].indexOf(value) !== -1;
},
@ -39,7 +38,7 @@ export default {
'storybook-button': true,
'storybook-button--primary': props.primary,
'storybook-button--secondary': !props.primary,
[`storybook-button--${props.size}`]: true,
[`storybook-button--${props.size || 'medium'}`]: true,
})),
style: computed(() => ({
backgroundColor: props.backgroundColor,

View File

@ -5,9 +5,15 @@ export default {
component: MyHeader,
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { MyHeader },
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
// Story args can be spread into the returned object
return { ...args };
},
// Then, the spread values can be accessed directly in the template
template: '<my-header :user="user" />',
});
@ -17,4 +23,6 @@ LoggedIn.args = {
};
export const LoggedOut = Template.bind({});
LoggedOut.args = {};
LoggedOut.args = {
user: null,
};

View File

@ -6,9 +6,15 @@ export default {
component: MyPage,
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
const Template = (args) => ({
// Components used in your story `template` are defined in the `components` object
components: { MyPage },
// The story's `args` need to be mapped into the template through the `setup()` method
setup() {
// Story args can be mapped to keys in the returned object
return { user: args.user };
},
// Then, those values can be accessed directly in the template
template: '<my-page :user="user" />',
});
@ -18,6 +24,4 @@ LoggedIn.args = {
};
export const LoggedOut = Template.bind({});
LoggedOut.args = {
...HeaderStories.LoggedOut.args,
};
LoggedOut.args = {};