@storybook/addon-themes
首先,您需要安裝 @storybook/addon-themes
。
執行以下腳本以安裝並註冊附加元件
npx storybook@latest add @storybook/addon-themes
在底層,這會執行 npx @storybook/auto-config themes
,它應該讀取您的專案並嘗試使用正確的裝飾器來設定您的 Storybook。如果直接執行該命令無法解決您的問題,請在 @storybook/auto-config 儲存庫上提交錯誤報告,以便我們進一步改進它。若要手動新增此附加元件,請先安裝它,然後將其新增至 .storybook/main.ts
中的 addons 陣列。
Material UI 依賴兩種字型才能按預期呈現,即 Google 的 Roboto
和 Material Icons
。雖然您可以直接從 Google Fonts CDN 加載這些字型,但將字型與 Storybook 捆綁在一起對於效能來說更好。
首先,安裝字型作為依賴項。
yarn add @fontsource/roboto @fontsource/material-icons
然後將 CSS 檔案匯入到 .storybook/preview.js
,您的 Storybook 的進入點。
// .storybook/preview.js
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import '@fontsource/material-icons';
在 .storybook/preview.js
內,匯入 <CssBaseline />
、<ThemeProvider />
和您的主題,然後使用 withThemeFromJSXProvider
裝飾器將它們應用於您的 stories,方法是將其新增至 decorators
陣列。
// .storybook/preview.js
import { CssBaseline, ThemeProvider } from '@mui/material';
import { withThemeFromJSXProvider } from '@storybook/addon-themes';
import { lightTheme, darkTheme } from '../src/themes.js';
/* snipped for brevity */
export const decorators = [
withThemeFromJSXProvider({
themes: {
light: lightTheme,
dark: darkTheme,
},
defaultTheme: 'light',
Provider: ThemeProvider,
GlobalStyles: CssBaseline,
}),
];
當您提供多個主題時,Storybook UI 中將會出現一個工具列選單,以選擇您想要的 stories 主題。
Storybook controls 為您提供圖形化控件來操作元件的 props。它們對於尋找元件的邊緣案例和在瀏覽器中進行原型設計非常方便。
通常,您必須手動設定 controls。但是,如果您使用 Typescript,則可以重複使用 Material UI 的元件屬性類型來自動產生 story controls。作為額外的好處,這也將自動在您的文件標籤中填入屬性表。
讓我們以以下 Button 元件為例。
// button.component.tsx
import React from 'react';
import { Button as MuiButton } from '@mui/material';
export interface ButtonProps {
label: string;
}
export const Button = ({ label, ...rest }: ButtonProps) => <MuiButton {...rest}>{label}</MuiButton>;
在這裡,我使用 label prop 作為 MuiButton
的 child,並傳遞所有其他 props。但是,當我們將其渲染到 Storybook 中時,我們的 controls 面板僅允許我們變更我們自己宣告的 label prop。
這是因為 Storybook 僅將元件的屬性類型或 Story Args 中明確宣告的 props 新增到 controls 表中。讓我們更新 Storybook 的 Docgen 組態,以將 Material UI 的 Button props 也帶入 controls 表中。
// .storybook/main.ts
module.exports = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-essentials', '@storybook/addon-styling'],
framework: '@storybook/your-framework',
typescript: {
reactDocgen: 'react-docgen-typescript',
reactDocgenTypescriptOptions: {
// Speeds up Storybook build time
compilerOptions: {
allowSyntheticDefaultImports: false,
esModuleInterop: false,
},
// Makes union prop types like variant and size appear as select controls
shouldExtractLiteralValuesFromEnum: true,
// Makes string and boolean types that can be undefined appear as inputs and switches
shouldRemoveUndefinedFromOptional: true,
// Filter out third-party props from node_modules except @mui packages
propFilter: (prop) =>
prop.parent
? !/node_modules\/(?!@mui)/.test(prop.parent.fileName)
: true,
},
},
};
我們也想要更新 .storybook/preview.js
中的 parameters,以顯示 controls 表的描述和預設欄位。
// .storybook/preview.js
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
expanded: true, // Adds the description and default columns
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
最後,更新 ButtonProps
類型以從 Material UI 的 Button props 擴展,以將所有這些 props 新增到 controls 中。
// button.component.tsx
import React from 'react';
import {
Button as MuiButton,
ButtonProps as MuiButtonProps,
} from '@mui/material';
export interface ButtonProps extends MuiButtonProps {
label: string;
}
export const Button = ({ label, ...rest }: ButtonProps) => (
<MuiButton {...rest}>{label}</MuiButton>
);
重新啟動您的 Storybook 伺服器,以便這些組態變更生效。您現在應該看到 Button 也具有所有 MuiButton
的 props 的 controls。
我們的按鈕現在有 27 個 props,這對於您的用例來說可能有點太多了。為了控制哪些 props 是可見的,我們可以使用 TypeScript 的 Pick<type, keys>
和 Omit<type, keys>
工具類型。
// button.component.tsx
import React from 'react';
import {
Button as MuiButton,
ButtonProps as MuiButtonProps,
} from '@mui/material';
// Only include variant, size, and color
type ButtonBaseProps = Pick<MuiButtonProps, 'variant' | 'size' | 'color'>;
// Use all except disableRipple
// type ButtonBaseProps = Omit<MuiButtonProps, "disableRipple">;
export interface ButtonProps extends ButtonBaseProps {
label: string;
}
export const Button = ({ label, ...rest }: ButtonProps) => (
<MuiButton {...rest}>{label}</MuiButton>
);
現在我們的 Button 將僅從 MuiButton
取得 variant、size 和 color props。
📣 感謝 Eric Mudrak 精彩的 Storybook with React & TypeScript 文章,它啟發了這個技巧。