在 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'
  }
};

明/暗類別

此外掛程式會將 dark 和 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']
  }
};

預覽類別目標

此外掛程式會將 dark/light 類別套用至預覽 iframe 的 <body> 元素。可以使用 classTarget 參數設定。該值將傳遞至 iframe 內的 querySelector()

如果 <body> 是根據父類別設定樣式的,則此方法很有用,在這種情況下,它可以設定為 html

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

故事整合

預覽類別名稱

如果您開啟 stylePreview 選項,此外掛程式會將 darkClasslightClass 類別套用至預覽 iframe。

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

React

如果您的元件使用自訂主題供應器,您可以使用提供的 hook 來整合它。

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 不會顯示其工具列,如果您想要在文件模式下控制該選項,也可以透過擴充套件通道觸發 UPDATE_DARK_MODE 事件,方法是編輯您的 .storybook/preview.js

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 規範。歡迎任何形式的貢獻!