加入直播:美东时间周四上午 11 点,Storybook 9 发布及问答

主题切换器

一个 Storybook 插件,用于在预览中切换不同的主题

在 Github 上查看

Storybook Addon Themes

深受 @storybook/addon-backgrounds 启发。

这个 Storybook 主题装饰器可用于在 Storybook 预览中添加自定义 HTML 类或多个类。

Demo

兼容性

此版本与 storybook 版本 6.0.x 兼容。

安装

npm i -D storybook-addon-themes

开始使用

然后将其添加到 storybook 配置目录中的 main.js 文件中,以激活该插件

module.exports = {
  addons: [
    // Maybe other addons here...
    'storybook-addon-themes'
    // Or here...
  ],
};

更多信息请参阅storybook 文档

参数

themes 参数接受一个 Theme 对象数组。

每个 Theme 是一个具有以下属性的对象

  • name (string): 主题名称
  • class (string | string[] - 可选): 与主题相关的 HTML 类或类数组
  • color (string): 主题选择器中徽章的颜色
  • default [已弃用] (boolean - 可选): 是否默认选择该主题?

themes 参数也接受一个具有以下属性的对象

  • default (string - 可选): 默认选择的主题名称
  • list (Theme[] - 必填): 主题列表
  • clearable (boolean - 可选 - 默认为 true): 用户是否可以清除选定的主题?
  • disable (boolean - 可选): 为某个故事禁用插件
  • Decorator (Component - 可选): 用作装饰器组件的组件 (更多信息请参见下方)
  • onChange ((themeName: Theme) => void - 可选): 主题更改时执行的回调函数
  • target (string - 可选): 使用 document.querySelector() 选择要应用类的目标元素。默认为 body,如果要应用于 documentElement 则为 root

配置

全局配置

您可以在 storybook 的 preview.js 文件中全局配置主题

export const parameters = {
  themes: {
    default: 'twitter',
    list: [
      { name: 'twitter', class: 'theme-twt', color: '#00aced' },
      { name: 'facebook', class: 'theme-fb', color: '#3b5998' }
    ],
  },
};

为了向后兼容,default (boolean) 也可以直接在 Theme 对象上设置。**此方式已弃用**,因为它需要重新定义所有 Theme 对象,导致更改默认主题变得困难。

// deprecated
export const parameters = {
  themes: [
      { name: 'twitter', class: 'theme-twt', color: '#00aced', default: true },
      { name: 'facebook', class: 'theme-fb', color: '#3b5998' }
  ],
};

更多信息请参阅storybook 文档

在故事中 (组件故事格式)

或者像这样在您的故事文件中配置主题

export default {
  title: 'CSF|Button',
  component: Button,
  parameters: {
    themes: {
      default: 'twitter',
      list: [
        { name: 'twitter', class: ['theme-twt', 'light-mode'], color: '#00aced' },
        { name: 'facebook', class: ['theme-fb', 'dark-mode'], color: '#3b5998' },
      ],
    },
  },
};

如果您只想为特定故事激活插件或覆盖主题,您可以这样写

export default {
  title: 'CSF|Button',
  component: Button,
};

export const withText = () => <Button onClick={action('clicked')}>Hello Button</Button>;
withText.story = {
  parameters: {
    themes: {
      default: 'twitter',
      list: [
        { name: 'twitter', class: ['theme-twt', 'light-mode'], color: '#00aced' },
        { name: 'facebook', class: ['theme-fb', 'dark-mode'], color: '#3b5998' },
      ],
    },
  },
};

在故事中 (StoriesOf API)

或者使用旧的 StoriesOf API

import { storiesOf } from '@storybook/react'; // <- or your storybook framework

storiesOf('StoriesOf|Button', module)
  .addParameters({
    themes: {
      default: 'twitter',
      list: [
        { name: 'twitter', class: ['theme-twt', 'light-mode'], color: '#00aced' },
        { name: 'facebook', class: ['theme-fb', 'dark-mode'], color: '#3b5998' },
      ],
    },
  })
  .add('with text', () => <button>Click me</button>);

对于单个故事

import { storiesOf } from '@storybook/react';

storiesOf('StoriesOf|Button', module)
  .add('with text', () => <button>Click me</button>, {
    themes: {
      list: [
        { name: 'red', class: 'theme-red', color: 'rgba(255, 0, 0)' },
      ],
    },
  });

覆盖单个属性

您也可以只覆盖 themes 参数中的单个键,例如为单个故事设置不同的默认值

export default {
  title: 'CSF|Button',
  component: Button,
};

export const withText = () => <Button onClick={action('clicked')}>Hello Button</Button>;
withText.story = {
  parameters: {
    themes: {
      default: 'facebook',
    },
  },
};

结合装饰器使用

默认情况下,类将被添加到 body 元素或通过 target 配置的元素。

但在这种情况下,您的主题将不会被其他插件(例如 @storybook/addon-storyshots)看到。

为了解决这个问题,您可以在您的故事中添加 withThemes 装饰器。

但是装饰器方法并非适用于所有框架

支持的框架列表请参见此处

全局配置

preview.js 文件中全局设置装饰器

import { addDecorator } from '@storybook/react'; // <- or your storybook framework
import { withThemes } from 'storybook-addon-themes/react'; // <- or your storybook framework

addDecorator(withThemes);

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  themes: {
    default: 'twitter',
    list: [
      { name: 'twitter', class: ['theme-twt', 'light-mode'], color: '#00aced' },
      { name: 'facebook', class: ['theme-fb', 'dark-mode'], color: '#3b5998' },
    ],
  },
};

在故事中 (组件故事格式)

或者在您的故事文件中(适用于该文件中的所有故事)

export default {
  title: 'CSF|Button',
  component: Button,
  decorators: [ withThemes ],
  parameters: {
    themes: {
      default: 'twitter',
      list: [
        { name: 'twitter', class: ['theme-twt', 'light-mode'], color: '#00aced' },
        { name: 'facebook', class: ['theme-fb', 'dark-mode'], color: '#3b5998' },
      ],
    },
  },
};

或者仅用于特定故事

export const withText = () => <Button onClick={action('clicked')}>Hello Button</Button>;
withText.story = {
  decorators: [ withThemes ],
  parameters: {
    themes: {
      default: 'twitter',
      list: [
        { name: 'twitter', class: ['theme-twt', 'light-mode'], color: '#00aced' },
        { name: 'facebook', class: ['theme-fb', 'dark-mode'], color: '#3b5998' },
      ],
    },
  },
};

在故事中 (StoriesOf API)

或者使用旧的 StoriesOf API

import { storiesOf } from '@storybook/react'; // <- or your storybook framework
import { withThemes } from 'storybook-addon-themes/react';

storiesOf('StoriesOf|Button', module)
  .addDecorator(withThemes)
  .add('with text', () => <button>Click me</button>);

自定义装饰器

通用

您可以在 theme 参数中使用 Decorator 选项提供一个用作装饰器的组件。

装饰器将获得以下属性

  • theme: 选定的主题,如果未选择任何主题则为 undefined
  • themes: 作为 theme 参数的 list 选项提供的所有主题列表。
  • themeClasses: 选定主题的格式化主题类(如果选定主题存在 class 选项)。
  • themeName: 选定主题的名称(如果未选择任何主题则等于 none)。

不要忘记使用 children prop (React/HTML) 或 <slot></slot> 元素 (Vue/Svelte) 渲染故事。

HTML 示例

为了管理与 HTML storybook 的响应性,您的装饰器必须返回一个包含两个元素的数组

  • 要在故事中显示的 HTML 元素
  • 一个更新回调函数,当主题更改时将被调用。与装饰器一样,回调函数将接收相同的 props(不包含 children)。

使用 CSS 文件更改主题的自定义装饰器示例

function getOrCreate(id) {
  const elementOnDom = document.getElementById(id);
  if (elementOnDom) {
    return elementOnDom;
  }

  const element = document.createElement('link');
  element.setAttribute('id', id);
  element.setAttribute('rel', 'stylesheet');
  return element;
}

function Decorator(props) {
  const { children } = props;

  function setStyles({ theme, themeName }) {
    const link = getOrCreate('theme-stylesheet');
    if (!theme) {
      link.parentNode && link.parentNode.removeChild(link);
    } else {
      link.href = themeName === 'facebook' ? 'Button-fb.css' : 'Button-twt.css';
      children.appendChild(link);
    }
  }
  setStyles(props);

  return [children, setStyles];
}

React 示例

与上面相同的 React 示例

function Decorator(props) {
  const { children, themeName } = props;
  return (
    <>
      {children}
      {themeName === 'twitter' && <link rel="stylesheet" href="twitter.css"/>}
      {themeName === 'facebook' && <link rel="stylesheet" href="facebook.css"/>}
    </>
  );
};

框架支持列表

React React Native Vue Angular Polymer Mithril HTML Marko Svelte Riot Ember Preact
不使用装饰器时的用法 + + + + + + + + + + +
结合装饰器使用 + + + +
作者
  • tonai
    tonai
合作方
    Angular
    Ember
    HTML
    Marko
    Mithril
    Preact
    Rax
    React
    Riot
    Svelte
    Vue
    Web Components
标签