参加直播:周四,美国东部时间上午 11 点,Storybook 9 发布 & AMA
返回集成
@mui/material

集成Material UI与 Storybook

Material UI 是一个基于 Google Material Design 规范的组件库。
先决条件

本示例假设您已经有一个使用 @mui/material 的 React 应用,并且刚按照入门指南设置了 Storybook >= 7.0。还没有?请遵循 MUI 的设置说明,然后运行

# Add Storybook:
npm create storybook@latest

1. 添加 @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 数组。

2. 打包字体和图标以获得更好的性能

Material UI 依赖于两种字体才能按预期呈现,分别是 Google 的 RobotoMaterial Icons。虽然您可以直接从 Google Fonts CDN 加载这些字体,但将字体与 Storybook 打包在一起对于性能更好。

  • 🏎️ 字体加载更快,因为它们与您的应用程序来自同一个位置
  • ✈️ 字体将离线加载,因此您可以在任何地方继续开发您的 Stories
  • 📸 不再出现不一致的快照测试,因为字体会立即加载

首先,将字体作为依赖项安装。

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';

3. 加载您的主题和全局 CSS

.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 选择所需的主题。

4. 使用 Material UI prop types 获取更好的控件和文档

Storybook 控件为您提供了图形化控件来操作组件的 props。它们对于查找组件的边界情况和在浏览器中进行原型设计非常方便。

通常,您必须手动配置控件。但是如果您使用 Typescript,您可以重用 Material UI 的组件 prop types 来自动生成 story 控件。此外,这还会自动填充文档选项卡中的 prop 表。

Changing the button components props using Storybook 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 的子项,并将所有其他 props 透传。然而,当我们在 Storybook 中渲染它时,我们的 controls 面板只允许我们更改我们自己声明的 label prop。

The button story with only a label prop control

这是因为 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 的控件。

The button story with all 27 prop controls from the MUI button 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。

The button story with only the controls specified

📣 特别鸣谢 Eric Mudrak 撰写的优秀文章 Storybook with React & TypeScript,本文的灵感来源于此。

标签
贡献者
  • shaunlloyd
    shaunlloyd