コンテンツにスキップ

ヘッダー階層メニュー(Collection Tree 連動)

概要

Storefront ヘッダーの商品一覧メニュー(PC メガメニュー / モバイルドロワー)で、Vendure の Collection tree の子孫階層をそのまま反映した開閉式ナビゲーションを実現します。検索導線は /search へ分離し、ここでは catalog 用の collection 導線だけを扱います。

データの流れ

Vendure Collection tree
  └─ buildCollectionBrowseGroups()          # 孫階層まで保持
      └─ filterCollectionBrowseGroupsByVisibleIds()  # 顧客可視性で再帰フィルタ
          └─ buildHeaderProductMenuSections()        # HeaderProductMenuLink へ変換
              └─ section.links に children を含む階層構造
  • CollectionBrowseItemchildren プロパティを追加し、collection tree をそのまま保持
  • フィルタリング(可視性)とセクション化は再帰的に適用
  • ヘッダー以外の collection 利用箇所(商品一覧画面など)も同じ tree を参照可能

表示順の制御(collection position 継承)

要件の正本: 2026-06 Storefront コレクション表示順 補足仕様。本節はその実装メモ。

コレクションの並び順は Vendure 標準の Collection.position(Vendure Dashboard で各 collection を編集)が唯一の正本です。独自の表示順カスタムフィールドや plugin は持ちません。collection-browse.tsbuildCollectionBrowseGroups() が同一階層(兄弟)の collection を position 昇順で並べ、これを配列順として焼き込みます(下流のサイドバー / メガメニュー / ドロワーは再ソートせず配列順で描画)。

並び順の正本ルールは次の 継承 + 子優先モデルです(実効position = 子の position ?? 親の実効position)。

  • 継承: 子に position が無いときは、親(祖先まで辿った)の実効 position を継承して並ぶ。継承が無い root(親なし)の未設定だけは末尾扱い(Number.MAX_SAFE_INTEGER)。
  • 子優先: 子に position が定義されていれば、親から継承する値を上書きして子自身の値で並ぶ。
  • 安定ソート: 実効 position が同値のときは取得順を保つ(兄弟内の index でタイブレーク)。
観点 挙動
子に position あり 子の値で並ぶ(親より優先)
子に position なし 親(祖先)の実効 position を継承して並ぶ
親も position なし さらに祖先を辿って継承。最終的に root も無ければ末尾
同値 取得順(安定ソート)

補足: 最上位 root グループ(ブランド / 製品タイプ / キャンペーン)の並びだけは position ではなく collection-browse-groups-for-browser.ts の slug 固定順。今回の継承は root 配下の子 collection 以降に効く。

実装は collection-browse.tsresolveEffectivePosition / sortBrowseNodesByPosition / toBrowseItem / buildCollectionBrowseGroups。回帰防止テストは collection-browse.test.ts(「子に position が無いとき、親の position を継承して並ぶ」「子に position が定義されているとき、継承する親の position より優先される」「親が position 未設定でも、子は祖先の position を辿って継承する」)。

画面イメージ

商品一覧 (megamenu)
├── ブランド (セクション見出し)
│   ├── ▶ エクスビアンス    [一覧へ]     ← クリックで展開/折り畳み
│   │   ├── 店頭用                       ← /products?collection=exuviance-retail
│   │   └── 業務用                       ← /products?collection=exuviance-professional
│   └── ▶ メソシューティカル [一覧へ]
│       └── 店頭用
├── 製品タイプ (セクション見出し)
│   └── スキンケア                       ← 子なし → 単純リンク
  • ▶ をクリック: 子階層が開く / 閉じる(PC: Collapsible / モバイル: Collapsible
  • [一覧へ] をクリック: 親コレクションの一覧ページへ遷移
  • 子を持たないリンクは従来どおり単純リンクとして表示
  • 閉じた状態では子項目は非表示(DOM 上に存在しない)

確認項目

閉じた状態

  • 親ラベル ▶ と [一覧へ] リンクが表示されている
  • 子階層のリンク(例: 店頭用, 業務用)は表示されていない
  • ▶ アイコンはデフォルトで右向き

展開した状態

  • ▶ アイコンが下向き(90度回転)になっている
  • 子階層のリンクがインデント表示される
  • 子リンクをクリックすると該当 collection フィルタリングページに遷移

PC メガメニュー(header-navigation.tsx)

  • セクション見出しは border-b で区切られる
  • セクション内の階層リンクは Collapsible で開閉

モバイルドロワー(header-mobile-menu.tsx)

  • top-level(商品一覧 / サポート)は Accordion で開閉
  • その内側で collection の階層リンクが Collapsible で開閉
  • 子リンク、[一覧へ] リンクのクリックでメニューが閉じる

関連ファイル

役割 ファイル
collection tree モデル / 表示順 collection-browse.ts
root 並び(slug 固定) collection-browse-groups-for-browser.ts
ヘッダーメニュー構築 header-product-menu.ts
ヘッダーモデル統合 header-model.ts
PC メガメニュー描画 header-navigation.tsx
モバイルドロワー描画 header-mobile-menu.tsx
WordPress メニュー正規化 cms/content/menus.ts

注意点

  • WordPress メニューの children は現状 1 段階のみ取得(2階層目は collection tree 側で表現)
  • HeaderProductMenuLink.children があれば自動的に Collapsible モードになる
  • 子なしのリンクは従来の単純リンクとしてそのまま表示される