fix(header): dynamic --hdr-h so a wrapped mobile header can't overlap content

The floating header pads the content below it by a FIXED `--hdr-h` (4.25rem,
sized for one row). On mobile the flex-wrap header grows to 2–3 rows, so the
content toolbar (sort / count / filter chips) slid up UNDER the header — the top
of the page was a pile of overlapping controls.

- Header.tsx: measure the real header box with a ResizeObserver and publish its
  height (offsetTop + offsetHeight + gap) as `--hdr-h` on the content column, so
  the content pad always tracks the actual (wrapped) header height at any width.
  Verified live at 440 (mobile) and desktop: the toolbar now sits cleanly below
  the header, no overlap; desktop unchanged (still ~4rem, one row).

Also addresses the S7 /code-review findings:
- App.tsx: note that `container-type: inline-size` makes the column a containing
  block for `position: fixed` descendants too (safe today — floating-ui menus
  body-portal, toasts are absolute).
- SearchBar.tsx: move the responsive-rules comment above `return` and note the
  rules are on the SHARED search box (every module, not just Plex).
- SearchBar.dom.test.tsx: lock the container-query classes (min-w floor +
  basis-full/order-last) so the wrap-not-collapse fix can't silently regress.

FE gate: tsc / knip / lint clean, 218 vitest pass.
This commit is contained in:
2026-08-17 21:31:22 +02:00
parent 14913a6447
commit 3575237382
4 changed files with 64 additions and 8 deletions
+6 -5
View File
@@ -55,12 +55,13 @@ export default function SearchBar({
const fire = () => {
if (onGo && !goDisabled) onGo();
};
// Responsive rules on the SHARED search box (every module's header, not just Plex), keyed on the
// content column's width via `@container/appmain` (E5 S7): `min-w-0` by default so it never forces
// overflow on a truly tiny column; once the column has room (≥420px) a `min-w-[15rem]` floor makes
// the box WRAP to its own line via the header's flex-wrap instead of shrinking the input to ~0; on
// a narrow column (≤640px) it also takes a full line of its own (basis-full) and sorts LAST, so the
// small pills (module name + Plex scope toggle / feed sync chip) share the line above it.
return (
// `min-w-0` by default so it never forces overflow on a truly tiny column; once the content
// column (@container/appmain) has room (≥420px) enforce a usable floor so the box WRAPS to its
// own line via the header's flex-wrap instead of shrinking the input to ~0. On a narrow column
// (≤640px) it also takes a full line of its own (basis-full) and sorts LAST, so the small pills
// (module name + Plex scope toggle) share the line above it — a tidy two-row header (E5 S7).
<div className="pointer-events-auto relative flex-1 min-w-0 max-w-xl @min-[420px]/appmain:min-w-[15rem] @max-[640px]/appmain:order-last @max-[640px]/appmain:basis-full">
<div className="h-11 pl-3 pr-2 rounded-2xl glass-menu flex items-center gap-2 focus-within:outline-2 focus-within:outline-accent/60 focus-within:outline-offset-2">
{leading ?? <Search className="w-4 h-4 shrink-0 text-muted" />}