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:
+30
-32
@@ -53,7 +53,7 @@ import NavSidebar from "./components/NavSidebar";
|
|||||||
import Sidebar from "./components/Sidebar";
|
import Sidebar from "./components/Sidebar";
|
||||||
import PlaylistsRail from "./components/PlaylistsRail";
|
import PlaylistsRail from "./components/PlaylistsRail";
|
||||||
import BackToTop from "./components/BackToTop";
|
import BackToTop from "./components/BackToTop";
|
||||||
import PageScroller from "./components/PageScroller";
|
import PageShell from "./components/PageShell";
|
||||||
import GlassTuner from "./components/GlassTuner";
|
import GlassTuner from "./components/GlassTuner";
|
||||||
import ChatDock from "./components/ChatDock";
|
import ChatDock from "./components/ChatDock";
|
||||||
import Toaster from "./components/Toaster";
|
import Toaster from "./components/Toaster";
|
||||||
@@ -715,34 +715,34 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<PageShell
|
||||||
<Header
|
header={
|
||||||
me={meQuery.data!}
|
<Header
|
||||||
filters={filters}
|
me={meQuery.data!}
|
||||||
setFilters={setFilters}
|
filters={filters}
|
||||||
plexQ={plexQ}
|
setFilters={setFilters}
|
||||||
setPlexQ={setPlexQ}
|
plexQ={plexQ}
|
||||||
channelsSearch={channelsSearch}
|
setPlexQ={setPlexQ}
|
||||||
setChannelsSearch={setChannelsSearch}
|
channelsSearch={channelsSearch}
|
||||||
playlistsSearch={playlistsSearch}
|
setChannelsSearch={setChannelsSearch}
|
||||||
setPlaylistsSearch={setPlaylistsSearch}
|
playlistsSearch={playlistsSearch}
|
||||||
onYtSearch={enterYtSearch}
|
setPlaylistsSearch={setPlaylistsSearch}
|
||||||
onGoToFullHistory={() => {
|
onYtSearch={enterYtSearch}
|
||||||
setChannelFilter("needs_full");
|
onGoToFullHistory={() => {
|
||||||
setChannelsView("subscribed"); // the status filter applies to the subscriptions tab
|
setChannelFilter("needs_full");
|
||||||
setChannelsFilterReset((n) => n + 1); // drop any stale column filter hiding the rows
|
setChannelsView("subscribed"); // the status filter applies to the subscriptions tab
|
||||||
setPage("channels");
|
setChannelsFilterReset((n) => n + 1); // drop any stale column filter hiding the rows
|
||||||
}}
|
setPage("channels");
|
||||||
/>
|
}}
|
||||||
{/* The header floats (fixed) over the content, so it no longer occupies flow space; this
|
/>
|
||||||
wrapper clears it with a top pad. (All left rails, incl. Playlists, are App-level
|
}
|
||||||
floating panels beside the nav — none sit inside this content column anymore.) */}
|
banners={
|
||||||
<div
|
<>
|
||||||
className="flex-1 min-w-0 min-h-0 flex flex-col pt-[var(--hdr-h)]"
|
{meQuery.data!.is_demo && <DemoBanner />}
|
||||||
>
|
<VersionBanner onOpen={() => openReleaseNotes(CURRENT_VERSION)} />
|
||||||
{meQuery.data!.is_demo && <DemoBanner />}
|
</>
|
||||||
<VersionBanner onOpen={() => openReleaseNotes(CURRENT_VERSION)} />
|
}
|
||||||
<PageScroller>
|
>
|
||||||
<Suspense fallback={pageFallback}>
|
<Suspense fallback={pageFallback}>
|
||||||
{page === "channels" ? (
|
{page === "channels" ? (
|
||||||
<Channels
|
<Channels
|
||||||
@@ -823,9 +823,7 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</PageScroller>
|
</PageShell>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{/* Toasts rise from the bottom-left, by the notification bell in the nav rail.
|
{/* Toasts rise from the bottom-left, by the notification bell in the nav rail.
|
||||||
Anchored inside the content column so they clear the sidebar automatically
|
Anchored inside the content column so they clear the sidebar automatically
|
||||||
|
|||||||
@@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user