Storybook 主题附加组件
极大地受到了 @storybook/addon-backgrounds 的启发。
这个 Storybook 主题装饰器可用于为 Storybook 中的预览添加自定义 HTML 类或多个类。

兼容性
此版本兼容 Storybook 版本 6.0.x。
安装
npm i -D storybook-addon-themes
开始使用
然后通过将其添加到 Storybook 的 main.js 文件中激活该附加组件(位于 Storybook 配置目录)
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 属性(React/HTML)或 <slot></slot> 元素(Vue/Svelte)来渲染故事。
HTML 示例
为了管理与 HTML Storybook 的响应性,您的装饰器必须返回一个包含两个元素的数组
- 要在故事中显示的 HTML 元素
- 一个更新回调函数,当主题变化时将被调用。与装饰器一样,该回调函数将接收相同的属性(不包含 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 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 不使用装饰器 | + | + | + | + | + | + | + | + | + | + | + | |
| 使用装饰器 | + | + | + | + |