97 lines
3.7 KiB
TypeScript
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>
|
|
);
|
|
};
|