import { forwardRef, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Youtube } from "lucide-react"; import type { Me } from "../lib/api"; import SearchBar from "./SearchBar"; import PlexSearchBox from "./PlexSearchBox"; import PlexScopeToggle from "./PlexScopeToggle"; import SyncStatus from "./SyncStatus"; 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 — 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). const Header = forwardRef< HTMLDivElement, { me: Me; // 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; } >(function Header({ me, onYtSearch }, ref) { const { t } = useTranslation(); const { page } = useNavigation(); const filters = useFeedFilters(); const { setFilters } = useFeedFiltersActions(); // Each module's search term lives in its own provider (shared with that module's rail/content): // playlists name filter, Plex search term, channels name filter. The channel sync chip's // "go to full history" jump is a ChannelsProvider action. const { search: playlistsSearch } = usePlaylists(); const { setSearch: setPlaylistsSearch } = usePlaylistsActions(); const { search: channelsSearch } = useChannels(); const { setSearch: setChannelsSearch, goToFullHistory: onGoToFullHistory } = useChannelsActions(); // 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.
{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. */} {page === "plex" && me.plex_enabled && } {/* 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") && ( )}
); }); export default Header;