From c3820a0b446a321441f339ad95c7c2fa45e8d0ca Mon Sep 17 00:00:00 2001 From: npeter83 Date: Fri, 21 Aug 2026 19:58:19 +0200 Subject: [PATCH] refactor(header): remove the ModuleName module-switcher pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user no longer wants the header's "FEED" pill (module label + accent dot + ◀/▶ module-step arrows). Drop it everywhere — the header now starts with the module's SearchBar; module switching stays on the nav rail. Deletes ModuleName.tsx, the now-unused stepModule() in lib/modules, and the Header's module-order/label/setPage plumbing that only fed it. --- frontend/src/components/Header.tsx | 24 +------ frontend/src/components/ModuleName.tsx | 90 -------------------------- frontend/src/lib/modules.ts | 12 ---- 3 files changed, 3 insertions(+), 123 deletions(-) delete mode 100644 frontend/src/components/ModuleName.tsx diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index fd8e822b..efb8e611 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -2,20 +2,17 @@ import { forwardRef, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Youtube } from "lucide-react"; import type { Me } from "../lib/api"; -import { moduleLabelKey, moduleOrder, stepModule } from "../lib/modules"; -import ModuleName from "./ModuleName"; import SearchBar from "./SearchBar"; import PlexSearchBox from "./PlexSearchBox"; import PlexScopeToggle from "./PlexScopeToggle"; import SyncStatus from "./SyncStatus"; -import { useNavigation, useNavigationActions } from "./NavigationProvider"; +import { useNavigation } from "./NavigationProvider"; import { useFeedFilters, useFeedFiltersActions } from "./FeedFiltersProvider"; import { usePlaylists, usePlaylistsActions } from "./PlaylistsProvider"; import { useChannels, useChannelsActions } from "./ChannelsProvider"; -// The floating top header: a row of glass pills over the content — a ModuleName pill (label + -// accent dot + back/forward arrows), the module's SearchBar where it has one, and the per-user -// sync chip at the right end. +// The floating top header: a row of glass pills over the content — the module's SearchBar where it +// has one, and the per-user sync chip at the right end. // `forwardRef` so PageShell can attach a ref to the header's root box and measure its real (possibly // wrapped) height — that measurement + the `--hdr-h` content pad it drives live in PageShell, which // owns both the header slot and the padded content wrapper (E5 S7). @@ -30,7 +27,6 @@ const Header = forwardRef< >(function Header({ me, onYtSearch }, ref) { const { t } = useTranslation(); const { page } = useNavigation(); - const { setPage } = useNavigationActions(); const filters = useFeedFilters(); const { setFilters } = useFeedFiltersActions(); // Each module's search term lives in its own provider (shared with that module's rail/content): @@ -40,12 +36,6 @@ const Header = forwardRef< const { setSearch: setPlaylistsSearch } = usePlaylistsActions(); const { search: channelsSearch } = useChannels(); const { setSearch: setChannelsSearch, goToFullHistory: onGoToFullHistory } = useChannelsActions(); - // The ◀/▶ arrows step cyclically through the user's available modules (derived from `me`, so - // it stays correct as modules are added/gated). Labels of every active module feed ModuleName's - // dynamic width so the pill is sized to the widest reachable title in the current language. - const order = moduleOrder(me); - const moduleLabels = order.map((p) => t(moduleLabelKey[p])); - const multiModule = order.length > 1; // Drop input focus on Go/Enter for the live-filter modules (feed's Go escalates to YouTube). const blur = () => (document.activeElement as HTMLElement | null)?.blur?.(); @@ -101,14 +91,6 @@ const Header = forwardRef< ref={ref} className="pointer-events-none absolute top-3 left-3 right-[10%] z-chrome flex flex-wrap items-center gap-x-3 gap-y-2" > - setPage(stepModule(me, page, -1))} - onNext={() => setPage(stepModule(me, page, 1))} - canPrev={multiModule} - canNext={multiModule} - /> {search} {/* Library scope (movie/show/both) sits right of the search box on Plex — relocated here from the filter sidebar (S3) so the primary "what am I browsing" control is always in reach. */} diff --git a/frontend/src/components/ModuleName.tsx b/frontend/src/components/ModuleName.tsx deleted file mode 100644 index 304e4e53..00000000 --- a/frontend/src/components/ModuleName.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useLayoutEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { ChevronLeft, ChevronRight } from "lucide-react"; - -// The floating module-name pill in the top header. The label area is sized to the WIDEST -// reachable module title (measured live, in the current language only — so dropping a locale or -// gaining/losing a module just changes the input, nothing is hard-coded), which keeps the label -// column a constant width: the name never shifts and the arrows stay put when paging between -// modules. Carries the back/forward arrows that step cyclically through the active modules, plus -// the accent dot (the former PageTitle "eyebrow" look, folded in here). -export default function ModuleName({ - label, - labels, - onPrev, - onNext, - canPrev, - canNext, -}: { - label: string; - // Every reachable module's title in the current language — drives the fixed label width. - labels: string[]; - onPrev: () => void; - onNext: () => void; - canPrev: boolean; - canNext: boolean; -}) { - const { t } = useTranslation(); - const labelRef = useRef(null); - const canvasRef = useRef(null); - const [labelW, setLabelW] = useState(); - - // Measure the widest label using the label span's own rendered font (respects font-scale, - // tracking and the uppercase transform). Re-runs whenever the label set changes — i.e. on a - // language switch or an account switch that changes which modules are reachable. - useLayoutEffect(() => { - const el = labelRef.current; - if (!el || labels.length === 0) return; - const cs = getComputedStyle(el); - const canvas = canvasRef.current ?? (canvasRef.current = document.createElement("canvas")); - const ctx = canvas.getContext("2d"); - if (!ctx) return; - ctx.font = `${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`; - const ls = parseFloat(cs.letterSpacing) || 0; - const upper = cs.textTransform === "uppercase"; - let max = 0; - for (const s of labels) { - const txt = upper ? s.toUpperCase() : s; - const w = ctx.measureText(txt).width + ls * txt.length; - if (w > max) max = w; - } - setLabelW(Math.ceil(max) + 2); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [labels.join("")]); - - const arrow = - "p-1 rounded-lg text-muted transition hover:text-fg hover:bg-card disabled:opacity-25 disabled:pointer-events-none"; - return ( -
- - - - - - - {label} - - -
- ); -} diff --git a/frontend/src/lib/modules.ts b/frontend/src/lib/modules.ts index 9952aa52..5268fa4b 100644 --- a/frontend/src/lib/modules.ts +++ b/frontend/src/lib/modules.ts @@ -145,15 +145,3 @@ export const SYSTEM_PAGES: readonly Page[] = MODULES.filter((m) => m.system).map export function moduleOrder(me: Me): Page[] { return MODULES.filter((m) => !m.gate || m.gate(me)).map((m) => m.id); } - -// Step to the next/previous module cyclically (wraps at both ends). `dir` = +1 forward, -1 back. -// If the current page isn't a primary module (e.g. Settings), forward lands on the first module -// and back on the last. -export function stepModule(me: Me, current: Page, dir: 1 | -1): Page { - const order = moduleOrder(me); - if (order.length === 0) return current; - const i = order.indexOf(current); - // order is non-empty (guarded above) and every index below is in-bounds, so none are undefined. - if (i === -1) return dir === 1 ? order[0]! : order[order.length - 1]!; - return order[(i + dir + order.length) % order.length]!; -}