feat(header): floating module header with cyclic nav and shared search

Replace the full-width top bar with a fixed floating header of glass pills: a
fixed-position ModuleName (accent dot + label + prev/next cyclic module stepper)
and a shared SearchBar. The header's left edge is anchored as if the nav rail
and filter sidebar were both open, so it never shifts on collapse or module
change; the right edge leaves a 10% margin.

- ModuleName label width is measured live from the reachable modules' titles in
  the current language, recomputing on a language or account change.
- Module names come from a shared moduleLabelKey (the same short labels as the
  nav rail); NavSidebar and the header now read from one source (lib/modules.ts).
- The arrows step cyclically through moduleOrder(me) — dynamic, no hard-coded
  targets, wrapping at both ends.
- Search is hoisted into the SearchBar for feed (YouTube search), plex, channels
  (including the Discovery tab) and playlists (new); Go button, Enter triggers it.
- The Playlists left rail now reaches the top; header clearance via --hdr-h.
This commit is contained in:
npeter83 2026-07-13 02:28:06 +02:00
parent 36c27250c3
commit 2739f25e6c
17 changed files with 420 additions and 141 deletions

View file

@ -17,10 +17,13 @@ import { useConfirm } from "./ConfirmProvider";
// the channel's metadata — the scheduler pulls its videos on its next run (see backend).
export default function ChannelDiscovery({
canWrite,
search,
onOpenWizard,
onViewChannel,
}: {
canWrite: boolean;
// Client-side name filter from the shared top SearchBar (matches title + handle).
search: string;
onOpenWizard: () => void;
onViewChannel: (id: string, name: string) => void;
}) {
@ -67,7 +70,10 @@ export default function ChannelDiscovery({
if (ok) subscribe.mutate(c);
};
const rows = query.data ?? [];
const q = search.trim().toLowerCase();
const rows = (query.data ?? []).filter(
(c) => !q || `${c.title ?? ""} ${c.handle ?? ""}`.toLowerCase().includes(q)
);
const columns: Column<DiscoveredChannel>[] = [
{