2021-09-05 01:34:54 +04:30

121 lines
3.3 KiB
TypeScript

import type { ConcreteComponent, Component, ComponentOptions, App } from 'vue';
import { h } from 'vue';
import { start } from '@storybook/core/client';
import {
ClientStoryApi,
StoryFn,
DecoratorFunction,
StoryContext,
Loadable,
} from '@storybook/addons';
import './globals';
import { IStorybookSection, StoryFnVueReturnType } from './types';
import render, { storybookApp } from './render';
/*
This normalizes a functional component into a render method in ComponentOptions.
The concept is taken from Vue 3's `defineComponent` but changed from creating a `setup`
method on the ComponentOptions so end-users don't need to specify a "thunk" as a decorator.
*/
function normalizeFunctionalComponent(options: ConcreteComponent): ComponentOptions {
return typeof options === 'function' ? { render: options, name: options.name } : options;
}
function prepare(rawStory: StoryFnVueReturnType, innerStory?: ConcreteComponent): Component | null {
const story = rawStory as ComponentOptions;
if (story == null) {
return null;
}
if (innerStory) {
return {
// Normalize so we can always spread an object
...normalizeFunctionalComponent(story),
components: { ...(story.components || {}), story: innerStory },
};
}
return {
render() {
return h(story);
},
};
}
const defaultContext: StoryContext = {
id: 'unspecified',
name: 'unspecified',
kind: 'unspecified',
parameters: {},
args: {},
argTypes: {},
globals: {},
};
function decorateStory(
storyFn: StoryFn<StoryFnVueReturnType>,
decorators: DecoratorFunction<ConcreteComponent>[]
): StoryFn<Component> {
return decorators.reduce(
(decorated: StoryFn<ConcreteComponent>, decorator) => (
context: StoryContext = defaultContext
) => {
let story;
const decoratedStory = decorator(
({ parameters, ...innerContext }: StoryContext = {} as StoryContext) => {
story = decorated({ ...context, ...innerContext });
return story;
},
context
);
if (!story) {
story = decorated(context);
}
if (decoratedStory === story) {
return story;
}
return prepare(decoratedStory, story);
},
(context) => prepare(storyFn(context))
);
}
const framework = 'vue3';
interface ClientApi extends ClientStoryApi<StoryFnVueReturnType> {
setAddon(addon: any): void;
configure(loader: Loadable, module: NodeModule): void;
getStorybook(): IStorybookSection[];
clearDecorators(): void;
forceReRender(): void;
raw: () => any; // todo add type
load: (...args: any[]) => void;
app: App;
}
const api = start(render, { decorateStory });
export const storiesOf: ClientApi['storiesOf'] = (kind, m) => {
return (api.clientApi.storiesOf(kind, m) as ReturnType<ClientApi['storiesOf']>).addParameters({
framework,
});
};
export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args);
export const { addDecorator } = api.clientApi;
export const { addParameters } = api.clientApi;
export const { clearDecorators } = api.clientApi;
export const { setAddon } = api.clientApi;
export const { forceReRender } = api;
export const { getStorybook } = api.clientApi;
export const { raw } = api.clientApi;
export const app: ClientApi['app'] = storybookApp;
export { activeStoryComponent } from './render';