工具列 & 全域變數
觀看影片教學
Storybook 預設搭載工具列附加元件,以控制 story 渲染時的視窗和背景。您也可以建立工具列項目來控制特殊的「全域變數」。接著您可以讀取全域變數值,以建立裝飾器來控制 story 渲染。
全域變數
Storybook 中的全域變數代表 story 渲染的「全域」(意即非 story 特定)輸入。由於它們並非 story 特定,因此不會在 args
參數中傳遞至 story 函式(儘管它們可以透過 context.globals
存取)。相反地,它們通常用於裝飾器中,裝飾器會套用至所有 stories。
當全域變數變更時,story 會重新渲染,且裝飾器會以新值重新執行。變更全域變數最簡單的方式是為它們建立工具列項目。
全域類型和工具列註解
Storybook 具有簡單、宣告式的語法來設定工具列選單。在您的 .storybook/preview.js|ts
中,您可以透過建立具有 toolbar
註解的 globalTypes
來新增您自己的工具列
// 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
。
當您啟動 Storybook 時,您的工具列應該會有一個新的下拉式選單,其中包含 light
和 dark
選項。
建立裝飾器
我們已經實作了一個 global
。讓我們把它連接起來!我們可以使用 context.globals.theme
值,在裝飾器中使用我們新的 theme
全域變數。
例如,假設您正在使用 styled-components
。您可以將主題提供器裝飾器新增至您的 .storybook/preview.js|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];
// Your theme provider and other context providers goes in the return statement
return;
},
],
};
export default preview;
在 story 上設定全域變數
當全域變數值透過 Storybook 中的工具列選單變更時,當您在 stories 之間導覽時,該值會繼續使用。但有時 story 需要特定值才能正確渲染,例如,當針對特定環境進行測試時。
為了確保 story 始終使用特定全域變數值,無論工具列中選擇了什麼,您都可以在 story 或元件上設定 globals
註解。這會覆寫這些 stories 的全域變數值,並在檢視 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 將強制所有 Button stories 使用灰色背景顏色,但 OnDark
story 除外,它將使用深色背景。對於所有 Button stories,backgrounds
全域變數的工具列選單將被停用,並顯示工具提示說明全域變數是在 story 層級設定的。
設定 story 的 globals
註解以覆寫專案層級的全域設定很有用,但應適度使用。未在 story 層級定義的全域變數可以在 Storybook 的 UI 中互動式選取,讓使用者可以探索每個現有的值組合(例如,全域變數值、args
)。在 story 層級設定它們將停用該控制項,阻止使用者探索可用的選項。
進階用法
到目前為止,我們已經在 Storybook 內部建立並使用了全域變數。
現在,讓我們看看更複雜的範例。假設我們想要實作一個名為 locale 的新全域變數,用於國際化,它會在工具列的右側顯示一個旗幟。
在您的 .storybook/preview.js|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
套件載入圖示。請參閱這裡,以取得您可以使用的可用圖示列表。
若要使用工具列,您必須安裝 @storybook/addon-toolbars
附加元件,該元件預設包含在 @storybook/addon-essentials
中。
新增設定元素 right
將在工具列選單的右側顯示文字,一旦您將其連接到裝飾器。
以下是可用的設定選項列表。
MenuItem | 類型 | 描述 | 是否必填 |
---|---|---|---|
value | 字串 | 在全域變數中設定的選單字串值 | 是 |
title | 字串 | 標題的主要文字 | 是 |
right | 字串 | 顯示在選單右側的字串 | 否 |
icon | 字串 | 如果選取此項目,則在工具列中顯示的圖示 | 否 |
從 story 內部取用全域變數
我們建議從裝飾器內部取用全域變數,並為所有 stories 定義全域設定。
但我們知道有時在每個 story 的基礎上使用工具列選項會更有益。
使用上述範例,您可以修改任何 story 以從 story 內容中檢索 Locale global
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>;
},
};
從附加元件內部取用全域變數
如果您正在開發 Storybook 附加元件並需要檢索全域變數,您可以這樣做。@storybook/manager-api
套件為此情境提供了一個 Hook。您可以使用 useGlobals()
Hook 來檢索您想要的任何全域變數。
使用上面的 ThemeProvider 範例,您可以擴展它以在面板內顯示哪個主題處於活動狀態,如下所示
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>
);
};
從附加元件內部更新全域變數
如果您正在開發需要更新全域變數並重新整理 UI 的 Storybook 附加元件,您可以這樣做。如先前所述,@storybook/manager-api
套件為此情境提供了必要的 Hook。您可以使用 updateGlobals
函式來更新您需要的任何全域變數值。
例如,如果您正在開發 工具列附加元件,並且您希望在使用者點擊按鈕後重新整理 UI 並更新全域變數
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>
);
};