jpskill.com
📦 その他 コミュニティ

nebula-page-stories

複数のコンポーネントを組み合わせて現実的なレイアウトのサンプルページを作成し、コンポーネント同士の連携やPageLayoutの使い方、Spacerによる間隔調整、単一ストーリーの持ち上げなどをStorybookで表現するSkill。

📜 元の英語説明(参考)

Create example page stories that compose multiple components into realistic layouts. Use when building page-level Storybook stories that showcase how components work together. Covers PageLayout usage, composition rules, spacing with Spacer, and single-story hoisting.

🇯🇵 日本人クリエイター向け解説

一言でいうと

複数のコンポーネントを組み合わせて現実的なレイアウトのサンプルページを作成し、コンポーネント同士の連携やPageLayoutの使い方、Spacerによる間隔調整、単一ストーリーの持ち上げなどをStorybookで表現するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o nebula-page-stories.zip https://jpskill.com/download/9239.zip && unzip -o nebula-page-stories.zip && rm nebula-page-stories.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9239.zip -OutFile "$d\nebula-page-stories.zip"; Expand-Archive "$d\nebula-page-stories.zip" -DestinationPath $d -Force; ri "$d\nebula-page-stories.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して nebula-page-stories.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → nebula-page-stories フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Storybook を使用したページのストーリーの例

ページのストーリーは、コンポーネントが現実的なレイアウトでどのように連携するかを示します。 エンドユーザーが Drupal Canvas で体験することと密接に一致させる必要があるので、Canvas で利用できないパターンは避けてください。

場所と命名

  • ページのストーリーは、必ず src/stories/example-pages/ に配置してください。
  • ページのストーリーファイルの名前は <page-name>.stories.jsx にしてください。
  • Storybook の title は、次の形式を使用する必要があります。 title: "Example pages/[ページタイトル]"

シングルストーリーのホイスティング

ナビゲーションをよりクリーンにするために、シングルストーリーのホイスティングを使用してください。 ページのストーリーでは、Storybook のシングルストーリーのホイスティング機能を使用して、サイドバーの不要なネストを避ける必要があります。これは、次の方法で実現されます。

  1. title にページ名全体を含める
  2. ストーリーを 1 つだけエクスポートする (通常は Default)
  3. ストーリーの 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",
};

これにより、ネストされた "Product: Detail" → "Default" 構造ではなく、フラットなサイドバーエントリ "Product: Detail" が "Example pages" の下に表示されます。

  • 新しいコンポーネントを作成するときは、既存のページのストーリーに自然に適合する場合は、それらのストーリーに追加することを検討するか、コンテキスト内でコンポーネントを示す新しいページのストーリーを作成してください。
  • 既存のコンポーネントを変更するときは、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 プロパティは 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 を使用してコンポーネント間のスペーシングを制御します。

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 cla
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Example page stories with Storybook

Page stories showcase how components work together in realistic layouts. They should closely mirror what end users will experience in Drupal Canvas, so avoid patterns that won't be available in Canvas.

Location and naming

  • Page stories MUST be placed in src/stories/example-pages/
  • Page story files should be named <page-name>.stories.jsx
  • The Storybook title MUST use this format: title: "Example pages/[Page Title]"

Single-story hoisting

Use single-story hoisting for cleaner navigation. Page stories should use Storybook's single-story hoisting feature to avoid unnecessary nesting in the sidebar. This is achieved by:

  1. Including the full page name in the title
  2. Exporting only one story (typically Default)
  3. Setting the story's name property to match the last segment of the title
// 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",
};

This results in a flat sidebar entry "Product: Detail" under "Example pages", rather than a nested "Product: Detail" → "Default" structure.

  • When creating new components, consider adding them to existing page stories if they fit naturally, or create new page stories to demonstrate the component in context.
  • When modifying existing components, review page stories in src/stories/example-pages/ to ensure changes work well in composed layouts and update them if needed.

PageLayout component

All page stories must use a shared PageLayout component from src/stories/example-pages/page-layout.jsx.

Create PageLayout when

  • Creating the first page story, OR
  • It doesn't exist in src/stories/example-pages/

PageLayout structure

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

Using 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" };

Composition rules

Page stories must only import and compose components.

Allowed

  • Import from @/components/<name>
  • Pass props and compose together
  • Define sample data (strings, objects, arrays)

Not allowed

  • Define React components inline
  • Use raw HTML elements (<div>, <span>) for layout
  • Duplicate existing component code
// Wrong - defines inline components and uses raw HTML elements
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>
);
// Correct - imports and composes existing components
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 />
  </>
);

If you need a <div>, look for an existing component. If none exists, create it in src/components/ first.

No className in page stories

The className prop is not exposed in Canvas. Page stories should not pass it.

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

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

When className IS appropriate:

  • Inside a component's index.jsx when composing other components
  • In individual component stories (not page stories)

Spacing with Spacer component

Control spacing between components using spacer, not margins or padding.

If spacer doesn't exist, copy it:

cp -r examples/components/spacer src/components/
cp examples/stories/spacer.stories.jsx src/stories/
// Correct
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>
);

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

Layout components

Use existing layout components instead of inline styles.

Check src/components/ and examples/components/ for:

  • Width constraints: section, container
  • Column layouts: grid-container, columns
  • Spacing: spacer, divider
// Correct
<WidthConstraintComponent variant="wide">
  <ColumnLayoutComponent columns="sidebar-main">
    {/* Content */}
  </ColumnLayoutComponent>
</WidthConstraintComponent>

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