@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 的入口点 .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 />
和您的主题,然后使用 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 的组件 prop types 来自动生成 story controls。 此外,这还将自动填充文档选项卡中的 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
中的 parameters,以显示 controls 表的 description 和 default 列。
// .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 文章,它启发了这个技巧。