Docs
Storybook Docs

工具栏和全局变量

Watch a video tutorial

Storybook 附带工具栏插件,用于控制故事渲染的 viewportbackground。你还可以创建控制特殊“全局变量”的工具栏项。然后,你可以读取全局值来创建 decorators 来控制故事渲染。

¥Storybook ships with toolbar addons to control the viewport and background the story renders in. You can also create toolbar items which control special “globals”. You can then read the global values to create decorators to control story rendering.

Toolbars and globals

全局

¥Globals

Storybook 中的全局变量代表对故事渲染的“全局”(非故事特定)输入。由于它们不是特定于故事的,因此它们不会在 args 参数中传递给故事函数(尽管它们可以作为 context.globals 访问)。相反,它们通常用于装饰器中,适用于所有故事。

¥Globals in Storybook represent “global” (as in not story-specific) inputs to the rendering of the story. As they aren’t specific to the story, they aren’t passed in the args argument to the story function (although they are accessible as context.globals). Instead, they are typically used in decorators, which apply to all stories.

当全局变量发生变化时,故事会重新渲染,装饰器会使用新值重新运行。更改全局变量的最简单方法是为它们创建一个工具栏项。

¥When the globals change, the story re-renders and the decorators rerun with the new values. The easiest way to change globals is to create a toolbar item for them.

全局类型和工具栏注释

¥Global types and the toolbar annotation

Storybook 具有用于配置工具栏菜单的简单声明性语法。在你的 .storybook/preview.js|ts 中,你可以通过创建带有 toolbar 注释的 globalTypes 来添加自己的工具栏:

¥Storybook has a simple, declarative syntax for configuring toolbar menus. In your .storybook/preview.js|ts, you can add your own toolbars by creating globalTypes with a toolbar annotation:

.storybook/preview.ts
// Replace your-framework with the framework you are using (e.g., react, vue3)
import { Preview } from '@storybook/your-framework';
 
const preview: Preview = {
  globalTypes: {
    theme: {
      description: 'Global theme for components',
      toolbar: {
        // The label to show for this toolbar item
        title: 'Theme',
        icon: 'circlehollow',
        // Array of plain string values or MenuItem shape (see below)
        items: ['light', 'dark'],
        // Change title based on selected value
        dynamicTitle: true,
      },
    },
  },
  initialGlobals: {
    theme: 'light',
  },
};
 
export default preview;

由于全局变量是全局的,因此你只能在 .storybook/preview.js|ts 中设置 globalTypesinitialGlobals

¥As globals are global you can only set globalTypes and initialGlobals in .storybook/preview.js|ts.

当你启动 Storybook 时,你的工具栏应该有一个新的下拉菜单,其中包含 lightdark 选项。

¥When you start your Storybook, your toolbar should have a new dropdown menu with the light and dark options.

创建装饰器

¥Create a decorator

我们已经实现了 global。让我们连接起来!我们可以使用 context.globals.theme 值在装饰器中使用我们的新 theme 全局变量。

¥We have a global implemented. Let's wire it up! We can consume our new theme global in a decorator using the context.globals.theme value.

例如,假设你正在使用 styled-components。你可以将主题提供程序装饰器添加到你的 .storybook/preview.js|ts 配置中:

¥For example, suppose you are using styled-components. You can add a theme provider decorator to your .storybook/preview.js|ts config:

.storybook/preview.ts
// Replace your-framework with the framework you are using (e.g., solid, qwik)
import { Preview } from '@storybook/your-framework';
 
import { MyThemes } from '../my-theme-folder/my-theme-file';
 
const preview: Preview = {
  decorators: [
    (story, context) => {
      const selectedTheme = context.globals.theme || 'light';
      const theme = MyThemes[selectedTheme];
      return (
        // Your theme provider and other context providers go here
      )
    },
  ],
};
 
export default preview;

在故事上设置全局变量

¥Setting globals on a story

Storybook 8.3+ 中提供了在故事或组件上设置全局变量的功能。一些插件,如 backgroundsviewport,已更新为在启用 功能标志 时使用 globals API。

¥The ability to set globals on a story or component is available in Storybook 8.3+. Some addons, like backgrounds and viewport, have been updated to use the globals API when a feature flag is enabled.

当使用 Storybook 中的工具栏菜单更改全局值时,该值将继续用于你在故事之间导航。但有时故事需要特定值才能正确渲染,例如在针对特定环境进行测试时。

¥When a global value is changed with a toolbar menu in Storybook, that value continues to be used as you navigate between stories. But sometimes a story requires a specific value to render correctly, e.g., when testing against a particular environment.

要确保故事始终使用特定的全局值,无论在工具栏中选择了什么,你都可以在故事或组件上设置 globals 注释。这将覆盖这些故事的全局值,并在查看故事时禁用该全局的工具栏菜单。

¥To ensure that a story always uses a specific global value, regardless of what has been chosen in the toolbar, you can set the globals annotation on a story or component. This overrides the global value for those stories and disables the toolbar menu for that global when viewing the stories.

Button.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta: Meta<typeof Button> = {
  component: Button,
  globals: {
    // 👇 Set background value for all component stories
    backgrounds: { value: 'gray', grid: false },
  },
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
export const OnDark: Story = {
  globals: {
    // 👇 Override background value for this story
    backgrounds: { value: 'dark' },
  },
};

在上面的例子中,Storybook 会强制所有按钮故事使用灰色背景颜色,但 OnDark 故事除外,它将使用深色背景。对于所有按钮故事,工具栏菜单将针对 backgrounds 全局禁用,并带有一个工具提示,说明全局是在故事级别设置的。

¥In the example above, Storybook will force all Button stories to use a gray background color, except the OnDark story, which will use the dark background. For all Button stories, the toolbar menu will be disabled for the backgrounds global, with a tooltip explaining that the global is set at the story level.

配置故事的 globals 注释以覆盖项目级全局设置很有用,但应适度使用。未在故事级别定义的全局变量可以在 Storybook 的 UI 中以交互方式选择,允许用户探索每个现有的值组合(例如,全局值,args)。在故事级别设置它们将禁用该控件,从而阻止用户探索可用选项。

¥Configuring a story's globals annotation to override the project-level global settings is useful but should be used with moderation. Globals that are not defined at the story level can be selected interactively in Storybook's UI, allowing users to explore every existing combination of values (e.g., global values, args). Setting them at the story level will disable that control, preventing users from exploring the available options.

高级用法

¥Advanced usage

到目前为止,我们已经在 Storybook 内部创建并使用了一个全局变量。

¥So far, we've created and used a global inside Storybook.

现在,让我们看一个更复杂的例子。假设我们想要实现一个名为 locale 的新全局变量以实现国际化,它在工具栏右侧显示一个标志。

¥Now, let's take a look at a more complex example. Suppose we wanted to implement a new global called locale for internationalization, which shows a flag on the right side of the toolbar.

在你的 .storybook/preview.js|ts 中,添加以下内容:

¥In your .storybook/preview.js|ts, add the following:

.storybook/preview.ts
// Replace your-framework with the framework you are using (e.g., react, vue3)
import { Preview } from '@storybook/your-framework';
 
const preview: Preview = {
  globalTypes: {
    locale: {
      description: 'Internationalization locale',
      toolbar: {
        icon: 'globe',
        items: [
          { value: 'en', right: '🇺🇸', title: 'English' },
          { value: 'fr', right: '🇫🇷', title: 'Français' },
          { value: 'es', right: '🇪🇸', title: 'Español' },
          { value: 'zh', right: '🇨🇳', title: '中文' },
          { value: 'kr', right: '🇰🇷', title: '한국어' },
        ],
      },
    },
  },
  initialGlobals: {
    locale: 'en',
  },
};
 
export default preview;

示例中使用的 icon 元素从 @storybook/components 包中加载图标。有关你可以使用的图标列表,请参阅 此处

¥The icon element used in the examples loads the icons from the @storybook/components package. See here for the list of available icons that you can use.

要使用工具栏,你必须安装 @storybook/addon-toolbars 插件,该插件默认包含在 @storybook/addon-essentials 中。

¥To use toolbars, you must install the @storybook/addon-toolbars add-on, which is included by default in @storybook/addon-essentials.

添加配置元素 right 后,一旦将其连接到装饰器,就会在工具栏菜单的右侧显示文本。

¥Adding the configuration element right will display the text on the right side in the toolbar menu once you connect it to a decorator.

以下是可用配置选项的列表。

¥Here's a list of the available configuration options.

MenuItem类型描述必需
value字符串在全局变量中设置的菜单的字符串值
title字符串标题的主要文本
right字符串显示在菜单右侧的字符串
icon字符串如果选择了此项,则工具栏中会显示一个图标

在故事中使用全局变量

¥Consuming globals from within a story

我们建议从装饰器中使用全局变量并为所有故事定义全局设置。

¥We recommend consuming globals from within a decorator and defining a global setting for all stories.

但我们知道有时在每个故事的基础上使用工具栏选项更有益。

¥But we're aware that sometimes it's more beneficial to use toolbar options on a per-story basis.

使用上面的示例,你可以修改任何故事以从故事上下文中检索 Locale global

¥Using the example above, you can modify any story to retrieve the Locale global from the story context:

MyComponent.stories.ts|tsx
import type { Meta, StoryObj } from '@storybook/react';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
const getCaptionForLocale = (locale) => {
  switch (locale) {
    case 'es':
      return 'Hola!';
    case 'fr':
      return 'Bonjour!';
    case 'kr':
      return '안녕하세요!';
    case 'zh':
      return '你好!';
    default:
      return 'Hello!';
  }
};
 
export const StoryWithLocale = {
  render: (args, { globals: { locale } }) => {
    const caption = getCaptionForLocale(locale);
    return <p>{caption}</p>;
  },
};

在插件中使用全局变量

¥Consuming globals from within an addon

如果你正在开发 Storybook 插件并需要检索全局变量,你可以这样做。@storybook/manager-api 包为此场景提供了一个钩子。你可以使用 useGlobals() 钩子来检索你想要的任何全局变量。

¥If you're working on a Storybook addon and need to retrieve globals, you can do so. The @storybook/manager-api package provides a hook for this scenario. You can use the useGlobals() hook to retrieve any globals you want.

使用上面的 ThemeProvider 示例,你可以将其展开以显示面板内哪个主题处于活动状态,如下所示:

¥Using the ThemeProvider example above, you could expand it to display which theme is active inside a panel as such:

your-addon-register-file.js
import React from 'react';
 
import { useGlobals } from '@storybook/manager-api';
 
import { AddonPanel, Placeholder, Separator, Source, Spaced, Title } from '@storybook/components';
 
import { MyThemes } from '../my-theme-folder/my-theme-file';
 
// Function to obtain the intended theme
const getTheme = (themeName) => {
  return MyThemes[themeName];
};
 
const ThemePanel = (props) => {
  const [{ theme: themeName }] = useGlobals();
 
  const selectedTheme = getTheme(themeName);
 
  return (
    <AddonPanel {...props}>
      {selectedTheme ? (
        <Spaced row={3} outer={1}>
          <Title>{selectedTheme.name}</Title>
          <p>The full theme object</p>
          <Source
            code={JSON.stringify(selectedTheme, null, 2)}
            language="js"
            copyable
            padded
            showLineNumbers
          />
        </Spaced>
      ) : (
        <Placeholder>No theme selected</Placeholder>
      )}
    </AddonPanel>
  );
};

从插件内部更新全局变量

¥Updating globals from within an addon

如果你正在开发需要更新全局变量并刷新 UI 的 Storybook 插件,你可以这样做。如前所述,@storybook/manager-api 包为这种情况提供了必要的钩子。你可以使用 updateGlobals 函数更新你需要的任何全局值。

¥If you're working on a Storybook addon that needs to update the global and refresh the UI, you can do so. As mentioned previously, the @storybook/manager-api package provides the necessary hook for this scenario. You can use the updateGlobals function to update any global values you need.

例如,如果你正在开发 工具栏插件,并且想要在用户单击按钮时刷新 UI 并更新全局变量:

¥For example, if you were working on a toolbar addon, and you want to refresh the UI and update the global once the user clicks on a button:

your-addon-register-file.js
import React, { useCallback } from 'react';
 
import { FORCE_RE_RENDER } from '@storybook/core-events';
import { useGlobals } from '@storybook/manager-api';
 
import { IconButton } from '@storybook/components';
import { OutlineIcon } from '@storybook/icons';
 
import { addons } from '@storybook/preview-api';
 
const ExampleToolbar = () => {
  const [globals, updateGlobals] = useGlobals();
 
  const isActive = globals['my-param-key'] || false;
 
  // Function that will update the global value and trigger a UI refresh.
  const refreshAndUpdateGlobal = () => {
    // Updates Storybook global value
    updateGlobals({
      ['my-param-key']: !isActive,
    }),
      // Invokes Storybook's addon API method (with the FORCE_RE_RENDER) event to trigger a UI refresh
      addons.getChannel().emit(FORCE_RE_RENDER);
  };
 
  const toggleOutline = useCallback(() => refreshAndUpdateGlobal(), [isActive]);
 
  return (
    <IconButton
      key="Example"
      active={isActive}
      title="Show a Storybook toolbar"
      onClick={toggleOutline}
    >
      <OutlineIcon />
    </IconButton>
  );
};