Skip to content

Commit c13c388

Browse files
Make menu surface edge-to-edge and align with header (#25)
1 parent 2740fa1 commit c13c388

3 files changed

Lines changed: 65 additions & 29 deletions

File tree

‎app/globals.css‎

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
--shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
5656
/* mvp.css caps header/footer/main at --width-content (1080px by default).
5757
Report pages and listings should fill the view: content uses 95% of the
58-
viewport width instead. */
58+
viewport width instead. (The top menu opts out entirely — #top-menu
59+
sets max-width: none so the menu surface reaches the page edges.) */
5960
--width-content: min(3840px, 95vw);
6061
}
6162

@@ -289,14 +290,24 @@ pre {
289290
whole rendered box (padding contributes outside max-height). */
290291
padding: 0;
291292
max-height: var(--menu-max-height);
293+
/* mvp.css also caps every header at --width-content; the menu surface
294+
must reach the left and right edges of the page instead. */
295+
max-width: none;
292296
}
293297

294298
.menu-bar {
295299
display: flex;
296300
align-items: center;
297301
gap: 0.4rem;
298302
flex-wrap: nowrap;
299-
max-width: 1200px;
303+
/* The menu surface (#top-menu) spans the full viewport, but its content
304+
keeps the centered --width-content column. border-box makes the cap
305+
bound the rendered box (padding inside), matching .theme-bar so the
306+
two rows' content edges align. The margin: 0 auto both centers the
307+
column and resets mvp.css's 7rem nav margin — without the reset the
308+
bar's bottom margin pushes #top-menu past its max-height cap. */
309+
max-width: var(--width-content);
310+
box-sizing: border-box;
300311
margin: 0 auto;
301312
padding: 0.3rem 0.8rem;
302313
min-height: 48px;
@@ -382,12 +393,12 @@ pre {
382393
/* ---- Breadcrumbs (user/report trail under the top menu) ---- */
383394
.breadcrumbs {
384395
/* Lives on the .theme-bar row (rendered by app/layout.jsx), sharing the
385-
line with the color theme toggle: the row owns the menu-column
386-
alignment formula (max-width: var(--width-content) + 0.8rem horizontal
387-
padding, see .theme-bar above), so the trail must add no box-model
388-
offsets of its own — flex-grow fills the row's left side, keeping the
389-
first crumb's glyph at the menu bar's left content edge while the
390-
toggle pins to the right edge, both vertically centered. */
396+
line with the color theme toggle: the row carries the menu-column
397+
alignment formula (max-width: var(--width-content) + 0.8rem horizontal
398+
padding, see .theme-bar below), so the trail must add no box-model
399+
offsets of its own — flex-grow fills the row's left side, keeping the
400+
first crumb's glyph at the menu bar's left content edge while the
401+
toggle pins to the right edge, both vertically centered. */
391402
flex: 1 1 auto;
392403
min-width: 0;
393404
/* mvp.css gives bare nav a 7rem bottom margin, bold text and a spaced flex
@@ -459,9 +470,9 @@ pre {
459470

460471
.hero-search {
461472
position: relative;
462-
/* Exactly half the rendered width of the top menu (#top-menu is capped by
463-
mvp.css at --width-content = 1080px, full width below that) and centered
464-
under it. */
473+
/* Exactly half the menu content column (.menu-bar is capped at
474+
--width-content = 1080px, full width below that) and centered under
475+
the edge-to-edge header. */
465476
width: min(540px, 50vw);
466477
margin: 1rem auto;
467478
text-align: left;
@@ -716,14 +727,14 @@ pre {
716727
/* ---- Color theme toggle (three radio modes, pure-CSS switching) ---- */
717728
/* Row below the site header (rendered by app/layout.jsx): the breadcrumb
718729
trail (when the route has one) on the left and the toggle on the right,
719-
vertically centered on one line. The alignment formula .breadcrumbs used
720-
to carry lives here now: --width-content column, 0.8rem horizontal
721-
padding, border-box — keeping the first crumb's glyph at the menu bar's
722-
left content edge and the toggle at its right content edge at every
723-
viewport, the two edges mirroring each other. #top-menu is capped by
724-
mvp.css at --width-content, so the row and the menu bar can never drift
725-
apart. Crumb routes grow the trail (flex: 1 1 auto); crumb-less routes
726-
leave only the toggle, pinned right by flex-end. */
730+
vertically centered on one line. The row shares the menu bar's centered
731+
--width-content content column (the #top-menu surface around it is edge
732+
to edge, but the bar keeps the column): 0.8rem horizontal padding,
733+
border-box — keeping the first crumb's glyph at the menu bar's left
734+
content edge and the toggle at its right content edge at every viewport,
735+
the two edges mirroring each other, so the row and the menu bar can
736+
never drift apart. Crumb routes grow the trail (flex: 1 1 auto);
737+
crumb-less routes leave only the toggle, pinned right by flex-end. */
727738
.theme-bar {
728739
display: flex;
729740
align-items: center;

‎tests/unit/layout-styles.test.js‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,37 @@ describe('layout styles (mvp.css section-flex reset)', () => {
5656
expect(body).toMatch(/margin:\s*0\s+auto\s*;/);
5757
});
5858

59+
test('the menu surface spans the viewport, its content stays centered', () => {
60+
// mvp.css caps every header (and footer/main) at --width-content; the
61+
// menu surface must reach the page's left/right edges instead, while
62+
// the inner bar keeps the centered --width-content content column and
63+
// the margin reset of mvp.css's 7rem nav margin. The .theme-bar row
64+
// below mirrors the bar's content edges (guarded in
65+
// tests/unit/theme-css.test.js and the E2E alignment checks).
66+
// ruleMap keeps only the last block per selector (#top-menu and
67+
// .menu-bar also appear inside the 900px media query), so scan every
68+
// block in the raw source instead.
69+
const menuBlocks = [...css.matchAll(/#top-menu\s*\{([^}]*)\}/g)]
70+
.map((m) => m[1]);
71+
expect(menuBlocks.length).toBeGreaterThan(0);
72+
expect(menuBlocks.some((b) => /max-width:\s*none/.test(b))).toBe(true);
73+
const barBlocks = [...css.matchAll(/\.menu-bar\s*\{([^}]*)\}/g)]
74+
.map((m) => m[1]);
75+
expect(barBlocks.length).toBeGreaterThan(0);
76+
expect(barBlocks.some((b) => /max-width:\s*var\(--width-content\)/.test(b))).toBe(true);
77+
// border-box keeps the padding inside the capped column, matching
78+
// .theme-bar's geometry (guarded in tests/unit/theme-css.test.js).
79+
expect(barBlocks.some((b) => /box-sizing:\s*border-box/.test(b))).toBe(true);
80+
expect(barBlocks.some((b) => /margin:\s*0\s+auto/.test(b))).toBe(true);
81+
});
82+
5983
test('the breadcrumb trail stretches inside the theme bar row (menu column)', () => {
6084
// The trail now shares the .theme-bar row with the color theme toggle:
61-
// the row carries the alignment formula (max-width: var(--width-content)
62-
// + 0.8rem horizontal padding, guarded in tests/unit/theme-css.test.js),
63-
// so .breadcrumbs must not add its own box-model offsets — it grows to
64-
// fill the row's left side, keeping the first crumb's glyph at the menu
65-
// column's left content edge while the toggle pins to the right edge.
85+
// the row is edge to edge like the menu bar (guarded in
86+
// tests/unit/theme-css.test.js), so .breadcrumbs must not add its own
87+
// box-model offsets — it grows to fill the row's left side, keeping the
88+
// first crumb's glyph at the menu bar's left content edge while the
89+
// toggle pins to the right edge.
6690
const body = rules.get('.breadcrumbs');
6791
expect(body).toBeTruthy();
6892
expect(body).toMatch(/flex:\s*1\s+1\s+auto/);

‎tests/unit/theme-css.test.js‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,12 @@ describe('toggle styles', () => {
289289
expect(height).toBeGreaterThanOrEqual(1.5);
290290
});
291291

292-
it('the row below the header mirrors the breadcrumb alignment formula', () => {
293-
// Same invariant layout-styles.test.js pinned for the standalone
294-
// .breadcrumbs bar: the row shares #top-menu's --width-content column
295-
// and the 0.8rem horizontal padding insets its children to the menu
296-
// bar's content edges at every viewport — the toggle on the right, the
292+
it('the row below the header mirrors the menu content column', () => {
293+
// The top menu surface is edge to edge (globals.css overrides mvp.css's
294+
// --width-content cap on #top-menu), but the .menu-bar content inside
295+
// keeps the centered --width-content column; the row must share it so
296+
// its 0.8rem horizontal padding insets its children to the menu bar's
297+
// content edges at every viewport — the toggle on the right, the
297298
// breadcrumb trail on the left (flex-grow), vertically centered.
298299
const row = rules.get('.theme-bar');
299300
expect(row).toBeTruthy();

0 commit comments

Comments
 (0)