@storybook/addon-themes
首先,您需要安装 @storybook/addon-themes
。
运行以下脚本以安装并注册插件
npx storybook@latest add @storybook/addon-themes
在底层,这会运行 npx @storybook/auto-config themes
,它应该读取您的项目并尝试使用正确的装饰器配置您的 Storybook。如果直接运行该命令无法解决您的问题,请在 @storybook/auto-config 仓库中提交一个 bug,以便我们进一步改进它。要手动添加此插件,请安装它,然后将其添加到您的 .storybook/main.ts
文件中的 addons 数组。
Material UI 依赖于两种字体才能按预期呈现,分别是 Google 的 Roboto
和 Material Icons
。虽然您可以直接从 Google Fonts CDN 加载这些字体,但将字体与 Storybook 打包在一起对于性能更好。
首先,将字体作为依赖项安装。
yarn add @fontsource/roboto @fontsource/material-icons
然后将 CSS 文件导入到您的 Storybook 入口文件 .storybook/preview.js
中。
// .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 />
和您的主题,然后通过将其添加到 decorators
数组中,使用 withThemeFromJSXProvider
装饰器将它们应用于您的 stories。
// .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 控件为您提供了图形化控件来操作组件的 props。它们对于查找组件的边界情况和在浏览器中进行原型设计非常方便。
通常,您必须手动配置控件。但是如果您使用 Typescript,您可以重用 Material UI 的组件 prop types 来自动生成 story 控件。此外,这还会自动填充文档选项卡中的 prop 表。
我们以以下 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
的子项,并将所有其他 props 透传。然而,当我们在 Storybook 中渲染它时,我们的 controls 面板只允许我们更改我们自己声明的 label prop。
这是因为 Storybook 只会将组件的 prop types 或 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
中的参数,以显示 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 添加到控件中。
// 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 的控件。
我们的按钮现在有 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,本文的灵感来源于此。