工具栏和全局变量
Watch a video tutorial
Storybook 附带工具栏插件,用于控制故事渲染的 viewport 和 background。你还可以创建控制特殊“全局变量”的工具栏项。然后,你可以读取全局值来创建 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.
全局
¥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:
// 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
中设置 globalTypes
和 initialGlobals
。
¥As globals are global you can only set globalTypes
and initialGlobals
in .storybook/preview.js|ts
.
当你启动 Storybook 时,你的工具栏应该有一个新的下拉菜单,其中包含 light
和 dark
选项。
¥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:
// 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+ 中提供了在故事或组件上设置全局变量的功能。一些插件,如 backgrounds 和 viewport,已更新为在启用 功能标志 时使用 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.
// 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:
// 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;
要使用工具栏,你必须安装 @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:
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:
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:
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>
);
};