我有一个非常简单明了的 ComponentX,它可以呈现一些样式化的 HTML,不需要数据获取甚至路由。它有一个简单的故事。 ComponentX 旨在用于深色主题网站,因此它假定它将继承 color: white;
和其他这样的风格。这对于正确渲染 ComponentX 至关重要。我不会让您厌烦 ComponentX 的代码。
那些上下文样式,比如background-color: black;
和 color: white;
, 适用于 <body>
通过 GlobalStyles
成分。 GlobalStyles
使用 css-in-js 库 Emotion
将样式应用于文档。
import { Global } from '@emotion/react';
export const GlobalStyles = () => (
<>
<Global styles={{ body: { backgroundColor: 'black' } }} />
<Outlet />
</>
);
如您所见,该组件不接受子组件,而是用作布局路由,因此它呈现一个 <Outlet />
。 .我希望应用程序使用 (1)
指示的布局路由来呈现如下所示的路由树。
<Router>
<Routes>
<Route element={<GlobalStyles/>} > <== (1)
<Route path="login">
<Route index element={<Login />} />
<Route path="multifactor" element={<Mfa />} />
</Route>
未显示:<Login>
和 <Mfa>
页面调用 ComponentX。
这有效!
问题出在故事上。如果我用 ComponentX 渲染一个普通的故事,它将很难被看到,因为它需要所有这些样式在 <body>
上。在场。显而易见的解决方案是创建一个装饰器,用这个 <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>
];
(我意识到我可以使 <GlobalStyles>
成为整个 <Router>
的简单包装器组件,但我想使用此模式为采用其他中间布局路线的其他组件创建故事。)
最佳答案
我通常做的是创建自定义装饰器组件来处理包装需要提供给它们的特定“上下文”的故事。
示例用法:
创建故事装饰器函数
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>
);
.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
任何需要应用布局和路由上下文的故事:
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({});
在我的示例中,您可以想象将 所有 您的提供程序和 Global
组件(w/props)放置在我作为 ThemeDecorator
实现的内容中> 并设置为所有故事的默认装饰器。
关于javascript - 你如何用 react-router 布局路由来包装故事?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74722305/