-
Notifications
You must be signed in to change notification settings - Fork 0
feat(nav): pin a shared ModeNav inside the universal header, wired to Therapy search #1390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
fea0489
chore: organize dirty work from claude/therapy-nav-redesign-350a0e
BigSimmo 0ce82de
Merge branch 'main' into claude/therapy-nav-redesign-350a0e
BigSimmo 6593afe
chore(mockups): register therapy navigation mockup routes and fix lint
BigSimmo d228c1d
feat(nav): add shared ModeNav pinned to the universal header
BigSimmo 8264f7d
Merge remote-tracking branch 'origin/main' into claude/therapy-nav-re…
BigSimmo bd3dc4f
Merge older remote branch head into claude/therapy-nav-redesign-350a0e
BigSimmo dad09be
Merge origin/main into claude/therapy-nav-redesign-350a0e
BigSimmo 8f76f0f
fix(mode-nav): return focus to the opener that actually exists
claude a5b95f8
Merge remote-tracking branch 'origin/main' into claude/therapy-nav-re…
claude 8a8a854
docs(ledger): record the PR #1391 review
claude 67d8fa5
Merge remote-tracking branch 'origin/main' into claude/therapy-nav-re…
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /** | ||
| * Slot capacities for the mode navigation bar, narrowest first. These mirror | ||
| * the `@container mode-nav (min-width: …)` bands in `globals.css`; changing one | ||
| * without the other is what `tests/mode-nav-contract.test.ts` guards against. | ||
| */ | ||
| export const MODE_NAV_BANDS = [3, 4, 5] as const; | ||
|
|
||
| export type ModeNavBand = (typeof MODE_NAV_BANDS)[number]; | ||
|
|
||
| /** Below this a bar is a label, not navigation, so nothing renders. */ | ||
| export const MODE_NAV_MIN_ITEMS = 2; | ||
|
|
||
| export type ModeNavBandPlan = { | ||
| /** Item index → the narrowest band at which it appears. Absent = always folded. */ | ||
| firstVisibleBand: Map<number, ModeNavBand>; | ||
| /** The widest band at which More still appears, or null when it is never needed. */ | ||
| moreUntil: ModeNavBand | null; | ||
| }; | ||
|
|
||
| /** | ||
| * Decides which destinations survive each width band. | ||
| * | ||
| * For a capacity of C slots: if every item fits, all of them show and there is | ||
| * no overflow entry; otherwise the first C-1 show and the tail folds into More, | ||
| * which occupies the last slot. | ||
| * | ||
| * Derived from the item COUNT alone — a static fact known at render — so this | ||
| * never measures layout. Runtime measurement ("priority plus") would let the | ||
| * bar's contents change under the user between screens and orientations, which | ||
| * is the one thing navigation must not do. | ||
| * | ||
| * Because a wider band is always a superset of a narrower one, the slots that | ||
| * stay never move as the container narrows; the overflow only fills from the | ||
| * tail. | ||
| */ | ||
| export function planModeNavBands(count: number): ModeNavBandPlan { | ||
| const firstVisibleBand = new Map<number, ModeNavBand>(); | ||
| let moreUntil: ModeNavBand | null = null; | ||
|
|
||
| for (const capacity of MODE_NAV_BANDS) { | ||
| const visible = count <= capacity ? count : capacity - 1; | ||
| for (let index = 0; index < visible; index += 1) { | ||
| if (!firstVisibleBand.has(index)) firstVisibleBand.set(index, capacity); | ||
| } | ||
| if (count > capacity) moreUntil = capacity; | ||
| } | ||
|
|
||
| return { firstVisibleBand, moreUntil }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| "use client"; | ||
|
|
||
| import { useLayoutEffect, useState, type ReactNode } from "react"; | ||
| import { createPortal } from "react-dom"; | ||
|
|
||
| import { phoneHeaderCollapseAddonSlotId } from "@/lib/mode-home-composer"; | ||
|
|
||
| /** | ||
| * Moves a mode's navigation bar into the universal header's collapse track at | ||
| * *every* width. | ||
| * | ||
| * This is a deliberate sibling of `PhoneHeaderCollapsePortal`, which resolves | ||
| * the same host only below the phone breakpoint. That gate is why the Therapy | ||
| * strip never travelled with the header on a tablet or desktop: it stayed in | ||
| * page flow as `position: sticky; top: 0` instead. | ||
| * | ||
| * The header needs no change for this to work. Its addon slot is rendered with | ||
| * no breakpoint prefix (`master-search-header.tsx`, `empty:hidden` inside | ||
| * `universal-header-collapse`), and for the `GlobalSearchShell` host — | ||
| * `{ strategy: "collapse", wide: "sticky" }` — the collapsing wrapper takes its | ||
| * ungated `grid-template-rows: 1fr -> 0fr` branch at every width. Occupying the | ||
| * slot from here therefore inherits the header's hide and reveal exactly, with | ||
| * no second scroll listener that could drift out of step with it. | ||
| * | ||
| * Falls back to normal flow when no host exists — routes rendered without the | ||
| * universal header, and the server pass — so navigation is never lost. | ||
| * | ||
| * Slot ownership: the addon slot holds ONE page-owned header. `DocumentViewer` | ||
| * and the differentials detail page already claim it on phones, so a mode whose | ||
| * routes include those pages must not also mount a bar there. `ModeNav` renders | ||
| * nothing below two destinations, which is what keeps those modes clear today; | ||
| * `tests/mode-nav-contract.test.ts` fails if that stops being true. | ||
| */ | ||
| export function ModeNavHeaderPortal({ children }: { children: ReactNode }) { | ||
| const [host, setHost] = useState<HTMLElement | null>(null); | ||
|
|
||
| useLayoutEffect(() => { | ||
| const sync = () => { | ||
| const next = document.getElementById(phoneHeaderCollapseAddonSlotId); | ||
| setHost((current) => (current === next ? current : next)); | ||
| }; | ||
|
|
||
| sync(); | ||
| // The shell remounts its header across mode switches, so the host element's | ||
| // identity is not stable for this component's lifetime. | ||
| const observer = new MutationObserver(sync); | ||
| observer.observe(document.body, { childList: true, subtree: true }); | ||
|
|
||
| return () => observer.disconnect(); | ||
| }, []); | ||
|
|
||
| return host ? createPortal(children, host) : children; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.