feat(design): the NavRail primitive with optional short labels, the flat surfaces onto roles (E28 S3d3, B7/B5)

This commit is contained in:
2026-09-25 00:47:46 +02:00
parent 6354454cc9
commit 7fa8f4ebde
20 changed files with 342 additions and 101 deletions
+52 -6
View File
@@ -28,6 +28,10 @@ export type ModuleDef = {
id: Page;
// i18n key for the SHORT display name — the same label the nav rail and the header pill show.
labelKey: string;
// The SHORT name under the icon when the user turns the rail labels on (B7): the rail is 3.75rem
// wide with labels or without, so it has to fit there ("Alerts", "Listák"). The full `labelKey`
// stays the tooltip and the accessible name.
railKey: string;
// i18n key for the LONGER, descriptive browser-tab title (e.g. "Channel manager" vs the rail's
// "Channels"). Absent → the tab title falls back to `labelKey`. Read via `pageTitleKey`.
titleKey?: string;
@@ -51,20 +55,45 @@ const MODULES: readonly ModuleDef[] = [
// Home leads: it is the landing (see navigationInit.loadInitialPage), and the module order IS the
// nav order and the header stepper's sequence. The feed takes the YouTube mark its own toolbar
// already uses, so the two are apart by shape and not just by position.
{ id: "home", labelKey: "home.navLabel", titleKey: "home.title", icon: Home },
{ id: "feed", labelKey: "header.account.feed", icon: Youtube },
{
id: "home",
labelKey: "home.navLabel",
railKey: "nav.rail.home",
titleKey: "home.title",
icon: Home,
},
{ id: "feed", labelKey: "header.account.feed", railKey: "nav.rail.feed", icon: Youtube },
{
id: "channels",
labelKey: "header.account.channels",
railKey: "nav.rail.channels",
titleKey: "header.channelManager",
icon: Tv,
},
{ id: "playlists", labelKey: "header.account.playlists", icon: ListVideo },
{ id: "plex", labelKey: "plex.navLabel", icon: Clapperboard, gate: (me) => me.plex_enabled },
{ id: "notifications", labelKey: "inbox.navLabel", icon: Bell, hasBadge: true },
{
id: "playlists",
labelKey: "header.account.playlists",
railKey: "nav.rail.playlists",
icon: ListVideo,
},
{
id: "plex",
labelKey: "plex.navLabel",
railKey: "nav.rail.plex",
icon: Clapperboard,
gate: (me) => me.plex_enabled,
},
{
id: "notifications",
labelKey: "inbox.navLabel",
railKey: "nav.rail.notifications",
icon: Bell,
hasBadge: true,
},
{
id: "messages",
labelKey: "messages.navLabel",
railKey: "nav.rail.messages",
icon: MessageSquare,
hasBadge: true,
gate: notDemo,
@@ -72,14 +101,22 @@ const MODULES: readonly ModuleDef[] = [
{
id: "downloads",
labelKey: "downloads.navLabel",
railKey: "nav.rail.downloads",
icon: Download,
hasBadge: true,
gate: notDemo,
},
{ id: "stats", labelKey: "header.account.stats", titleKey: "header.usageStats", icon: BarChart3 },
{
id: "stats",
labelKey: "header.account.stats",
railKey: "nav.rail.stats",
titleKey: "header.usageStats",
icon: BarChart3,
},
{
id: "scheduler",
labelKey: "header.account.scheduler",
railKey: "nav.rail.scheduler",
titleKey: "header.scheduler",
icon: Activity,
system: true,
@@ -88,6 +125,7 @@ const MODULES: readonly ModuleDef[] = [
{
id: "config",
labelKey: "header.account.config",
railKey: "nav.rail.config",
titleKey: "header.configuration",
icon: SlidersHorizontal,
system: true,
@@ -96,6 +134,7 @@ const MODULES: readonly ModuleDef[] = [
{
id: "users",
labelKey: "header.account.users",
railKey: "nav.rail.users",
titleKey: "header.users",
icon: Users,
system: true,
@@ -104,6 +143,7 @@ const MODULES: readonly ModuleDef[] = [
{
id: "audit",
labelKey: "header.account.audit",
railKey: "nav.rail.audit",
titleKey: "audit.title",
icon: ScrollText,
system: true,
@@ -112,6 +152,7 @@ const MODULES: readonly ModuleDef[] = [
{
id: "logs",
labelKey: "header.account.logs",
railKey: "nav.rail.logs",
titleKey: "adminLogs.title",
icon: FileSearch,
system: true,
@@ -124,6 +165,7 @@ const MODULES: readonly ModuleDef[] = [
const SETTINGS_MODULE: ModuleDef = {
id: "settings",
labelKey: "header.account.settings",
railKey: "nav.rail.settings",
titleKey: "settings.title",
icon: Settings,
};
@@ -149,6 +191,10 @@ export const moduleLabelKey: Record<Page, string> = Object.fromEntries(
[...MODULES, SETTINGS_MODULE].map((m) => [m.id, m.labelKey]),
) as Record<Page, string>;
export const moduleRailKey: Record<Page, string> = Object.fromEntries(
[...MODULES, SETTINGS_MODULE].map((m) => [m.id, m.railKey]),
) as Record<Page, string>;
// Admin/system modules — rendered in their own section (below a divider) in the nav rail.
export const SYSTEM_PAGES: readonly Page[] = MODULES.filter((m) => m.system).map((m) => m.id);