63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
// Layout-Shell mit Navigation — SHL-01 Akzeptanzkriterium 1.
|
|
// Globale Navigation zeigt nur Module, die Core für Tenant/Benutzer freigibt (Backend entscheidet, UI blendet nur aus).
|
|
|
|
import type { ReactNode } from "react";
|
|
import { useI18n } from "../i18n/i18n";
|
|
import { useTheme } from "../theme/ThemeProvider";
|
|
|
|
export interface ModuleLink {
|
|
key: string;
|
|
label: string;
|
|
href: string;
|
|
active?: boolean;
|
|
}
|
|
|
|
export interface ShellProps {
|
|
modules: ModuleLink[];
|
|
tenantLabel: string;
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function Shell({ modules, tenantLabel, children }: ShellProps) {
|
|
const { scheme, toggle } = useTheme();
|
|
const { t } = useI18n();
|
|
|
|
return (
|
|
<div className="shl-shell">
|
|
<a className="shl-skip-link" href="#shl-main-content">
|
|
{t("shl.shell.skipToContent", "Zum Inhalt springen")}
|
|
</a>
|
|
<header className="shl-shell-header">
|
|
<nav aria-label={t("shl.shell.moduleNav", "Modul-Navigation")}>
|
|
<ul>
|
|
{modules.map((mod) => (
|
|
<li key={mod.key}>
|
|
<a href={mod.href} aria-current={mod.active ? "page" : undefined}>
|
|
{mod.label}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
<div className="shl-shell-context">
|
|
<span className="shl-tenant-context">{tenantLabel}</span>
|
|
<button
|
|
type="button"
|
|
onClick={toggle}
|
|
aria-label={
|
|
scheme === "light" ? t("shl.theme.toggleToDark") : t("shl.theme.toggleToLight")
|
|
}
|
|
>
|
|
{scheme === "light" ? "🌙" : "☀️"}
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<main id="shl-main-content" className="shl-shell-content" tabIndex={-1}>
|
|
{children}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|