返回集成
@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 仓库上提交一个错误报告,以便我们进一步改进它。 要手动添加此插件,请安装它,然后将其添加到 .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 /> 和您的主题,然后使用 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 选择所需的主题。

4. 使用 Material UI prop types 来获得更好的 controls 和文档

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

通常,您必须手动配置 controls。 但是,如果您使用的是 Typescript,则可以重用 Material UI 的组件 prop types 来自动生成 story controls。 此外,这还将自动填充文档选项卡中的 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 中的 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。

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

The button story with only the controls specified

📣 特别鸣谢 Eric Mudrak 的精彩 Storybook with React & TypeScript 文章,它启发了这个技巧。

标签
贡献者
  • shaunlloyd
    shaunlloyd