Files
ICON-CRM/src/components/Header.tsx
T
2026-09-14 16:55:36 +02:00

97 lines
3.7 KiB
TypeScript

import React from 'react';
import { THEME_PALETTES, THEME_PALETTE_LABELS, type View } from '../types';
import { GlobalSearch } from './GlobalSearch';
import { useTheme } from '../lib/theme';
interface HeaderProps {
view: View;
onToggleSidebar: () => void;
}
const SUN =
'M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10Zm0-5v3m0 14v3M2 12h3m14 0h3M4.2 4.2l2.1 2.1m11.4 11.4 2.1 2.1M19.8 4.2l-2.1 2.1M6.3 17.7l-2.1 2.1';
const MOON = 'M21 13a9 9 0 1 1-10-10 7 7 0 0 0 10 10Z';
export const Header: React.FC<HeaderProps> = ({ view, onToggleSidebar }) => {
const { theme, setTheme, mode, toggleMode, resetMode, followsSystem } = useTheme();
const isDark = mode === 'dark';
/*
* Sicherheitsnetz: kennt die Liste den Namen nicht, darf der Kopf trotzdem
* nicht mit einer TypeError-Ausnahme aussteigen - er trägt die ganze
* Anwendung. Ein entfernter Wert hat genau das schon einmal getan.
*/
const label = THEME_PALETTE_LABELS[theme] ?? THEME_PALETTE_LABELS.icon;
return (
<header className="app-header flex h-16 shrink-0 items-center gap-3 border-b border-ink-200 bg-surface px-4">
<button
type="button"
onClick={onToggleSidebar}
aria-label="Menü ein- und ausklappen"
className="rounded-lg p-2 text-ink-500 hover:bg-ink-100"
>
<svg viewBox="0 0 24 24" className="h-5 w-5" fill="currentColor" aria-hidden="true">
<path d="M3 6h18v2H3V6Zm0 5h18v2H3v-2Zm0 5h18v2H3v-2Z" />
</svg>
</button>
<h1 className="hidden shrink-0 text-lg font-bold text-ink-900 md:block">{view}</h1>
<div className="flex flex-1 justify-center">
<GlobalSearch />
</div>
{/*
Farbwelt. Ein Auswahlfeld und keine Knopfreihe: es kommen weitere
Designs dazu, und drei bis fünf Knöpfe im Kopf würden die Suche
verdrängen. Die Wahl gilt für den angemeldeten Benutzer - sie steht an
seinem Konto, nicht nur im Browser.
*/}
<label className="sr-only" htmlFor="theme-palette">
Design
</label>
<select
id="theme-palette"
value={THEME_PALETTE_LABELS[theme] ? theme : 'icon'}
onChange={(event) => setTheme(event.target.value as typeof theme)}
title={`Design: ${label.hint}`}
className="rounded-lg border border-ink-200 bg-surface px-2.5 py-2 text-xs font-semibold text-ink-600 outline-none hover:border-brand-300 hover:text-brand-700"
>
{THEME_PALETTES.map((option) => (
<option key={option} value={option}>
{THEME_PALETTE_LABELS[option].name}
</option>
))}
</select>
{/* Doppelklick löst die manuelle Wahl und folgt wieder dem System. */}
<button
type="button"
onClick={toggleMode}
onDoubleClick={resetMode}
aria-pressed={isDark}
aria-label={`${isDark ? 'Helles' : 'Dunkles'} Design einschalten`}
title={`${isDark ? 'Helles' : 'Dunkles'} Design einschalten${
followsSystem ? ' (folgt derzeit dem System)' : ' · Doppelklick: wieder dem System folgen'
}`}
className="flex items-center gap-2 rounded-lg border border-ink-200 px-2.5 py-2 text-xs font-semibold text-ink-600 hover:border-brand-300 hover:text-brand-700"
>
<svg
viewBox="0 0 24 24"
className="h-4 w-4"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d={isDark ? SUN : MOON} />
</svg>
<span className="hidden sm:inline">{isDark ? 'Hell' : 'Dunkel'}</span>
</button>
</header>
);
};