Storybook 擴充套件預覽可以在 Storybook 中顯示使用者選擇的各種框架程式碼的旋鈕 (knobs)

在 Github 上檢視

Storybook 擴充套件預覽

npm version

Storybook 擴充套件預覽可以在Storybook中顯示使用者選擇的各種框架程式碼的控制項 (controls)(args)旋鈕 (knobs)

開始使用

  • 需要 Storybook 6 或更新版本。
  • 如果您使用 Storybook 5,請使用 1.x 版本。
npm i storybook-addon-preview --dev

.storybook/main.js

module.exports = {
    addons: [
        "storybook-addon-preview/register"
    ],
};

現在,使用預覽撰寫您的 stories。

如何搭配控制項 (controls)(args) 使用

import { previewTemplate, DEFAULT_VANILLA_CODESANDBOX } from "storybook-addon-preview";
// CSF https://storybook.dev.org.tw/docs/react/api/csf

export default {
    title: "Example",
}
export const example = e => {
    e.opt1;
    e.num1;
    return ....;
}
example.parameters = {
    preview: [
        {
            tab: "Vanilla",
            template: previewTemplate`
const inst = new Instance({
    opt1: ${"opt1"},
    num1: ${"num1"},
});
            `,
            language: "ts",
            copy: true,
            codesandbox: DEFAULT_VANILLA_CODESANDBOX(["@egjs/infinitegrid"]),
        },
    ],
};
example.args = {
    opt1: false,
    num1: 0,
};
example.argTypes = {
    opt1: {
        control: { type: "boolean" },
        defaultValue: false,
    },
    num1: {
        control: { type: "number" },
        defaultValue: 0,
    },
};

如何搭配旋鈕 (knobs) 使用 (已棄用)

import { withPreview, previewTemplate, DEFAULT_VANILLA_CODESANDBOX } from "storybook-addon-preview";
import { withKnobs, boolean, number } from "@storybook/addon-knobs";

const stories = storiesOf("Example", module);

stories.addDecorator(withKnobs).addDecorator(withPreview);

stories.add("example", e => {
    const opt1Value = boolean("opt1", false);
    const num1Value = number("num1", 0);

    return ....;
}, {
    preview: [
        {
            tab: "Vanilla",
            template: previewTemplate`
const inst = new Instance({
    opt1: ${"opt1"},
    num1: ${"num1"},
});
            `,
            language: "ts",
            copy: true,
            codesandbox: DEFAULT_VANILLA_CODESANDBOX(["@egjs/infinitegrid"]),
        },
    ]
});
// CSF https://storybook.dev.org.tw/docs/react/api/csf

export default {
    title: "Example",
    decorators: [withKnobs, withPreview],
    parameters: {
        preview: [
            {
                tab: "Vanilla",
                template: previewTemplate`
    const inst = new Instance({
        opt1: ${"opt1"},
        num1: ${"num1"},
    });
                `,
                language: "ts",
                copy: true,
                codesandbox: DEFAULT_VANILLA_CODESANDBOX(["@egjs/infinitegrid"]),
            },
        ],
    },
}
export const example = e => {
    const opt1Value = boolean("opt1", false);
    const num1Value = number("num1", 0);

    return ....;
}

InfiniteGrid 的 Storybook 範例

屬性

export interface PreviewParameter {
    /**
     * The name of the tab to appear in the preview panel
     */
    tab?: string;
    /**
     * Code to appear in the corresponding tab of the preview panel.
     */
    template?: string
    | ((props: Record<string, any>, globals: Record<string, any>) => any)
    | ReturnType<typeof previewTemplate>;
    /**
     * Description of the corresponding template code
     */
    description?: string;
    /**
     * Custom args or knobs that can be used in the preview template.
     */
    knobs?: Record<string, any>;
    /**
     * Custom args or knobs that can be used in the preview template.
     */
    args?: Record<string, any>;
    /**
     * Whether to display the copy button
     */
    copy?: boolean;
    /**
     * Language to highlight its code ("html", "css", "jsx", "tsx", "ts", "js")
     */
    language?: string;
    /**
     * Language presets to link to codesandbox
     * @see {@link https://github.com/naver/storybook-addon-preview/blob/master/README.md}
     */
    codesandbox?: CodeSandboxValue
    | ((previewMap: Record<string, string[]>) => CodeSandboxValue);
    /**
     * Whether to share line numbers when tab names are the same
     */
    continue?: boolean;
    /**
     * Formatting type for that code if you want formatting
     * Only "html" is supported as built-in support.
     * If you want to use custom formatter, use `previewFormatter` config in manager.js
     * @see {@link https://github.com/naver/storybook-addon-preview/blob/master/README.md}
     */
    format?: string | boolean;
}

格式化

Storybook 基本上使用獨立版本的 prettierparser-html。因此 PreviewParameter 格式僅支援 "html"。

如果您使用自訂格式器,請小心,因為檔案大小可能會增加。

請參閱:https://prettier.dev.org.tw/docs/en/browser.html

// .storybook/manager.js
import { addons } from "@storybook/addons";
import * as prettier from "prettier/standalone";
import * as htmlParser from "prettier/parser-html";
import * as babelParser from "prettier/parser-babel";
import * as postCSSParser from "prettier/parser-postcss";

addons.setConfig({
    previewFormatter: (format, code) => {
        if (format === "tsx") {
            return prettier.format(code, {
                parser: "babel-ts",
                plugins: [
                    htmlParser,
                    babelParser,
                    postCSSParser,
                ],
            });
        } else if (format === "vue") {
            return prettier.format(code, {
                parser: "vue",
                plugins: [
                    htmlParser,
                    babelParser,
                    postCSSParser,
                ],
            });
        }
        return code;
    },
});
// stories.js

export const Story = {
    parameters: {
        preview: [
            {
                tab: "Vanilla",
                template: `
const inst = new Instance({
    opt1: "opt1",
    num1: "num1",
});
                `,
                language: "ts",
                format: "tsx",
            },
        ],
    }
}

範本

  • 如果範本是不使用旋鈕的程式碼,您可以直接將其寫為 string 類型。
{
    template: `
const inst = new Instance({
    opt1: 1,
    num1: 1,
});
`,
}
  • 如果您只想按原樣表達旋鈕,請使用 previewTemplate 函式
import { previewTemplate } from "storybook-addon-preview";

{
    args: {
        args1: true,
    },
    template: previewTemplate`
const inst = new Instance({
    opt1: ${"opt1"},
    num1: ${"num1"},
    args1: ${"args1"},
});
`,
}
  • 如果您想使用變數,請使用函式
{
    args: {
        args1: true,
    },
    template: (props, globals) => `
const inst = new Instance({
    opt1: ${props.opt1},
    num1: ${props.num1},
    args1: ${props.args1},
});
`,
}

醒目提示

  • 如果您想醒目提示您的程式碼,請加入 [highlight] 註解。
[
    {
        template: previewTemplate`
    const inst = new Instance({
        /* [highlight] highlight opt1 */
        opt1: ${"opt1"},
        num1: ${"num1"},
    });
        `,
        language: "js",
    },
    {
        template: previewTemplate`
<!-- [highlight] highlight html -->
<div style="width: ${"width"}px;"></div>
        `,
        language: "html",
    },
]
  • 如果您想醒目提示您的程式碼區域,請加入 [highlight-start][highlight-end] 註解。
[
    {
        template: previewTemplate`
    const inst = new Instance({
        /* [highlight-start] highlight options */
        opt1: ${"opt1"},
        num1: ${"num1"},
        /* [highlight-end] */
    });
    `,
    },
    {
        template: previewTemplate`
<!-- [highlight-start] highlight html -->
<div style="width: ${"width"}px;"></div>
<!-- [highlight-end] -->
        `,
        language: "html",
    },
]

Props

當您有很多選項時,可以輕鬆使用選項或 props,或使用 props 範本

export interface PropsOptions {
    indent?: number;
    wrap?: string;
    prefix?: string;
}
  • DEFAULT_PROPS_TEMPLATE(names: string[], options: PropsOptions)
import { previewTemplate, DEFAULT_PROPS_TEMPLATE } from "storybook-addon-preview";

{
    template: previewTemplate`
/* [highlight] You can see opt1, num1 options. */
const inst = new Instance({
${DEFAULT_PROPS_TEMPLATE(["opt1", "num1"], { indent: 4 })}
});
`,
}
  • JSX_PROPS_TEMPLATE(names: string[], options: PropsOptions)
import { previewTemplate, JSX_PROPS_TEMPLATE } from "storybook-addon-preview";

{
    template: previewTemplate`
/* [highlight] You can see opt1, num1 options. */
<Instance
${JSX_PROPS_TEMPLATE(["opt1", "num1"], { indent: 4 })}
    />
`,
    language: "jsx",
}
  • ANGULAR_PROPS_TEMPLATE(names: string[], options: PropsOptions)
import { previewTemplate, ANGULAR_PROPS_TEMPLATE } from "storybook-addon-preview";

{
    template: previewTemplate`
/* [highlight] You can see opt1, num1 options. */
<ngx-instance
${ANGULAR_PROPS_TEMPLATE(["opt1", "num1"], { indent: 4 })}
    ></ngx-instance>
`,
    language: "html",
}
  • VUE_PROPS_TEMPLATE(names: string[], options: PropsOptions)
import { previewTemplate, VUE_PROPS_TEMPLATE } from "storybook-addon-preview";

{
    template: previewTemplate`
/* [highlight] You can see opt1, num1 options. */
<vue-instance
${VUE_PROPS_TEMPLATE(["opt1", "num1"], { indent: 4 })}
    ></vue-instance>
`,
    language: "html",
}
  • LIT_PROPS_TEMPLATE(names: string[], options: PropsOptions)
import { previewTemplate, LIT_PROPS_TEMPLATE } from "storybook-addon-preview";

{
    template: previewTemplate`
/* [highlight] You can see opt1, num1 options. */
html${"`"}<lit-instance
${LIT_PROPS_TEMPLATE(["opt1", "num1"], { indent: 4 })}
    ></lit-instance>${"`"};
`,
    language: "js",
}

CodeSandBox

將您使用的程式碼連結到 CodeSandbox。有一個用於連結 CodeSandbox 的相依性和初始設定檔。我們支援的框架有 react、angular、svelte、lit、preact 和 vue。

const CodeSandboxValue = {
    // https://github.com/codesandbox/codesandbox-importers/blob/master/packages/import-utils/src/create-sandbox/templates.ts#L63
    template: "vue-cli",
    files: {
        "src/main.js": `
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({
render: h => h(App)
}).$mount("#app");
    `,
        "src/App.vue": {
            tab: "Vue",
        },
    },
    dependencies: {
        "vue": "^2.6.0",
    },
    userDependencies: ["@egjs/vue-infinitegrid@^3"],
});

您可以使用預設的 codesandbox 預設值。

  • 程式碼中使用的框架模組以外的外部模組
// DEFAULT_(VANILLA)_CODESANDBOX
// DEFAULT_(REACT)_CODESANDBOX
// DEFAULT_(ANGULAR)_CODESANDBOX
type DEFAULT_FRAMEWORK_CODESANDBOX: CodeSandboxTemplate = (userDependencies?: string[], files?: FilesParam) => CodeSandboxValue;
  • 預覽中提供的 codesandbox 預設值為 vanilla、react、angular、vue、preact、lit 和 svelte。
名稱 預設標籤名稱 程式碼
DEFAULT_VANILLAJS_CODESANDBOX(JS) HTML、VANILLA、CSS(選用) 檢視程式碼
DEFAULT_VANILLA_CODESANDBOX(TS) HTML、VANILLA、CSS(選用) 檢視程式碼
DEFAULT_REACT_CODESANDBOX(TS) React、CSS(選用) 檢視程式碼
DEFAULT_REACTJS_CODESANDBOX(TS) ReactJS、CSS(選用) 檢視程式碼
DEFAULT_ANGULAR_CODESANDBOX Angular(html、component、module)、CSS(選用) 檢視程式碼
DEFAULT_VUE_CODESANDBOX Vue 檢視程式碼
DEFAULT_VUE3_CODESANDBOX Vue3 檢視程式碼
DEFAULT_SVELTE_CODESANDBOX Svelte 檢視程式碼
DEFAULT_LIT_CODESANDBOX Lit、CSS(選用) 檢視程式碼

以下說明如何使用預設的 codesandbox 預設值。

import {
    DEFAULT_VANILLA_CODESANDBOX,
    DEFAULT_REACT_CODESANDBOX,
    DEFAULT_ANGULAR_CODESANDBOX,
} from "storybook-addon-preview";

{
    preview: [
        {
            // { tab: "HTML" }
            tab: "HTML",
            template: ...,
        },
        {
            // { tab: "CSS" }
            tab: "CSS",
            template: ...,
        },
        {
            // { tab: "Vanilla" }
            tab: "Vanilla",
            template: ...,
            codesandbox: DEFAULT_VANILLA_CODESANDBOX(["@egjs/infinitegrid"]),
        },
        {
            // { tab: "React" }
            tab: "React",
            template: ...,
            codesandbox: DEFAULT_REACT_CODESANDBOX(["@egjs/react-infinitegrid"]),
        },
        {
            // { tab: "Angular", index: 0 }
            tab: "Angular",
            description: "app.component.html",
            template: ...,
            language: "markup",
            codesandbox: DEFAULT_ANGULAR_CODESANDBOX(["@egjs/ngx-infinitegrid"]),
        },
        {
            // { tab: "Angular", index: 1 }
            tab: "Angular",
            description: "app.component.ts",
            template: ...,
            language: "tsx",
            codesandbox: DEFAULT_ANGULAR_CODESANDBOX(["@egjs/ngx-infinitegrid"]),
        },
        {
            // { tab: "Angular", index: 2 }
            tab: "Angular",
            description: "app.module.ts",
            template: ...,
            language: "typescript",
            codesandbox: DEFAULT_ANGULAR_CODESANDBOX(["@egjs/ngx-infinitegrid"]),
        },
    ],
}

變更標籤名稱

檢查檔案名稱的 codesandbox 預設程式碼。

import {
    DEFAULT_VANILLA_CODESANDBOX,
    DEFAULT_REACT_CODESANDBOX,
    DEFAULT_ANGULAR_CODESANDBOX,
} from "storybook-addon-preview";

{
    preview: [
        {
            tab: "Custom HTML",
            template: ...,
        },
        {
            tab: "Custom CSS",
            template: ...,
        },
        {
            tab: "Vanilla",
            template: ...,
            codesandbox: DEFAULT_VANILLA_CODESANDBOX(["@egjs/infinitegrid"], {
                "index.html": {
                    tab: "Custom HTML",
                    template: "html",
                    values: {
                        cssFiles: ["src/styles.css"],
                        jsFiles: ["src/index.ts"],
                    },
                },
                "src/styles.css" : { tab: "Custom CSS" },
            }),
        },
        {
            tab: "Angular Component HTML",
            description: "app.component.html",
            template: ...,
            language: "markup",
            codesandbox: DEFAULT_ANGULAR_CODESANDBOX(["@egjs/ngx-infinitegrid"], {
                "src/app/app.component.html": { tab: "Angular Component HTML" },
                "src/app/app.component.ts": { tab: "Angular Component" },
                "src/app/app.module.ts": { tab: "Angular Module" },
            }),
        },
        {
            tab: "Angular Component",
            description: "app.component.ts",
            template: ...,
            language: "tsx",
            codesandbox: DEFAULT_ANGULAR_CODESANDBOX(["@egjs/ngx-infinitegrid"], {
                "src/app/app.component.html": { tab: "Angular Component HTML" },
                "src/app/app.component.ts": { tab: "Angular Component" },
                "src/app/app.module.ts": { tab: "Angular Module" },
            }),
        },
        {
            tab: "Angular Module",
            description: "app.module.ts",
            template: ...,
            language: "typescript",
            codesandbox: DEFAULT_ANGULAR_CODESANDBOX(["@egjs/ngx-infinitegrid"], {
                "src/app/app.component.html": { tab: "Angular Component HTML" },
                "src/app/app.component.ts": { tab: "Angular Component" },
                "src/app/app.module.ts": { tab: "Angular Module" },
            }),
        },
    ],
};

建立自訂 CodeSandbox

  • template 基於 邏輯。
  • dependenciesdevDependenciesscripts 基於 package.jsondependenciesdevDependenciesscripts
  • userDependencies 是陣列類型的相依性。 ([vue@^2.6.0])
  • files 具有字串、CodeFileTab(物件) 和 null 類型。
    • CodeFileTab:將預覽標籤以字串值傳回。
    • null:刪除現有檔案。

CodeSandbox 支援各種範本。若要使用範本,您需要自行定義基本檔案。請參閱 CodeSandbox 中的範本。

export const DEFAULT_VUE_CODESANDBOX: CodeSandboxTemplate = (userDependencies = [], files = {}) => {
    return {
        template: "vue-cli",
        files: {
            "src/main.js": `
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({
    render: h => h(App)
}).$mount("#app");
        `,
            "src/App.vue": {
                tab: "Vue",
            },
            ...files,
        },
        dependencies: {
            "vue": "^2.6.0",
        },
        userDependencies,
    };
};

貢獻

請參閱 CONTRIBUTING.md

授權

storybook-addon-preview 是在 MIT 授權下發行的。

Copyright (c) 2020-present NAVER Corp.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.