feat(ui): F8-09 — turn report sticky icon-popup section menu (#52)
- Replace the 14 rem sticky sidebar (and its mobile <select> twin)
with a single sticky icon-popup trigger pinned to the top-right
corner of the report column. Trigger shows `≡` followed by the
currently active section title (CSS-clamped with text-overflow:
ellipsis so long RU titles cannot bloat the button). Click opens
an anchored popover on desktop and a fixed bottom-sheet on
<768.98 px (mirrors lib/active-view/map-toggles.svelte).
- Each menuitem closes the popover and scrolls the matching
`<section id="report-<slug>">` into view. The scroll is deferred
one animation frame so the surface unmount + restoreFocus's
focus restoration on the (sticky) trigger commit first; otherwise
the focus call could cancel the just-started smooth/instant
scroll under desktop Chromium and WebKit.
- Drop the in-report "Back to map" button — the same affordance
lives in the app-shell view menu (tests/e2e/game-shell.spec.ts
covers it).
- Tighten the report grid to a single flex column so the section
body now occupies the full container width.
- i18n: remove game.report.back_to_map and
game.report.toc.mobile_label; add game.report.toc.open and
game.report.toc.close (mirrors game.map.toggles.open/close).
- Tests: Vitest report-toc.test.ts rewritten for the new icon-popup
contract; Playwright report-sections.spec.ts switches the anchor
loop to trigger → menuitem and adds a mobile bottom-sheet
assertion; game-shell-stubs.test.ts no longer asserts the
back-to-map button on the report orchestrator.
- Docs: ui/docs/report-view.md (TOC + i18n + test seams) and
docs/FUNCTIONAL{,_ru}.md §6.4 updated. The stale SvelteKit
Snapshot reference (the route file was removed by the single-URL
app-shell) is dropped at the same time.
Refs: #52 (#43 umbrella).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+46
-29
@@ -15,7 +15,7 @@ section column.
|
||||
|
||||
```
|
||||
report.svelte
|
||||
├── report/report-toc.svelte // anchor list + mobile <select>
|
||||
├── report/report-toc.svelte // sticky icon-popup section menu
|
||||
├── report/section-galaxy-summary.svelte
|
||||
├── report/section-votes.svelte
|
||||
├── report/section-player-status.svelte
|
||||
@@ -85,27 +85,35 @@ the body — both surfaces stay in sync by construction.
|
||||
|
||||
## Table of contents and active highlight
|
||||
|
||||
`report/report-toc.svelte` renders two surfaces driven by the same
|
||||
entry list:
|
||||
`report/report-toc.svelte` renders a single sticky icon-popup
|
||||
trigger in the top-right corner of the report column. The trigger
|
||||
shows `≡` followed by the title of the currently-active section
|
||||
(CSS-clamped with `text-overflow: ellipsis` so a long RU title
|
||||
cannot bloat the button). Clicking opens an anchored popover
|
||||
(`role="menu"`) that lists every section as a `role="menuitem"`
|
||||
button; the active item gets `aria-current="location"` and the
|
||||
`.active` class. A click on an item closes the popover and scrolls
|
||||
the matching `<section id="report-<slug>">` into view via
|
||||
`scrollIntoView` (with `prefers-reduced-motion` falling back to
|
||||
`behavior: "auto"`).
|
||||
|
||||
- **Desktop / tablet sidebar** — sticky `<aside>` with vertical
|
||||
anchor list. The anchor for the currently-visible section gets
|
||||
`aria-current="location"` and an `.active` CSS class.
|
||||
- **Mobile (< 768 px)** — the desktop sidebar is hidden via CSS
|
||||
and a sticky `<select>` takes its place at the top of the body.
|
||||
Picking an option scrolls the matching section into view. The
|
||||
mobile contract intentionally avoids stacking another overlay on
|
||||
top of the existing layout-owned bottom-tabs.
|
||||
On viewports below `768.98 px` the same surface re-styles into a
|
||||
fixed bottom-sheet anchored above the layout-owned bottom-tabs
|
||||
bar (mirrors `lib/active-view/map-toggles.svelte`), so the same
|
||||
trigger and the same menuitem list serve desktop and mobile.
|
||||
|
||||
Both surfaces also expose a "Back to map" affordance
|
||||
(`report-back-to-map`) at the top.
|
||||
Open/close state matches the `map-toggles.svelte` precedent:
|
||||
Escape closes, outside click closes, item-pick closes. The
|
||||
`restoreFocus` action returns keyboard focus to the trigger on
|
||||
dismount. The menu is non-modal — no focus trap.
|
||||
|
||||
The active slug is computed by an `IntersectionObserver` rooted on
|
||||
the viewport (`root: null`) with `rootMargin: "-30% 0px -60% 0px"`.
|
||||
The skew biases the active band toward the upper third of the
|
||||
visible area so that scrolling down advances the highlight
|
||||
naturally. The observer is created on mount and torn down on
|
||||
unmount.
|
||||
unmount. The TOC itself owns no observer — the orchestrator passes
|
||||
`activeSlug` in as a prop.
|
||||
|
||||
The in-game shell (`lib/game/game-shell.svelte`)
|
||||
expands `<main class="active-view-host">` to fit content rather
|
||||
@@ -129,8 +137,10 @@ highlight stays consistent without a separate source of truth.
|
||||
|
||||
All strings live under `game.report.*`:
|
||||
- `game.report.loading` — section loading placeholder.
|
||||
- `game.report.back_to_map`, `game.report.toc.title`,
|
||||
`game.report.toc.mobile_label` — shell-level strings.
|
||||
- `game.report.toc.title` — `aria-label` on the TOC root;
|
||||
`game.report.toc.open` / `game.report.toc.close` — `aria-label`
|
||||
on the trigger button, swapped by the open state (mirrors
|
||||
`game.map.toggles.open` / `close`).
|
||||
- `game.report.section.<slug>.title` — section heading.
|
||||
- `game.report.section.<slug>.empty` — empty-state copy (where
|
||||
applicable).
|
||||
@@ -151,19 +161,26 @@ couple them silently.
|
||||
shapes: kv-list (`report-section-galaxy-summary.test.ts`), grid
|
||||
with conditional row state (`report-section-bombings.test.ts`),
|
||||
per-race sub-table (`report-section-foreign-sciences.test.ts`),
|
||||
TOC (`report-toc.test.ts`). Each spec mounts the component
|
||||
against a synthetic `RenderedReportSource`, so the orchestrator
|
||||
/ IntersectionObserver are out of scope.
|
||||
TOC (`report-toc.test.ts`). The TOC spec exercises the icon-popup
|
||||
state machine (trigger label, open/close, menuitem list, active
|
||||
highlight, scroll-on-pick, Escape, outside-click). Each spec
|
||||
mounts the component against a synthetic `RenderedReportSource`,
|
||||
so the orchestrator / IntersectionObserver are out of scope.
|
||||
- **Playwright** — `tests/e2e/report-sections.spec.ts` exercises
|
||||
the full integration: every TOC anchor lands its section in
|
||||
view, the back-to-map button switches to the map view
|
||||
(`activeView.select("map")`), and the mobile `<select>` scrolls
|
||||
to the chosen section on a narrow viewport. The spec drives the
|
||||
app-shell through `window.__galaxyNav` (the dev-only nav surface)
|
||||
instead of `page.goto` per-view URLs. The old "scroll position
|
||||
survives a `/map` round-trip via SvelteKit `Snapshot`" case was
|
||||
dropped — see the [scroll position](#scroll-position) note.
|
||||
the full integration: every popover menuitem click lands its
|
||||
section in view and closes the popover, and on the
|
||||
`chromium-mobile` project the same trigger surfaces a fixed
|
||||
bottom-sheet popover instead of the anchored desktop variant.
|
||||
The spec drives the app-shell through `window.__galaxyNav` (the
|
||||
dev-only nav surface) instead of `page.goto` per-view URLs. The
|
||||
old "scroll position survives a `/map` round-trip via SvelteKit
|
||||
`Snapshot`" case was dropped — see the
|
||||
[scroll position](#scroll-position) note. F8-09 also removed the
|
||||
in-report "Back to map" test; the same affordance is exercised
|
||||
through the app-shell view menu by `tests/e2e/game-shell.spec.ts`
|
||||
("header view-menu navigates to every active view").
|
||||
|
||||
Test IDs follow the pattern `report-section-<slug>` for section
|
||||
roots, `report-toc-<slug>` for TOC anchors, and per-section row
|
||||
identifiers (e.g. `report-bombing-row`, `my-planets-row`).
|
||||
roots, `report-toc-trigger` / `report-toc-surface` for the popup
|
||||
shell, `report-toc-item-<slug>` for each menuitem, and per-section
|
||||
row identifiers (e.g. `report-bombing-row`, `my-planets-row`).
|
||||
|
||||
Reference in New Issue
Block a user