feat(bookTree): add Redux state handlers for Drupal book trees
MR: feat(RecursiveMenu): add book tree support with isBookMenu prop
Branch: feature/add-book-state-handlers | Base: master | Generated: 2026-04-15 09:45
Summary
Add Drupal book tree support to RecursiveMenu via new isBookMenu and bookId optional props. When active, the component swaps the menu data provider (<Menu>) for a new <BookMenu> that reads from the book tree Redux state instead. The rendering pipeline remains identical — all existing menu functionality (collapsible, max menu, breadcrumb mode) works seamlessly with books. Fully backwards compatible.
Changes
-
Types:
RecursiveMenuPropsnow uses a discriminated union (RecursiveMenuStandardProps | RecursiveMenuBookProps) to enforce type safety —menuIdis required whenisBookMenuis absent;bookIdis required whenisBookMenu: true.RecursiveMenuMaxMenuPropsupdated to use type intersection. -
RecursiveMenuRenderer: Destructures
isBookMenu,menuId,bookIdat top level; adds runtime guard for missingbookIdwhenisBookMenu=true; usesin_active_trailfrom Drupal API for book menu trail detection (vs. URL prefix matching for standard menus); conditional render of<BookMenu bookId={...}>or<Menu id={...}>. -
BookMenu component: New functional component (
src/Drupal/Entity/BookMenu/index.tsx) dispatchesbookGetCurrentTree(bookId)on mount; readsstate.bookTree.trees[bookId]and error state; implements render-prop pattern matchingMenucontract. -
Exports:
BookMenuadded tosrc/index.tsas a public component. -
Stories: Added
BookMenustory with mock Redux store and Drupal book tree test data. -
Book state (prior commits): Redux actions (
bookGetTree,bookGetCurrentTree) and reducer already in place; types expanded withBookTreeLink,BookTreeRouteParameters,BookTreeMetaData. -
Project config:
CLAUDE.mdcreated documenting ecosystem, patterns, and TypeScript rules.
How to Test
-
ESLint: yarn eslint src/Components/RecursiveMenu/— 0 errors on the component files -
Standard menu (unchanged): Open Storybook ( yarn start) → "Molecule/RecursiveMenu" → verify all existing stories (Default, WithIcon, CollapsibleMenu, MaxMenu variants) render and behave identically - Does not pass due to outstanding issues which are not in the scope of this MR -
Book menu rendering: In Storybook, navigate to "Molecule/RecursiveMenu" → "BookMenu" story → verify the menu tree renders with correct hierarchy (root + 2 leaf nodes) -
Type safety: Try passing both menuIdandbookIdin TypeScript — should error. TryisBookMenu={true}withoutbookId— should error. -
Active trail: On the BookMenu story, the tree should use in_active_trailfrom the API (mocked tofalsein story data) for ancestor highlighting, not URL matching.
Review Guide
Suggested reading order:
-
src/Components/RecursiveMenu/types.ts— Type changes: discriminated union definition (clearest expression of the new API contract) -
src/Components/RecursiveMenu/index.tsx— Renderer: prop destructuring, conditional data provider, trail logic -
src/Drupal/Entity/BookMenu/index.tsx— New component: data fetching pattern and render props -
src/index.ts— Export addition (straightforward) -
src/Components/RecursiveMenu/stories.jsx— Story: mock Redux setup and usage example
Key implementation detail: Active trail detection switches on isBookMenu — book menus use in_active_trail from the API, standard menus derive it from URL prefix matching.
Edited by Liam O'Toole