style(web): dunkle App-Leiste und Fluent-Seitenleiste

Kopfzeile 48 px dunkel mit Seitentitel statt immer Startseite,
Dashboard-Reiter mit Akzent-Unterstrich; Seitenleiste mit
Auswahlpille, Modul-Kacheln, aufgeklappten Kategorien und gefuelltem
Suchfeld; eingeklappt nur Kacheln. Einheitlicher PageHeader.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 14:32:37 +02:00
parent 7192be223a
commit b80db49c5e
11 changed files with 389 additions and 150 deletions
+62
View File
@@ -0,0 +1,62 @@
import { create } from 'zustand';
export interface NavModule {
id: string;
slug: string;
name: string;
category: string;
}
interface NavState {
/** Freigegebene Module, wie die Seitenleiste sie geladen hat. */
modules: NavModule[];
setModules: (modules: NavModule[]) => void;
}
/**
* Geteilter Stand der Seitenleiste: die Kopfzeile liest daraus den
* Modulnamen fuer den Seitentitel, ohne selbst noch einmal zu laden.
*/
export const useNavStore = create<NavState>()((set) => ({
modules: [],
setModules: (modules) => set({ modules }),
}));
/** Ersatztitel, solange die Seitenleiste die Module noch nicht geladen hat. */
const MODULE_TITLE_KEYS: Record<string, string> = {
proxmox: 'proxmox.title',
domaincheck: 'domaincheck.title',
'cert-manager': 'certManager.title',
'dkv-fleet': 'dkvFleet.pageTitle',
'tender-radar': 'tenderRadar.page.title',
};
/**
* Seitentitel der Kopfzeile zu einem Pfad. `null` auf der Startseite (dort
* stehen die Dashboard-Reiter), sonst ein Uebersetzungsschluessel oder ein
* fertiger Modulname.
*/
export function resolvePageTitle(
pathname: string,
modules: NavModule[],
): { key: string } | { text: string } | null {
if (pathname === '/') return null;
if (pathname.startsWith('/marketplace')) return { key: 'sidebar.marketplace' };
if (pathname.startsWith('/settings')) return { key: 'sidebar.settings' };
if (pathname.startsWith('/changelog')) return { key: 'sidebar.whatsNew' };
if (pathname.startsWith('/admin')) return { key: 'sidebar.admin' };
if (pathname.startsWith('/change-password')) return { key: 'auth.changePassword.title' };
if (pathname.startsWith('/modules/')) {
const segments = pathname.split('/').filter(Boolean).slice(1);
for (const segment of segments) {
const mod = modules.find((m) => m.slug === segment);
if (mod) return { text: mod.name };
}
for (const segment of segments) {
const key = MODULE_TITLE_KEYS[segment];
if (key) return { key };
}
return { text: '' };
}
return { key: 'header.breadcrumb.home' };
}