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:
parent
36c27250c3
commit
2739f25e6c
17 changed files with 420 additions and 141 deletions
50
frontend/src/lib/modules.ts
Normal file
50
frontend/src/lib/modules.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import type { Me } from "./api";
|
||||
import type { Page } from "./urlState";
|
||||
|
||||
// Admin/system modules — rendered in their own section (below a divider) in the nav rail.
|
||||
export const SYSTEM_PAGES: readonly Page[] = ["scheduler", "config", "users", "audit"];
|
||||
|
||||
// The i18n key for each module's SHORT display name — the same label the nav rail shows. Single
|
||||
// source so the nav rail and the header's ModuleName pill always read identically (the longer,
|
||||
// descriptive `pageTitleKey` names are kept only for the browser tab title).
|
||||
export const moduleLabelKey: Record<Page, string> = {
|
||||
feed: "header.account.feed",
|
||||
channels: "header.account.channels",
|
||||
playlists: "header.account.playlists",
|
||||
plex: "plex.navLabel",
|
||||
notifications: "inbox.navLabel",
|
||||
messages: "messages.navLabel",
|
||||
downloads: "downloads.navLabel",
|
||||
stats: "header.account.stats",
|
||||
scheduler: "header.account.scheduler",
|
||||
config: "header.account.config",
|
||||
users: "header.account.users",
|
||||
audit: "header.account.audit",
|
||||
settings: "header.account.settings",
|
||||
};
|
||||
|
||||
// The ordered list of primary module pages available to THIS user, in nav-rail order. Single
|
||||
// source of truth for both the nav rail (NavSidebar) and the header's ◀/▶ module stepper, so a
|
||||
// new module (or a newly-gated one) shows up in both at once — nothing hard-codes the sequence.
|
||||
// Availability is derived from the account (plex toggle, demo restrictions, admin role), so the
|
||||
// reachable set changes with the language-independent account state, not the UI.
|
||||
export function moduleOrder(me: Me): Page[] {
|
||||
const pages: Page[] = ["feed", "channels", "playlists"];
|
||||
if (me.plex_enabled) pages.push("plex");
|
||||
pages.push("notifications");
|
||||
if (!me.is_demo) pages.push("messages", "downloads");
|
||||
pages.push("stats");
|
||||
if (me.role === "admin") pages.push(...SYSTEM_PAGES);
|
||||
return pages;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (i === -1) return dir === 1 ? order[0] : order[order.length - 1];
|
||||
return order[(i + dir + order.length) % order.length];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue