ヘッダー階層メニュー(Collection Tree 連動)¶
概要¶
Storefront ヘッダーの商品一覧メニュー(PC メガメニュー / モバイルドロワー)で、Vendure の Collection tree の子孫階層をそのまま反映した開閉式ナビゲーションを実現します。検索導線は /search へ分離し、ここでは catalog 用の collection 導線だけを扱います。
データの流れ¶
Vendure Collection tree
└─ buildCollectionBrowseGroups() # 孫階層まで保持
└─ filterCollectionBrowseGroupsByVisibleIds() # 顧客可視性で再帰フィルタ
└─ buildHeaderProductMenuSections() # HeaderProductMenuLink へ変換
└─ section.links に children を含む階層構造
CollectionBrowseItemにchildrenプロパティを追加し、collection tree をそのまま保持- フィルタリング(可視性)とセクション化は再帰的に適用
- ヘッダー以外の collection 利用箇所(商品一覧画面など)も同じ tree を参照可能
表示順の制御(collection position 継承)¶
要件の正本: 2026-06 Storefront コレクション表示順 補足仕様。本節はその実装メモ。
コレクションの並び順は Vendure 標準の Collection.position(Vendure Dashboard で各 collection を編集)が唯一の正本です。独自の表示順カスタムフィールドや plugin は持ちません。collection-browse.ts の buildCollectionBrowseGroups() が同一階層(兄弟)の 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.ts の resolveEffectivePosition / 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 モードになる- 子なしのリンクは従来の単純リンクとしてそのまま表示される