返回集成
@mui/material

集成Material UI与 Storybook

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

此示例假设您已经拥有一个使用 @mui/material 的 React 应用程序,并且刚刚使用 入门指南 设置了 **Storybook >= 7.0**。如果没有,请按照 MUI 的 安装说明 进行操作,然后运行

# Add Storybook:
npx storybook@latest init

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 打包字体可以提高性能。

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

要开始,请将字体安装为依赖项。

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

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

.storybook/preview.js 中,导入 <CssBaseline /><ThemeProvider /> 和您的主题,然后使用 withThemeFromJSXProvider 装饰器将它们应用于您的故事,方法是将其添加到 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 中将出现一个工具栏菜单,用于为您的故事选择所需的主题。

4. 使用 Material UI 属性类型以获得更好的控件和文档

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

通常,您需要手动配置控件。但是,如果您使用的是 Typescript,则可以重用 Material UI 的组件 prop 类型来自动生成故事控件。作为奖励,这还将自动填充文档选项卡中的 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 中时,我们的控件面板只允许我们更改我们自己声明的 label prop。

The button story with only a label prop control

这是因为 Storybook 仅将显式声明在组件的 prop 类型或 Story Args 中的 props 添加到控件表中。让我们更新 Storybook 的 Docgen 配置,也将 Material UI 的 Button props 引入控件表中。

// .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 中的参数,以显示控件表的描述和默认列。

// .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