refactor(shell): introduce PageShell for the normal-page chrome

Collapse App's hand-assembled normal-page chrome (floating Header +
top-padded wrapper + PageScroller + banners) into a single PageShell
contract with declared fixed vs scrollable regions. No behaviour change;
the channel page still renders its own scroller (dissolved next).

Groundwork for the S4 fixed-chrome/scroll-ownership seam.
This commit is contained in:
2026-07-18 03:15:41 +02:00
parent 91691c150d
commit dc6e62e822
2 changed files with 72 additions and 32 deletions
+42
View File
@@ -0,0 +1,42 @@
import { type ReactNode } from "react";
import PageScroller from "./PageScroller";
// The page shell: the fixed chrome around a single scrollable content region. It formalizes "what
// is fixed vs what scrolls" per module, so the chrome App used to hand-assemble around every page
// (a floating Header + a top-padded wrapper + PageScroller, plus the channel page's separate
// branch that rendered its OWN scroller) collapses to one contract and one scroller.
//
// Regions, top to bottom:
// - `header` — floats (absolute, height `var(--hdr-h)`) over the content column; the wrapper pads
// its top by that height so nothing hides under it. Omit it (channel page) → no pad
// and no fixed top band, so the module's own banner can scroll from the very top.
// - `banners` — in-flow, fixed notices (demo/version) above the scroll region.
// - `toolbar` — in-flow, fixed per-module band (filter chips / sort / counts) that must NOT scroll
// with the content.
// - children — the ONE scrollable region. `PageScroller` owns the scroller and its two
// invariants (`scrollbar-gutter: stable`, edge fade); see that file.
//
// The shell renders a fragment so it drops straight into App's `relative` content column — the
// floating header keeps resolving its `absolute` position against that same ancestor.
export default function PageShell({
header,
banners,
toolbar,
children,
}: {
header?: ReactNode;
banners?: ReactNode;
toolbar?: ReactNode;
children: ReactNode;
}) {
return (
<>
{header}
<div className={`flex-1 min-w-0 min-h-0 flex flex-col ${header ? "pt-[var(--hdr-h)]" : ""}`}>
{banners}
{toolbar}
<PageScroller>{children}</PageScroller>
</div>
</>
);
}