Quellcode des ICON CRM (4)
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user