import { type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Youtube } from "lucide-react"; import type { FeedFilters, Me } from "../lib/api"; import { moduleLabelKey, moduleOrder, stepModule } from "../lib/modules"; import ModuleName from "./ModuleName"; import SearchBar from "./SearchBar"; import SyncStatus from "./SyncStatus"; import { useNavigation, useNavigationActions } from "./NavigationProvider"; // 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. export default function Header({ me, filters, setFilters, plexQ, setPlexQ, channelsSearch, setChannelsSearch, playlistsSearch, setPlaylistsSearch, onYtSearch, onGoToFullHistory, }: { me: Me; filters: FeedFilters; setFilters: (f: FeedFilters) => void; // Plex has its own ephemeral search term (see App) — the box is shared UI, the state is not. plexQ: string; setPlexQ: (q: string) => void; channelsSearch: string; setChannelsSearch: (q: string) => void; playlistsSearch: string; setPlaylistsSearch: (q: string) => void; // Trigger a live YouTube search for the current term (feed only; hidden for the demo account, // which can't spend the shared quota). onYtSearch: (q: string) => void; // Jump to the channel manager filtered to channels still missing full history. onGoToFullHistory: () => void; }) { const { t } = useTranslation(); const { page } = useNavigation(); const { setPage } = useNavigationActions(); // 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?.(); // Per-page SearchBar wiring — null for modules without a search. let search: ReactNode = null; if (page === "feed") { const trimmed = filters.q.trim(); search = ( { // When a search first appears, rank the feed by relevance — set atomically with the // query (race-free vs. per-keystroke updates). Only overrides the default "newest" // sort; a custom sort the user chose is left alone. Cleared in Feed. const startSearch = !filters.q.trim() && !!q.trim() && filters.sort === "newest"; setFilters({ ...filters, q, ...(startSearch ? { sort: "relevance" } : {}) }); }} onGo={() => onYtSearch(trimmed)} goLabel={t("header.searchYoutube")} goTitle={t("header.searchYoutubeTip")} goIcon={} goDisabled={!trimmed || me.is_demo} /> ); } else if (page === "plex" && me.plex_enabled) { search = ( ); } else if (page === "channels") { search = ( ); } else if (page === "playlists") { search = ( ); } return ( // Absolutely positioned within the (relative) content column, so its left edge tracks the // actual left zone: it sits just inside the content, reclaiming the space when the nav rail is // slim and/or no filter sidebar is present — no hard-coded offset.
setPage(stepModule(me, page, -1))} onNext={() => setPage(stepModule(me, page, 1))} canPrev={multiModule} canNext={multiModule} /> {search} {/* Per-user sync status — it used to cost the nav rail a whole block. It sits with the other pills rather than pinned to the far right: on a wide window that just strands it. Only on the two modules it says anything about: the feed those subscriptions fill, and the manager where you curate them. It's noise on Plex (a different source entirely), playlists, etc. */} {(page === "feed" || page === "channels") && ( )}
); }