feat(260924-h7x): Dashboard-Reiter als Umschalter in der Kopfzeile
- Kopfzeile bekommt Einhaengepunkt header-center-slot; auf / entfaellt der Text Startseite - DashboardTabs rendert per createPortal dorthin, eigene Zeile ueber dem Raster entfaellt - eingelassene Spur mit erhabenem aktivem Reiter, waagrecht scrollbar mit weicher Randausblendung - role=tablist/tab, Pfeiltasten/Pos1/Ende, sichtbarer Fokusring, aktiver Reiter wird ins Bild gescrollt - Plus-Knopf ausserhalb der Spur, Loeschdialog am Dokumentkoerper, Ziehhinweis als sr-only/Tooltip Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Kennung des Einhaengepunkts in der Mitte der Kopfzeile (quick-260924-h7x).
|
||||
* Die Kopfzeile rendert ein leeres Element mit dieser Kennung; Seiten
|
||||
* zeichnen per `createPortal` hinein (heute: die Dashboard-Reiter).
|
||||
*/
|
||||
export const HEADER_CENTER_SLOT_ID = 'header-center-slot';
|
||||
@@ -150,3 +150,31 @@ describe('Header — Sitzungswaechter (quick-260917-gyd)', () => {
|
||||
).toHaveTextContent('?');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Header — Mitte der Kopfzeile (quick-260924-h7x)', () => {
|
||||
it('auf der Startseite: kein Text „Startseite“, aber der leere Einhaengepunkt fuer die Reiter', async () => {
|
||||
mockPathname = '/';
|
||||
vi.stubGlobal('location', { href: '', pathname: '/', search: '' });
|
||||
fetchSessionState.mockResolvedValue({ status: 'unavailable' });
|
||||
|
||||
await renderHeader();
|
||||
|
||||
expect(screen.queryByText('Startseite')).not.toBeInTheDocument();
|
||||
const slot = document.getElementById('header-center-slot');
|
||||
expect(slot).not.toBeNull();
|
||||
expect(slot).toBeEmptyDOMElement();
|
||||
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: '' });
|
||||
fetchSessionState.mockResolvedValue({ status: 'unavailable' });
|
||||
|
||||
await renderHeader();
|
||||
|
||||
expect(screen.getByText('Startseite')).toBeInTheDocument();
|
||||
expect(document.getElementById('header-center-slot')).not.toBeNull();
|
||||
await waitFor(() => expect(fetchSessionState).toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { fetchSessionState, logout } from '@/lib/auth-actions';
|
||||
import { buildNextParam } from '@/lib/safe-next';
|
||||
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';
|
||||
|
||||
export function Header() {
|
||||
@@ -97,7 +98,7 @@ export function Header() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 flex h-[var(--header-height)] items-center border-b border-border bg-background px-4 shadow-sm">
|
||||
{/* Left: Logo + Mobile hamburger */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex shrink-0 items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobileOpen(true)}
|
||||
@@ -132,15 +133,23 @@ export function Header() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Center: Breadcrumb / page title */}
|
||||
<div className="flex-1 px-4 text-center">
|
||||
<span className="text-sm text-muted-foreground truncate">
|
||||
{tHeader('breadcrumb.home')}
|
||||
</span>
|
||||
{/* Mitte: Einhaengepunkt fuer die Dashboard-Reiter (quick-260924-h7x).
|
||||
`DashboardTabs` rendert per Portal hierher; der Punkt muss deshalb
|
||||
im React-Baum der Kopfzeile LEER bleiben. Auf der Startseite
|
||||
entfaellt der Text „Startseite“, auf allen anderen Seiten bleibt
|
||||
er wie bisher. `min-w-0` laesst die Mitte schrumpfen, damit Logo
|
||||
und Aktionen rechts auch bei schmalem Fenster Platz behalten. */}
|
||||
<div className="flex min-w-0 flex-1 items-center justify-center px-2 text-center md:px-4">
|
||||
<div id={HEADER_CENTER_SLOT_ID} className="flex min-w-0 max-w-full justify-center" />
|
||||
{pathname !== '/' && (
|
||||
<span className="text-sm text-muted-foreground truncate">
|
||||
{tHeader('breadcrumb.home')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<BugReportButton />
|
||||
<ThemeToggle />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user