big upgrade

This commit is contained in:
knso committed 2026-05-10 12:19:58 +02:00
1 parent 5bd2019fc6
commit f7dd547f8d
38 files changed
+2363 -329

No files matched your search

@@ -0,0 +1,263 @@
# Design-Dokument – System-Theme-Erkennung (Light/Dark Mode)
## Übersicht
Dieses Feature integriert die bereits installierte Bibliothek `next-themes` (v0.3.0) in die bestehende React + Vite + Tailwind + shadcn/ui-Webseite, um eine automatische Erkennung des Betriebssystem-Themes (hell/dunkel) sowie manuelles Umschalten zu ermöglichen.
Aktuell definiert `:root` in `src/index.css` das dunkle Farbschema. Es existiert zwar ein `.dark`-Block, dieser wird aber nicht aktiv genutzt. Die Tailwind-Konfiguration verwendet bereits `darkMode: ["class"]`, was perfekt mit `next-themes` zusammenarbeitet.
Kernentscheidungen:
- `next-themes` wird als ThemeProvider eingesetzt (bereits als Dependency vorhanden, `attribute: "class"` für Tailwind-Kompatibilität)
- Das aktuelle `:root`-Farbschema wird zum Light Theme umstrukturiert, ein neues Dark Theme wird unter `.dark` definiert
- Ein Theme-Toggle-Button wird im Header platziert (Desktop und Mobile)
- Ein Inline-Script in `index.html` verhindert Theme-Flackern beim Laden (FOUC-Prevention)
## Architektur
```mermaid
graph TD
A[index.html – Inline-Script] -->|Setzt class auf html| B[html-Element]
B --> C[ThemeProvider – next-themes]
C --> D[App.tsx]
D --> E[Header mit ThemeToggle]
D --> F[Alle Seiten & Komponenten]
G[localStorage – theme] <-->|Lesen/Schreiben| C
H[prefers-color-scheme] -->|System-Erkennung| C
I[index.css – :root Light] --> F
J[index.css – .dark Dark] --> F
```
### Datenfluss
1. Beim Laden der Seite liest ein Inline-Script in `index.html` den `localStorage`-Wert (`theme`) aus. Falls keiner vorhanden ist, wird `prefers-color-scheme` geprüft. Die entsprechende CSS-Klasse (`dark` oder keine) wird auf `<html>` gesetzt – noch bevor React rendert.
2. `ThemeProvider` aus `next-themes` übernimmt die Verwaltung im React-Baum. Er synchronisiert den Zustand mit `localStorage` und reagiert auf Änderungen der System-Einstellung via `matchMedia`-Listener.
3. Der `ThemeToggle`-Button im Header nutzt den `useTheme()`-Hook, um zwischen `light`, `dark` und `system` zu wechseln.
4. Alle Komponenten nutzen weiterhin `hsl(var(--...))` CSS-Variablen – der Wechsel erfolgt rein über CSS-Klassen.
## Komponenten und Schnittstellen
### 1. ThemeProvider-Wrapper (`src/components/ThemeProvider.tsx`)
Dünner Wrapper um `ThemeProvider` aus `next-themes`:
```typescript
interface ThemeProviderProps {
children: React.ReactNode;
defaultTheme?: string; // Standard: "system"
storageKey?: string; // Standard: "theme"
enableSystem?: boolean; // Standard: true
disableTransitionOnChange?: boolean; // Standard: false
}
```
Konfiguration:
- `attribute="class"` – Tailwind-kompatibel
- `defaultTheme="system"` – Erstbesucher erhalten System-Theme
- `enableSystem={true}` – Automatische Erkennung aktiv
- `storageKey="theme"` – localStorage-Schlüssel
Wird in `App.tsx` als äußerster Wrapper um den gesamten Komponentenbaum eingebunden.
### 2. ThemeToggle-Komponente (`src/components/ThemeToggle.tsx`)
Button-Komponente mit Dropdown-Menü (shadcn/ui `DropdownMenu`):
```typescript
interface ThemeToggleProps {
className?: string;
}
```
Funktionalität:
- Zeigt ein Icon basierend auf dem aktuellen Theme (Sun, Moon, Monitor aus `lucide-react`)
- Dropdown mit drei Optionen: „Hell", „Dunkel", „System"
- Nutzt `useTheme()` Hook für `theme`, `setTheme`, `resolvedTheme`
- `aria-label` beschreibt den aktuellen Zustand
- Tastatur-bedienbar (Tab, Enter/Space)
- Mounted-Check verhindert Hydration-Mismatch (Icon wird erst nach Mount gerendert)
### 3. Anpassungen am Header (`src/components/Header.tsx`)
- ThemeToggle wird im Desktop-NavBody neben dem „Kontakt"-Button eingefügt
- ThemeToggle wird im Mobile-Nav-Header neben dem Hamburger-Menü eingefügt
- Positionierung: rechts, vor dem Kontakt-Button (Desktop) bzw. links vom Toggle-Icon (Mobile)
### 4. Inline-Script in `index.html`
Ein `<script>`-Block im `<head>`, der vor dem React-Bundle ausgeführt wird:
```javascript
// Liest localStorage("theme") aus
// Falls "system" oder nicht vorhanden: prüft prefers-color-scheme
// Setzt class="dark" auf <html> wenn Dark Mode aktiv
```
Dieses Script verhindert das sichtbare Flackern (FOUC) beim Seitenaufruf.
### 5. CSS-Variablen-Umstrukturierung (`src/index.css`)
- `:root` → Light Theme (helle Hintergründe, dunkle Texte)
- `.dark` → Dark Theme (das aktuelle `:root`-Farbschema wird hierhin verschoben)
- Alle bestehenden Komponentenstile (`.glass-nav`, `.card-minimal`, `.project-card`, `.btn`, etc.) werden auf Theme-Kompatibilität geprüft und ggf. angepasst
- Hardcodierte Farbwerte in Komponentenstilen werden durch CSS-Variablen ersetzt
### Komponentendiagramm
```mermaid
graph LR
subgraph App.tsx
TP[ThemeProvider]
TP --> Router
end
subgraph Header
TT[ThemeToggle]
TT -->|useTheme| TP
end
subgraph CSS
LT[":root – Light Theme"]
DT[".dark – Dark Theme"]
end
TP -->|class auf html| CSS
```
## Datenmodelle
### Theme-Zustand
```typescript
type ThemeValue = "light" | "dark" | "system";
// Von next-themes bereitgestellt via useTheme()
interface ThemeState {
theme: ThemeValue; // Gewählte Präferenz ("light" | "dark" | "system")
resolvedTheme: "light" | "dark"; // Tatsächlich angewendetes Theme
setTheme: (theme: ThemeValue) => void;
systemTheme: "light" | "dark"; // Aktuelle OS-Einstellung
}
```
### localStorage-Schema
| Schlüssel | Wert | Beschreibung |
|-----------|------|--------------|
| `theme` | `"light"` \| `"dark"` \| `"system"` | Gespeicherte Benutzerpräferenz |
### CSS-Custom-Properties (Light Theme – neu zu erstellen)
```css
:root {
--background: 0 0% 100%; /* Weiß */
--foreground: 0 0% 9%; /* Fast Schwarz */
--card: 0 0% 98%; /* Sehr helles Grau */
--card-foreground: 0 0% 9%;
--primary: 198 93% 42%; /* Cyan-Blau (konsistent) */
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 94%; /* Helles Grau */
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 94%;
--muted-foreground: 0 0% 40%;
--accent: 0 0% 94%;
--accent-foreground: 0 0% 9%;
--border: 0 0% 88%;
--input: 0 0% 88%;
--ring: 198 93% 42%;
--destructive: 0 62% 50%;
--destructive-foreground: 0 0% 98%;
/* ... weitere Token analog */
}
```
### CSS-Custom-Properties (Dark Theme – bestehendes Schema)
```css
.dark {
--background: 0 0% 0%; /* Schwarz (aktuelles :root) */
--foreground: 0 0% 92%;
--card: 0 0% 6%;
--card-foreground: 0 0% 92%;
--primary: 180 40% 50%;
--primary-foreground: 0 0% 3%;
/* ... restliche aktuelle :root-Werte */
}
```
## Korrektheitseigenschaften (Correctness Properties)
*Eine Korrektheitseigenschaft ist ein Merkmal oder Verhalten, das über alle gültigen Ausführungen eines Systems hinweg gelten soll – im Wesentlichen eine formale Aussage darüber, was das System tun soll. Eigenschaften bilden die Brücke zwischen menschenlesbaren Spezifikationen und maschinell überprüfbaren Korrektheitsgarantien.*
### Property 1: System-Theme-Auflösung
*Für jede* beliebige System-Theme-Einstellung (hell oder dunkel) gilt: Wenn die Theme-Präferenz auf „system" steht (oder keine Präferenz gespeichert ist), dann soll das aufgelöste Theme (`resolvedTheme`) dem aktuellen System-Theme entsprechen.
**Validates: Requirements 1.1, 1.2, 1.3, 2.5**
### Property 2: Explizite Theme-Wahl überschreibt System
*Für jede* beliebige System-Theme-Einstellung und *für jede* explizite Theme-Wahl (`"light"` oder `"dark"`) gilt: Das aufgelöste Theme (`resolvedTheme`) soll immer der expliziten Wahl entsprechen, unabhängig vom System-Theme.
**Validates: Requirements 2.3, 2.4**
### Property 3: Persistenz-Round-Trip
*Für jeden* gültigen Theme-Wert (`"light"`, `"dark"`, `"system"`) gilt: Wenn der Wert über `setTheme()` gesetzt wird, dann soll der im `localStorage` gespeicherte Wert identisch sein, und beim erneuten Laden soll dieser gespeicherte Wert korrekt als aktive Präferenz wiederhergestellt werden.
**Validates: Requirements 3.1, 3.2**
## Fehlerbehandlung
| Szenario | Verhalten |
|----------|-----------|
| `localStorage` nicht verfügbar (blockiert, privater Modus) | ThemeProvider fällt auf System-Theme-Erkennung zurück. Kein Fehler wird geworfen. Die Seite funktioniert normal, nur die Persistenz entfällt. |
| `prefers-color-scheme` nicht unterstützt (ältere Browser) | `next-themes` fällt auf den `defaultTheme` zurück (`"system"` → wird als `"light"` aufgelöst). |
| Ungültiger Wert im `localStorage` | `next-themes` ignoriert ungültige Werte und fällt auf `defaultTheme` zurück. |
| JavaScript deaktiviert | Das Inline-Script in `index.html` wird nicht ausgeführt. Die Seite zeigt das Standard-CSS (Light Theme in `:root`). Kein Theme-Toggle verfügbar, aber die Seite bleibt nutzbar. |
| Hydration-Mismatch (SSR/SSG) | Nicht relevant für Vite-SPA. Der Mounted-Check im ThemeToggle verhindert trotzdem Icon-Flackern. |
## Teststrategie
### Property-Based Tests (fast-check)
Bibliothek: `fast-check` (für TypeScript/Vitest)
Konfiguration:
- Minimum 100 Iterationen pro Property-Test
- Jeder Test referenziert die zugehörige Design-Property
- Tag-Format: **Feature: system-theme-detection, Property {number}: {property_text}**
Die drei Korrektheitseigenschaften werden als Property-Based Tests implementiert:
1. **Property 1** – Theme-Auflösungslogik bei „system"-Präferenz: Generiere zufällige System-Theme-Werte, setze Präferenz auf „system", prüfe `resolvedTheme === systemTheme`.
2. **Property 2** – Explizite Wahl überschreibt System: Generiere zufällige Kombinationen aus System-Theme und expliziter Wahl, prüfe `resolvedTheme === expliziteWahl`.
3. **Property 3** – Persistenz-Round-Trip: Generiere zufällige gültige Theme-Werte, speichere via `setTheme()`, lese aus `localStorage`, prüfe Gleichheit. Simuliere Neustart, prüfe ob Wert wiederhergestellt wird.
### Unit Tests (Vitest + React Testing Library)
Ergänzend zu den Property-Tests werden folgende Example-basierte Tests geschrieben:
- **ThemeToggle-Rendering**: Button ist im Header vorhanden (Req. 2.1)
- **Dropdown-Optionen**: Drei Optionen (Hell, Dunkel, System) sind vorhanden (Req. 2.2)
- **Icon-Zuordnung**: Korrektes Icon für jeden Theme-Zustand (Req. 2.6)
- **CSS-Token-Vollständigkeit**: Alle erforderlichen Custom Properties sind im Light Theme definiert (Req. 4.1)
- **Kontrastverhältnisse**: Schlüssel-Farbpaare erfüllen WCAG-AA (Req. 4.2)
- **Tastatur-Bedienbarkeit**: Tab-Navigation und Enter/Space funktionieren (Req. 6.1)
- **aria-label**: Beschreibt den aktuellen Zustand korrekt (Req. 6.2)
- **aria-live**: Zustandswechsel wird kommuniziert (Req. 6.3)
### Edge-Case Tests
- **localStorage blockiert**: Fallback auf System-Theme ohne Fehler (Req. 3.3)
### Integrationstests
- **matchMedia-Listener**: Simuliere OS-Theme-Wechsel, prüfe ob resolvedTheme sich aktualisiert (Req. 1.4)
### Nicht automatisiert testbar
- Visuell korrekte Darstellung der Komponentenstile im Light Theme (Req. 4.3) → Manuelle visuelle Prüfung
- Kein sichtbares Flackern beim Laden (Req. 5.2) → Manuelle visuelle Prüfung