|
2 | 2 |
|
3 | 3 | ## Goals |
4 | 4 |
|
5 | | -1. Three-mode theme control in the top menu bar — **light / dark / system** — |
6 | | - with small images (icons) for each mode. |
7 | | -2. The control's right edge aligns with the top menu's right content edge at |
| 5 | +1. Three-mode theme control — **light / dark / system** — with small images |
| 6 | + (icons) for each mode, on a sub-row below the top menu bar. |
| 7 | +2. The control's right edge aligns with the menu bar's right content edge at |
8 | 8 | every viewport (mirroring how the breadcrumb trail aligns with the menu's |
9 | 9 | left edge). |
10 | 10 | 3. All *theming* is pure CSS: custom properties + `prefers-color-scheme` + |
|
18 | 18 | `components/theme-toggle.jsx` (a Server Component — no state, no handlers) |
19 | 19 | renders three radios named `rf-theme` (`rf-theme-light`, `rf-theme-dark`, |
20 | 20 | `rf-theme-system`, with `system` `defaultChecked`), each wrapped in a labelled |
21 | | -icon pill (inline SVG: sun / moon / monitor). The toggle is the **last flex |
22 | | -child of `.menu-bar`**, after `MenuSearch`. |
| 21 | +icon pill (inline SVG: sun / moon / monitor). The toggle sits on the |
| 22 | +**`.theme-bar` sub-row under `.menu-bar`**, inside `#top-menu`, right-aligned |
| 23 | +with the menu bar's content edge. |
23 | 24 |
|
24 | 25 | Why radios: "one of three modes" is exactly a radio group, the checked state |
25 | 26 | is queryable from CSS (`:has(#id:checked)`), and switching requires zero JS. |
@@ -121,12 +122,15 @@ practices" and "only CSS" conflict here, so the plan resolves it as: |
121 | 122 |
|
122 | 123 | ### Alignment with the menu |
123 | 124 |
|
124 | | -The toggle is a normal flex child at the end of `.menu-bar` (which carries |
125 | | -`padding: 0.3rem 0.8rem`), so its right edge is flush with the menu's content |
126 | | -edge by construction — the same 0.8rem inset the breadcrumbs mirror on the |
127 | | -left (`tests/unit/layout-styles.test.js` pins that invariant). Below 900px it |
128 | | -keeps `order: 2` so it sits beside the hamburger at the right edge. No |
129 | | -absolute positioning, no magic numbers. |
| 125 | +The toggle lives on a `.theme-bar` sub-row inside `#top-menu`, right after |
| 126 | +`.menu-bar`, and pinned right via `justify-content: flex-end`. The row |
| 127 | +reuses the breadcrumbs' exact alignment formula — `max-width: |
| 128 | +var(--width-content)`, `box-sizing: border-box`, `margin: 0 auto`, |
| 129 | +`padding: 0.2rem 0.8rem 0.45rem` (see `tests/unit/layout-styles.test.js` for |
| 130 | +the breadcrumb invariant this mirrors) — so its right content edge is the |
| 131 | +menu bar's right content edge at every viewport, with no positioning tricks |
| 132 | +or magic numbers; the formula works identically at mobile widths, where the |
| 133 | +sub-row simply follows the wrapped menu bar. |
130 | 134 |
|
131 | 135 | ### Toggle UI / a11y |
132 | 136 |
|
@@ -188,10 +192,10 @@ mustache-rendered). |
188 | 192 |
|
189 | 193 | 1. Write the failing unit/integration tests (above). |
190 | 194 | 2. `components/theme-toggle.jsx` + render it in `components/site-header.jsx` |
191 | | - after `MenuSearch`. |
| 195 | + on a `.theme-bar` sub-row after `.menu-bar`. |
192 | 196 | 3. globals.css: split color roles, sweep hardcoded colors into variables, add |
193 | 197 | the three cascade blocks + mvp variable overrides + dark overrides for the |
194 | | - report template's light-only rules + toggle styles (+ mobile order). |
| 198 | + report template's light-only rules + `.theme-bar`/toggle styles. |
195 | 199 | 4. `app/layout.jsx`: inline persistence script after `<SiteHeader />` + |
196 | 200 | `color-scheme` meta. |
197 | 201 | 5. Unit/integration green; `npx eslint` clean. |
@@ -250,11 +254,11 @@ mustache-rendered). |
250 | 254 | were pre-verified (dark bg/text ≈13:1, muted ≈8:1, links ≈7.7:1, focus |
251 | 255 | indicator 3.8:1 on the page bg (3.3:1 on surfaces), brand-bg+white 5.5:1, |
252 | 256 | error/success ≈8:1). |
253 | | -10. **Alignment is structural, not geometric** — placing the toggle inside |
254 | | - `.menu-bar` (like the search before it) guarantees right-edge alignment |
255 | | - at every viewport with zero positioning code; the E2E test pins it with a |
256 | | - bounding-box comparison. This mirrors (and is simpler than) the |
257 | | - breadcrumbs' `--width-content` trick, which only aligns below ~1263px. |
| 257 | +10. **Alignment is structural, not geometric** — the `.theme-bar` sub-row |
| 258 | + reuses the breadcrumbs' `--width-content` column + 0.8rem padding formula, |
| 259 | + so the toggle's right edge is the menu bar's right content edge at every |
| 260 | + viewport with zero positioning code; the E2E test pins it with a |
| 261 | + bounding-box comparison, at desktop and mobile widths. |
258 | 262 |
|
259 | 263 | Verdict: the design is sound. The only genuine trade-offs — the persistence |
260 | 264 | shim (flagged deviation) and the duplicated dark block (test-guarded) — are |
|
0 commit comments