如何用react-router布局路线来包装一个故事?

iva*_*nas 5 javascript reactjs react-router storybook

我有一个非常简单明了的 ComponentX,它可以呈现一些样式化的 HTML,不需要数据获取,甚至不需要路由。它有一个简单的故事。ComponentX 旨在用于深色主题的网站,因此它假设它将继承color: white;和其他此类样式。这对于正确渲染 ComponentX 至关重要。我不会用 ComponentX 的代码让您感到厌烦。

这些上下文样式(例如background-color: black;和)由组件color: white;应用于。使用 css-in-js 库将样式应用到文档。<body>GlobalStylesGlobalStylesEmotion

import { Global } from '@emotion/react';

export const GlobalStyles = () => (
  <>
    <Global styles={{ body: { backgroundColor: 'black' } }} />
    <Outlet />
  </>
);
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,该组件不接受子组件,而是用作布局路由,因此它呈现一个<Outlet />. 我希望应用程序使用由以下所示的布局路线来呈现如下所示的路线树(1)

  <Router>
    <Routes>
      <Route element={<GlobalStyles/>} >      <== (1)
        <Route path="login">
          <Route index element={<Login />} />
          <Route path="multifactor" element={<Mfa />} />
        </Route>
Run Code Online (Sandbox Code Playgroud)

未图示:<Login><Mfa>页面调用 ComponentX。

这有效!

问题出在故事上。如果我用 ComponentX 渲染一个简单的故事,它将很难看到,因为它期望所有这些样式都<body>存在。显而易见的解决方案是创建一个装饰器,用 this 包装每个故事<Route element={<GlobalStyles/>} >。如何才能做到这一点?这是我的工作但未达到预期的组件 x.stories.tsx:

import React from 'react';

import ComponentX from './ComponentX';

export default {
  component: ComponentX,
  title: 'Component X',
};

const Template = args => <ComponentX {...args} />;

export const Default = Template.bind({});
Default.args = {};
Default.decorators = [
  (story) => <div style={{ padding: '3rem' }}>{story()}</div>
];
Run Code Online (Sandbox Code Playgroud)

(我意识到我可以<GlobalStyles>围绕整个 制作一个简单的包装器组件<Router>,但我想使用此模式为采用其他中间布局路线的其他组件创建故事。)

Dre*_*ese 1

我通常做的是创建自定义装饰器组件来处理包装需要提供给它们的特定“上下文”的故事。

用法示例:

创建故事装饰器函数

import React from 'react';
import { Story } from '@storybook/react';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import theme from '../src/constants/theme';
import { AppLayout } from '../src/components/layout';

// Provides global theme and resets/normalizes browser CSS
export const ThemeDecorator = (Story: Story) => (
  <ThemeProvider theme={theme}>
    <CssBaseline />
    <Story />
  </ThemeProvider>
);

// Render a story into a routing context inside a UI layout
export const AppScreen = (Story: Story) => (
  <Router>
    <Routes>
      <Route element={<AppLayout />}>
        <Route path="/*" element={<Story />} />
      </Route>
    </Routes>
  </Router>
);
Run Code Online (Sandbox Code Playgroud)

.storybook/preview.js

import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
import { ThemeDecorator } from './decorators';

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
  options: {
    storySort: {
      includeName: true,
      method: 'alphabetical',
      order: ['Example', 'Theme', 'Components', 'Pages', '*'],
    },
  },
  viewport: {
    viewports: {
      ...INITIAL_VIEWPORTS,
    },
  },
};

export const decorators = [ThemeDecorator]; // <-- provide theme/CSS always
Run Code Online (Sandbox Code Playgroud)

任何需要应用程序布局和路由上下文的故事:

import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { AppScreen, MarginPageLayout } from '../../.storybook/decorators';

import BaseComponentX from './ComponentX';

export default {
  title: 'Components/Component X',
  component: BaseComponentX,
  decorators: [AppScreen], // <-- apply additional decorators
  parameters: {
    layout: 'fullscreen',
  },
} as ComponentMeta<typeof BaseComponentX>;

const BaseComponentXTemplate: ComponentStory<typeof BaseComponentX> = () => (
  <BaseComponentX />
);

export const ComponentX = BaseComponentXTemplate.bind({});
Run Code Online (Sandbox Code Playgroud)

在我的示例中,您可以想象将所有提供者和该Global组件(带道具)放置在我已实现的内容中ThemeDecorator,并设置为所有故事的默认装饰器。