ReviewOS

also looking at this

stacks/analyticshq

fix(ui): bound the mobile menu to the visible viewport

#53
Merged glennmichael123 wants to merge fix/mobile-nav into main
2 files +26 -0
public/marketing.cssmodified+4-0
Changes to public/marketing.css
@@ -186,6 +186,9 @@
186186 gap: 0.25rem;
187187 min-width: 232px;
188188 padding: 0.6rem;
189 max-height: calc(100dvh - 92px);
190 overflow-y: auto;
191 overscroll-behavior: contain;
189192 background: var(--surface-2);
190193 border: 1px solid var(--line-strong);
191194 border-radius: var(--r);
@@ -695,6 +698,7 @@
695698 .footer-brand { grid-column: 1 / -1; }
696699 }
697700 @media (max-width: 860px) {
701 body:has(.nav-menu[open]) { overflow: hidden; }
698702 .nav-links { display: none; }
699703 .nav-cta { display: none; }
700704 .nav-menu { display: block; }
tests/unit/mobile-layout.test.tsadded+22-0
Changes to tests/unit/mobile-layout.test.ts
@@ -0,0 +1,22 @@
1import { describe, expect, test } from 'bun:test'
2import { readFileSync } from 'node:fs'
3import { join } from 'node:path'
4
5const ROOT = join(import.meta.dir, '../..')
6const marketing = readFileSync(join(ROOT, 'public/marketing.css'), 'utf8')
7
8// The phone menu lists every marketing destination in one column, which is
9// taller than an 812px viewport. The panel has to scroll inside the viewport
10// and the page behind it has to stay put, or the last links are unreachable.
11describe('mobile layout contracts', () => {
12 test('marketing menu is bounded by the visible mobile viewport', () => {
13 const panel = marketing.match(/\.nav-menu-panel\s*\{[^}]*\}/)?.[0] ?? ''
14 expect(panel).toContain('max-height: calc(100dvh - 92px)')
15 expect(panel).toContain('overflow-y: auto')
16 expect(panel).toContain('overscroll-behavior: contain')
17 })
18
19 test('opening the marketing menu locks the page behind it', () => {
20 expect(marketing).toContain('body:has(.nav-menu[open]) { overflow: hidden; }')
21 })
22})