在 Storybook 中切换明暗模式

在 Github 上查看

storybook-dark-mode

一个 Storybook 插件,允许用户在暗模式和亮模式之间切换。

Example

安装

安装以下 npm 模块

npm i --save-dev storybook-dark-mode

或使用 yarn

yarn add -D storybook-dark-mode

然后,将以下内容添加到 .storybook/main.js

module.exports = {
  addons: ['storybook-dark-mode']
};

从早期版本升级

.storybook/main.js 中更改

module.exports = {
-  addons: ['storybook-dark-mode/register']
+  addons: ['storybook-dark-mode']
};

配置

通过将以下内容添加到 .storybook/preview.js 文件中,配置暗模式和亮模式

import { themes } from '@storybook/theming';

export const parameters = {
  darkMode: {
    // Override the default dark theme
    dark: { ...themes.dark, appBg: 'black' },
    // Override the default light theme
    light: { ...themes.normal, appBg: 'red' }
  }
};

默认主题

初始颜色方案的优先级顺序

  1. 如果用户之前设置了颜色主题,则使用该主题
  2. 在你的 Storybook 中为 current 参数配置的值
  3. 操作系统颜色方案偏好

设置初始颜色方案后,后续重新加载将使用此值。 要清除缓存的颜色方案,需要在 Chrome 控制台中执行 localStorage.clear()

export const parameters = {
  darkMode: {
    // Set the initial theme
    current: 'light'
  }
};

暗/亮类

此插件将为管理器应用暗类名和亮类名。 这使你可以轻松地为 Storybook UI 编写支持暗模式的主题覆盖。

可以使用 darkClasslightClass 参数来覆盖在明暗模式之间切换时应用的类名。

export const parameters = {
  darkMode: {
    darkClass: 'lights-out',
    lightClass: 'lights-on'
  }
};

还可以传递数组来应用多个类。

export const parameters = {
  darkMode: {
    darkClass: ['lights-out', 'foo'],
    lightClass: ['lights-on', 'bar']
  }
};

预览类目标

此插件将把暗/亮类应用到预览 iframe 的 <body> 元素。 这可以通过 classTarget 参数进行配置。 该值将传递到 iframe 内部的 querySelector()

如果 <body> 是根据父级的类进行样式设置,则这很有用,在这种情况下可以将其设置为 html

export const parameters = {
  darkMode: {
    classTarget: 'html'
  }
};

故事集成

预览类名

如果打开 stylePreview 选项,此插件将把 darkClasslightClass 类应用到预览 iframe。

export const parameters = {
  darkMode: {
    stylePreview: true
  }
};

React

如果你的组件使用自定义主题提供者,可以通过使用提供的钩子来集成它。

import { useDarkMode } from 'storybook-dark-mode';
import { addDecorator } from '@storybook/react';

// your theme provider
import ThemeContext from './theme';

// create a component that uses the dark mode hook
function ThemeWrapper(props) {
  // render your custom theme provider
  return (
    <ThemeContext.Provider value={useDarkMode() ? darkTheme : defaultTheme}>
      {props.children}
    </ThemeContext.Provider>
  );
}

export const decorators = [renderStory => <ThemeWrapper>{renderStory()}</ThemeWrapper>)];

主题旋钮

如果想要让 UI 的暗模式与组件的暗模式分开,请实现此全局装饰器

import { themes } from '@storybook/theming';

// Add a global decorator that will render a dark background when the
// "Color Scheme" knob is set to dark
const knobDecorator = storyFn => {
  // A knob for color scheme added to every story
  const colorScheme = select('Color Scheme', ['light', 'dark'], 'light');

  // Hook your theme provider with some knobs
  return React.createElement(ThemeProvider, {
    // A knob for theme added to every story
    theme: select('Theme', Object.keys(themes), 'default'),
    colorScheme,
    children: [
      React.createElement('style', {
        dangerouslySetInnerHTML: {
          __html: `html { ${
            colorScheme === 'dark' ? 'background-color: rgb(35,35,35);' : ''
          } }`
        }
      }),
      storyFn()
    ]
  });
};

export const decorators = [knobDecorator];

事件

还可以通过插件通道监听 DARK_MODE 事件。

import { addons } from '@storybook/preview-api';
import { addDecorator } from '@storybook/react';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';

// your theme provider
import ThemeContext from './theme';

// get channel to listen to event emitter
const channel = addons.getChannel();

// create a component that listens for the DARK_MODE event
function ThemeWrapper(props) {
  // this example uses hook but you can also use class component as well
  const [isDark, setDark] = useState(false);

  useEffect(() => {
    // listen to DARK_MODE event
    channel.on(DARK_MODE_EVENT_NAME, setDark);
    return () => channel.off(DARK_MODE_EVENT_NAME, setDark);
  }, [channel, setDark]);

  // render your custom theme provider
  return (
    <ThemeContext.Provider value={isDark ? darkTheme : defaultTheme}>
      {props.children}
    </ThemeContext.Provider>
  );
}

export const decorators = [renderStory => <ThemeWrapper>{renderStory()}</ThemeWrapper>)];

由于在文档模式下,Storybook 不会显示其工具栏,因此如果想要在文档模式下控制该选项,可以通过编辑 .storybook/preview.js 来触发插件通道中的 UPDATE_DARK_MODE 事件。

import React from 'react';
import { addons } from '@storybook/preview-api';
import { DocsContainer } from '@storybook/addon-docs';
import { themes } from '@storybook/theming';

import {
  DARK_MODE_EVENT_NAME,
  UPDATE_DARK_MODE_EVENT_NAME
} from 'storybook-dark-mode';

const channel = addons.getChannel();

export const parameters = {
  darkMode: {
    current: 'light',
    dark: { ...themes.dark },
    light: { ...themes.light }
  },
  docs: {
    container: props => {
      const [isDark, setDark] = React.useState();

      const onChangeHandler = () => {
        channel.emit(UPDATE_DARK_MODE_EVENT_NAME);
      };

      React.useEffect(() => {
        channel.on(DARK_MODE_EVENT_NAME, setDark);
        return () => channel.removeListener(DARK_MODE_EVENT_NAME, setDark);
      }, [channel, setDark]);

      return (
        <div>
          <input type="checkbox" onChange={onChangeHandler} />
          <DocsContainer {...props} />
        </div>
      );
    }
  }
};

贡献者 ✨

感谢这些很棒的人(表情符号键

这个项目遵循 all-contributors 规范。 欢迎任何形式的贡献!

由以下人员制作
  • alisowski
    alisowski
标签