diff --git a/apps/web/src/components/brand/tessera-logo.tsx b/apps/web/src/components/brand/tessera-logo.tsx index db8519f..e27a175 100644 --- a/apps/web/src/components/brand/tessera-logo.tsx +++ b/apps/web/src/components/brand/tessera-logo.tsx @@ -20,9 +20,10 @@ export interface TesseraLogoProps { * Schaltet die halbtransparente helle Kontur der Grundplatte im dunklen * Erscheinungsbild ein. Auf Flaechen, die unabhaengig vom * Erscheinungsbild immer hell sind (etwa das gelbe Anmelde-Panel), muss - * dies auf `false` gesetzt werden. Vorgabe: `true`. + * dies auf `false` gesetzt werden; auf immer dunklen Flaechen (App-Leiste, + * Anmelde-Panel) auf `'always'`. Vorgabe: `true`. */ - plateOutline?: boolean; + plateOutline?: boolean | 'always'; /** Zusaetzliche Klassen fuer das aeusserste Element der gewaehlten Variante. */ className?: string; /** Zusaetzliche Klassen fuer den Schriftzug (nur `horizontal`). */ @@ -38,7 +39,7 @@ function LogoMark({ ariaLabel, }: { size: number; - plateOutline: boolean; + plateOutline: boolean | 'always'; className?: string; ariaLabel: string; }) { @@ -61,9 +62,11 @@ function LogoMark({ fill={BRAND_PLATE} strokeWidth={2} className={ - plateOutline - ? 'stroke-transparent dark:stroke-white/25' - : 'stroke-transparent' + plateOutline === 'always' + ? 'stroke-white/20' + : plateOutline + ? 'stroke-transparent dark:stroke-white/25' + : 'stroke-transparent' } /> {/* diff --git a/apps/web/src/components/bug-report/bug-report-button.tsx b/apps/web/src/components/bug-report/bug-report-button.tsx index b9cc8d8..9901366 100644 --- a/apps/web/src/components/bug-report/bug-report-button.tsx +++ b/apps/web/src/components/bug-report/bug-report-button.tsx @@ -34,7 +34,7 @@ export function BugReportButton() { diff --git a/apps/web/src/components/layout/header.test.tsx b/apps/web/src/components/layout/header.test.tsx index 675d951..e99e1e3 100644 --- a/apps/web/src/components/layout/header.test.tsx +++ b/apps/web/src/components/layout/header.test.tsx @@ -1,6 +1,7 @@ import { cleanup, render, screen, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { useAuthStore } from '@/lib/stores/auth-store'; +import { useNavStore } from '@/lib/stores/nav-store'; /** * header.test — Sitzungswaechter im Header (quick-260917-gyd). @@ -166,15 +167,35 @@ describe('Header — Mitte der Kopfzeile (quick-260924-h7x)', () => { await waitFor(() => expect(fetchSessionState).toHaveBeenCalled()); }); - it('auf anderen Seiten: „Startseite“ bleibt wie bisher', async () => { - mockPathname = '/modules/proxmox'; - vi.stubGlobal('location', { href: '', pathname: '/modules/proxmox', search: '' }); + it('auf anderen Seiten: Name der aktuellen Seite statt „Startseite“ (Design „Mosaik“)', async () => { + mockPathname = '/marketplace'; + vi.stubGlobal('location', { href: '', pathname: '/marketplace', search: '' }); fetchSessionState.mockResolvedValue({ status: 'unavailable' }); await renderHeader(); - expect(screen.getByText('Startseite')).toBeInTheDocument(); + expect(screen.getByText('Marktplatz')).toBeInTheDocument(); + expect(screen.queryByText('Startseite')).not.toBeInTheDocument(); expect(document.getElementById('header-center-slot')).not.toBeNull(); await waitFor(() => expect(fetchSessionState).toHaveBeenCalled()); }); + + it('auf Modulseiten: Modulname aus der Seitenleiste, sonst der Ersatztitel', async () => { + mockPathname = '/modules/infrastructure/proxmox'; + vi.stubGlobal('location', { href: '', pathname: mockPathname, search: '' }); + fetchSessionState.mockResolvedValue({ status: 'unavailable' }); + + await renderHeader(); + // Noch keine Module geladen: Ersatztitel aus den Uebersetzungen. + expect(screen.getByText('Proxmox')).toBeInTheDocument(); + + cleanup(); + useNavStore.getState().setModules([ + { id: 'm1', slug: 'proxmox', name: 'Proxmox-Monitor', category: 'infrastructure' }, + ]); + await renderHeader(); + expect(screen.getByText('Proxmox-Monitor')).toBeInTheDocument(); + useNavStore.getState().setModules([]); + await waitFor(() => expect(fetchSessionState).toHaveBeenCalled()); + }); }); diff --git a/apps/web/src/components/layout/header.tsx b/apps/web/src/components/layout/header.tsx index 145f605..70bfdaf 100644 --- a/apps/web/src/components/layout/header.tsx +++ b/apps/web/src/components/layout/header.tsx @@ -12,6 +12,7 @@ import { ThemeToggle } from '@/components/theme-toggle'; import { BugReportButton } from '@/components/bug-report/bug-report-button'; import { HEADER_CENTER_SLOT_ID } from '@/components/layout/header-slot'; import { usePathname } from 'next/navigation'; +import { resolvePageTitle, useNavStore } from '@/lib/stores/nav-store'; export function Header() { const t = useTranslations(); @@ -95,14 +96,25 @@ export function Header() { const roleLabel = user ? tHeader(`role.${user.role}`) : ''; + // Seitentitel in der Mitte (Design „Mosaik“): statt immer „Startseite“ + // der Name der aktuellen Seite bzw. des Moduls. + const navModules = useNavStore((s) => s.modules); + const pageTitle = resolvePageTitle(pathname, navModules); + const pageTitleText = pageTitle + ? 'key' in pageTitle + ? t(pageTitle.key) + : pageTitle.text + : null; + return ( -
- {/* Left: Logo + Mobile hamburger */} -
+
+ {/* Links: Menue (mobil) + Bildmarke. Dunkle App-Leiste wie bei GitHub + bzw. Microsoft 365 — in beiden Erscheinungsbildern dunkel. */} +
- + + +
{/* Mitte: Einhaengepunkt fuer die Dashboard-Reiter (quick-260924-h7x). @@ -143,15 +159,15 @@ export function Header() { und Aktionen rechts auch bei schmalem Fenster Platz behalten. */}
- {pathname !== '/' && ( - - {tHeader('breadcrumb.home')} + {pageTitleText !== null && ( + + {pageTitleText} )}
{/* Right: Actions */} -
+
@@ -160,7 +176,7 @@ export function Header() { - {openCategories.has(category) && ( -
    - {mods.map((mod) => ( -
  • - - {mod.name} - -
  • - ))} + {open && ( +
      + {mods.map((mod) => { + const href = `/modules/${category}/${mod.slug}`; + const active = isActive(href) || isActive(`/modules/${mod.slug}`); + return ( +
    • + + {active && } + + {mod.name} + +
    • + ); + })}
    )} - ))} -
- )} -
+ ); + })} + + )}
)} -
+
@@ -210,7 +258,7 @@ export function Sidebar() { {/* Versionszeile (quick-260914-ku1): bewusst ohne `hidden md:block`, damit auch die mobile Schublade sie zeigt; eingeklappt nichts (Muster oben). */} {!isCollapsed && ( -
+
)} @@ -220,7 +268,7 @@ export function Sidebar() { return ( <>