SuperAPI AI 工具集

nebula-page-stories

前端与建站

查看项目

更新于 10-15 11:23

技能介绍

用途

本技能用于创建页面级 Storybook 故事(Page Stories),展示多个组件如何组合成真实页面布局。页面故事应尽量贴近终端用户在 Drupal Canvas 中的实际体验,因此应避免使用 Canvas 中不可用的模式。

位置与命名

  • 页面故事必须放在 src/stories/example-pages/ 目录。
  • 页面故事文件应命名为 <page-name>.stories.jsx。
  • Storybook 标题必须使用以下格式:title: "Example pages/[Page Title]"。

单故事提升(Single-story hoisting)

为获得更简洁的导航,页面故事应使用 Storybook 的单故事提升功能,避免侧边栏出现不必要的嵌套。实现方式:

  • 在标题中包含完整页面名称;
  • 只导出一个故事(通常为 Default);
  • 将故事的 name 属性设置为与标题最后一段一致。

示例:

// src/stories/example-pages/product-detail.stories.jsx
export default {
  title: "Example pages/Product: Detail",
  component: ProductDetailPage,
  parameters: {
    layout: "fullscreen",
  },
};

export const Default = {
  name: "Product: Detail",
};

这样会在 "Example pages" 下生成扁平的侧边栏条目 "Product: Detail",而不是嵌套的 "Product: Detail" → "Default" 结构。

创建新组件时,如果适合,可将其加入现有页面故事,或创建新的页面故事以在上下文中展示该组件。修改现有组件时,应检查 src/stories/example-pages/ 中的页面故事,确保改动在组合布局中正常工作,必要时更新它们。

PageLayout 组件

所有页面故事必须使用来自 src/stories/example-pages/page-layout.jsx 的共享 PageLayout 组件。

创建 PageLayout 的时机:

  • 创建第一个页面故事时,或
  • src/stories/example-pages/ 中不存在该组件时。

PageLayout 结构示例:

// src/stories/example-pages/page-layout.jsx
import Footer from "@/components/footer";
import Header from "@/components/header";
import Section from "@/components/section";

const footerData = {
  // Shared footer data
};

const PageLayout = ({ children }) => (
  <>
    <Section width="wide" content={<Header />} />
    {children}
    <Section width="wide" content={<Footer {...footerData} />} />
  </>
);

export default PageLayout;

使用 PageLayout 的示例:

// src/stories/example-pages/about-page.stories.jsx
import Section from "@/components/section";
import Text from "@/components/text";

import PageLayout from "./page-layout";

const AboutPage = () => (
  <PageLayout>
    <Section width="normal" content={<Text text="<p>About us...</p>" />} />
  </PageLayout>
);

export default {
  title: "Example pages/About",
  component: AboutPage,
  parameters: { layout: "fullscreen" },
};

export const Default = { name: "About" };

组合规则

页面故事只能导入和组合组件。

允许:

  • 从 @/components/<name> 导入;
  • 传递 props 并组合在一起;
  • 定义示例数据(字符串、对象、数组)。

不允许:

  • 内联定义 React 组件;
  • 使用原始 HTML 元素(<div>、<span>)进行布局;
  • 复制现有组件代码。

错误示例(内联定义组件并使用原始 HTML 元素):

const Logo = ({ color }) => <div className="flex">...</div>;

const Page = () => (
  <div className="flex flex-col gap-8">
    <Logo color="#000" />
    <div className="mx-auto max-w-3xl">Content</div>
  </div>
);

正确示例(导入并组合现有组件):

import Footer from "@/components/footer";
import Header from "@/components/header";
import Section from "@/components/section";
import Text from "@/components/text";

const Page = () => (
  <>
    <Header />
    <Section width="normal" content={<Text text="<p>Content here</p>" />} />
    <Footer />
  </>
);

如果需要 <div>,应寻找现有组件。如果不存在,先在 src/components/ 中创建它。

页面故事中不使用 className

className prop 在 Canvas 中未暴露,页面故事不应传递它。

错误示例:

const AboutPage = () => (
  <PageLayout>
    <Section width="normal">
      <Text className="mt-8 mb-12" content="..." />
    </Section>
    <Card className="shadow-xl" title="Mission" />
  </PageLayout>
);

正确示例:

const AboutPage = () => (
  <PageLayout>
    <Section width="normal">
      <Text content="..." />
    </Section>
    <Card title="Mission" />
  </PageLayout>
);

className 适用的场景:

  • 在组件自身的 index.jsx 中组合其他组件时;
  • 在单个组件故事中(非页面故事)。

使用 Spacer 组件控制间距

使用 spacer 而非 margin 或 padding 来控制组件之间的间距。

如果 spacer 不存在,复制它:

cp -r examples/components/spacer src/components/
cp examples/stories/spacer.stories.jsx src/stories/

正确示例:

import Spacer from "@/components/spacer";

const AboutPage = () => (
  <PageLayout>
    <Hero title="About Us" />
    <Spacer height="large" />
    <Section width="normal">
      <Text content="<p>Our story...</p>" />
    </Section>
    <Spacer height="medium" />
    <Section width="normal">
      <Text content="<p>Our mission...</p>" />
    </Section>
  </PageLayout>
);

错误示例:

const AboutPage = () => (
  <PageLayout>
    <Hero title="About Us" />
    <div className="mt-16">
      <Section width="normal">
        <Text content="<p>Our story...</p>" />
      </Section>
    </div>
  </PageLayout>
);

布局组件

使用现有布局组件而非内联样式。检查 src/components/ 和 examples/components/ 中的:

  • 宽度约束:section、container;
  • 列布局:grid-container、columns;
  • 间距:spacer、divider。

正确示例:

<WidthConstraintComponent variant="wide">
  <ColumnLayoutComponent columns="sidebar-main">
    {/* Content */}
  </ColumnLayoutComponent>
</WidthConstraintComponent>

错误示例:

<div className="mx-auto max-w-6xl px-4">
  <div className="grid grid-cols-[300px_1fr] gap-8">
    {/* Content */}
  </div>
</div>

适用场景

  • 为 Nebula 项目创建页面级 Storybook 故事;
  • 展示多个组件在真实页面布局中的组合方式;
  • 确保页面故事与 Drupal Canvas 的终端用户体验保持一致;
  • 在修改现有组件时验证其在组合布局中的表现。

限制

  • 页面故事必须放在指定目录并使用指定命名和标题格式;
  • 只能导入和组合现有组件,不能内联定义组件或使用原始 HTML 元素做布局;
  • 页面故事中不能传递 className;
  • 间距必须使用 spacer 组件,布局必须使用现有布局组件;
  • 应避免使用 Canvas 中不可用的模式。