refactor(frontend): each page fills the floating header itself; the header knows no page

The header used to pick its pills by `page === …` and import five module controls, and Plex's
Discover/Browse toggle needed a context bridge to reach the sub-view that lives in PlexBrowse.
Now the header is an empty band: PageShell hands its root box out as a slot, a page portals its
own pills into it (PageHeader), and the palette's Jump-to pill shows where no page does
(PageHeaderDefault, rendered inside the page's Suspense so a loading page shows no stand-in).
Plex's toggles take the sub-view as a prop; PlexViewBridge and its context are gone, and with
them the header's five layer suppressions.

No visible change: every header pill's box measured identical against the previous build on eight
pages, in Hungarian and English.
This commit is contained in:
2026-10-23 00:23:38 +02:00
parent 0b24f2d8fa
commit 4ef963b005
36 changed files with 530 additions and 316 deletions
@@ -64,6 +64,7 @@ import { useFeedFilters, useFeedFiltersActions } from "../feed";
import { useWizard } from "../../features/wizard/wizardContext";
import { useMe } from "../../lib/useMe";
import { useTags } from "../../lib/useTags";
import ChannelsHeader from "./ChannelsHeader";
export type ChannelsView = "subscribed" | "discovery" | "hidden" | "blocked";
@@ -687,6 +688,7 @@ export default function Channels() {
return (
<>
<ChannelsHeader />
{pageMenuItems.length > 0 && (
<PageMenu ariaLabel={t("common.pageMenu")} items={pageMenuItems} />
)}
@@ -0,0 +1,26 @@
import { useTranslation } from "react-i18next";
import { useMe } from "../../lib/useMe";
import { PageHeader } from "../../components/ui/PageShell";
import SearchBar from "../../features/header/SearchBar";
import { blurActive } from "../../features/header/blurActive";
import { useChannels, useChannelsActions } from "./channelsContext";
import SyncStatus from "./SyncStatus";
// The channel manager's pills in the floating header: the name filter and the per-user sync chip.
export default function ChannelsHeader() {
const { t } = useTranslation();
const me = useMe();
const { search } = useChannels();
const { setSearch, goToFullHistory } = useChannelsActions();
return (
<PageHeader>
<SearchBar
value={search}
placeholder={t("channels.searchPlaceholder")}
onChange={setSearch}
onGo={blurActive}
/>
<SyncStatus isAdmin={me.role === "admin"} onGoToFullHistory={goToFullHistory} />
</PageHeader>
);
}
+3 -1
View File
@@ -1,3 +1,5 @@
// The channel manager's public face: its store, mounted at the root and read app-wide.
// The channel manager's public face: its store, mounted at the root and read app-wide, and the sync
// chip, which the feed's header shows too.
export { ChannelsProvider } from "./ChannelsProvider";
export { useChannels, useChannelsActions } from "./channelsContext";
export { default as SyncStatus } from "./SyncStatus";