mitgliederverwaltung/docs/sidebar-requirements-v2.md
Simon 16ca4efc03
Some checks failed
continuous-integration/drone/push Build is failing
feat: implement standard-compliant sidebar with comprehensive tests
Implement a new sidebar component based on DaisyUI Drawer pattern without
custom CSS variants. The sidebar supports desktop (expanded/collapsed states)
and mobile (overlay drawer) with full accessibility compliance.

Sidebar Implementation:
- Refactor sidebar component with sidebar_header, menu_item, menu_group,
  sidebar_footer sub-components
- Add logo (mila.svg) with size-8 (32px) always visible
- Implement toggle button with icon swap (chevron-left/right) for desktop
- Add nested menu support with details/summary (expanded) and dropdown
  (collapsed) patterns
- Implement footer with language selector (expanded-only), theme toggle,
  and user menu with avatar
- Update layouts.ex to use drawer pattern with data-sidebar-expanded
  attribute for state management

CSS & JavaScript:
- Add CSS styles for sidebar state management via data-attribute selectors
- Implement SidebarState JavaScript hook for localStorage persistence
- Add smooth width transitions (w-64 ↔ w-16) for desktop collapsed state
- Add CSS classes for expanded-only, menu-label, and icon visibility

Documentation:
- Add sidebar-analysis-current-state.md: Analysis of current implementation
- Add sidebar-requirements-v2.md: Complete specification for new sidebar
- Add daisyui-drawer-pattern.md: DaisyUI pattern documentation
- Add umsetzung-sidebar.md: Step-by-step implementation guide

Testing:
- Add comprehensive component tests for all sidebar sub-components
- Add integration tests for sidebar state management and mobile drawer
- Extend accessibility tests (ARIA labels, roles, keyboard navigation)
- Add regression tests for duplicate IDs, hover effects, and tooltips
- Ensure full test coverage per specification requirements
2025-12-18 16:36:16 +01:00

1250 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Sidebar Requirements Specification v2
**Erstellt:** 2025-12-16
**Version:** 2.0
**Status:** Spezifikation für Neuimplementierung
**Basierend auf:** `sidebar-analysis-current-state.md`, `daisyui-drawer-pattern.md`
---
## Executive Summary
Diese Spezifikation definiert die Anforderungen für eine **standardkonforme Sidebar-Implementierung** basierend auf **DaisyUI Drawer Pattern** ohne Custom CSS Variants. Die Sidebar unterstützt Desktop (expanded/collapsed States) und Mobile (Overlay Drawer).
**Kernprinzipien:**
-**100% DaisyUI Standard** - keine Custom Variants
-**Ein Layout** - keine Duplikate für Mobile/Desktop
-**State via data-attribute** - CSS reagiert auf `[data-sidebar-expanded]`
-**localStorage Persistence** - State bleibt über Seitenreloads erhalten
-**WCAG 2.1 Level AA** - vollständig accessible
---
## 1. Funktionale Anforderungen
### 1.1 Logo
| Eigenschaft | Wert | Begründung |
|-------------|------|------------|
| **Größe** | `size-8` (32px × 32px) | Konsistent, gut lesbar |
| **Sichtbarkeit** | Immer sichtbar | Branding, Orientierung |
| **States** | Gleich in expanded & collapsed | Keine unterschiedlichen Logo-Elemente |
| **Position** | Links im Header | Standard-Pattern |
| **Pfad** | `/images/mila.svg` | Projektspezifisch |
**Verhalten:**
- Expanded: Logo + Club-Name
- Collapsed: Logo zentriert (horizontal & vertikal)
### 1.2 Toggle-Button
| Eigenschaft | Wert | Begründung |
|-------------|------|------------|
| **Sichtbarkeit** | Nur Desktop (≥ lg) | Mobile nutzt Hamburger in Header |
| **Position** | Rechts im Sidebar-Header | Schnell erreichbar |
| **Icon (expanded)** | `hero-chevron-left` | Zeigt Collapse-Richtung |
| **Icon (collapsed)** | `hero-chevron-right` | Zeigt Expand-Richtung |
| **Größe** | `btn-sm btn-square` | Kompakt, icon-only |
| **Variante** | `btn-ghost` | Dezent, nicht dominant |
**Icon-Swap Strategie:**
- Zwei separate `<.icon>` Elemente
- CSS-Klassen: `.sidebar-expanded-icon` und `.sidebar-collapsed-icon`
- CSS zeigt/versteckt basierend auf `[data-sidebar-expanded]`
**ARIA:**
- `aria-label="Toggle sidebar"`
- `aria-expanded="true|false"` (via JavaScript)
- `aria-controls="main-sidebar"`
### 1.3 Menü-Items (Flat)
**Expanded State:**
- Icon (links) + Text-Label (rechts)
- Standard DaisyUI menu styling
- Gap: `gap-3` zwischen Icon und Text
**Collapsed State:**
- Nur Icon (zentriert)
- Tooltip erscheint rechts (`tooltip-right`)
- Tooltip-Text aus `data-tip` Attribut
**Hover-Effekt:**
- Einheitlich für expanded UND collapsed
- Standard DaisyUI: `hover:bg-base-300`
- Keine Custom-Styles
**Liste der Menü-Items:**
1. **Members** - `hero-users` - `/members`
2. **Users** - `hero-user-circle` - `/users`
3. **Custom Fields** - `hero-rectangle-group` - `/custom_fields`
4. **Settings** - `hero-cog-6-tooth` - `#` (Placeholder)
### 1.4 Nested Menu "Beiträge" (Contributions)
**Problem:** Standard Menu-Item Pattern funktioniert nicht für verschachtelte Menüs.
**Lösung:** Zwei unterschiedliche Darstellungen je nach State.
#### Expanded State:
```html
<details class="expanded-menu-group">
<summary>
Icon + "Beiträge"
</summary>
<ul>
<li>Beitragsarten</li>
<li>Einstellungen</li>
</ul>
</details>
```
**Eigenschaften:**
- Native `<details>/<summary>` für auf/zuklappbar
- Submenu eingerückt (`ml-4`)
- Chevron-Icon rotiert automatisch (Browser-Standard)
#### Collapsed State:
```html
<div class="collapsed-menu-group dropdown dropdown-right">
<button>Icon</button>
<ul class="dropdown-content">
<li class="menu-title">Beiträge</li>
<li>Beitragsarten</li>
<li>Einstellungen</li>
</ul>
</div>
```
**Eigenschaften:**
- DaisyUI `dropdown` mit `dropdown-right`
- Flyout erscheint RECHTS vom Icon
- Menu-Title zeigt "Beiträge"
- Tooltip auf Button (collapsed)
**Wichtig:**
- Nur EIN Hover-Effekt (nicht doppelt)
- CSS-Klassen `.expanded-menu-group` und `.collapsed-menu-group`
- CSS zeigt/versteckt basierend auf `[data-sidebar-expanded]`
**Submenu-Items:**
1. **Beitragsarten** - `/contribution_types`
2. **Einstellungen** - `/contribution_settings`
### 1.5 Footer
**Position:**
- IMMER am unteren Ende der Sidebar
- Flexbox: Navigation mit `flex-1`, Footer mit `mt-auto`
**Komponenten:**
#### 1.5.1 Language Selector
- **Sichtbarkeit:** Nur expanded (`.expanded-only`)
- **Typ:** `<select>` mit Form (POST zu `/locale`)
- **Variante:** `select select-sm w-full`
- **Optionen:** Deutsch (de), English (en)
- **Funktion:** Auto-submit on change
#### 1.5.2 Theme Toggle
- **Sichtbarkeit:** Immer (expanded & collapsed)
- **Layout:** Horizontal: Sun Icon + Toggle + Moon Icon
- **Variante:** `toggle toggle-sm theme-controller`
- **Funktion:** DaisyUI automatische Theme-Umschaltung
- **Collapsed:** Zentriert horizontal
#### 1.5.3 User Menu
- **Typ:** DaisyUI `dropdown dropdown-top dropdown-end`
- **Avatar:** Rund, erste Buchstabe der Email, zentriert
- **Größe:** `w-8 h-8` (collapsed), `w-10 h-10` (expanded)
- **Email:** Nur expanded sichtbar, `truncate`
- **Dropdown-Items:**
1. Profile (`/users/:id`)
2. Logout (`/sign-out`)
**Footer-Layout:**
```
┌─────────────────────────┐
│ [Language Selector] │ ← Nur expanded
│ ☀️ [Toggle] 🌙 │ ← Immer
│ [Avatar] [Email ▼] │ ← Avatar immer, Email nur expanded
└─────────────────────────┘
```
### 1.6 State Persistence
**localStorage Key:** `sidebar-expanded`
**Werte:**
- `"true"` - Sidebar ist expanded (default)
- `"false"` - Sidebar ist collapsed
**data-attribute auf Root:**
```html
<div data-sidebar-expanded="true">
```
**Verhalten:**
1. Beim Mount: Restore aus localStorage
2. Beim Toggle: Update localStorage + data-attribute
3. CSS reagiert auf data-attribute änderung
### 1.7 Responsive Verhalten
**Mobile (< lg / < 1024px):**
- Standard DaisyUI Drawer Overlay
- Hamburger-Icon in Mobile Header (außerhalb Sidebar)
- Overlay verdunkelt Hintergrund
- Click auf Overlay schließt Drawer
- Sidebar: volle Breite `w-64` (16rem)
- Toggle-Button in Sidebar: NICHT sichtbar
**Desktop (≥ lg / ≥ 1024px):**
- Fixed Sidebar (persistent)
- `lg:drawer-open` forciert Drawer als sichtbar
- Expanded: `w-64` (16rem)
- Collapsed: `w-16` (4rem)
- Smooth width transition: `300ms ease-in-out`
- Toggle-Button in Sidebar: sichtbar
- Mobile Header: NICHT sichtbar (`lg:hidden`)
---
## 2. Wireframes (ASCII-Art)
### 2.1 Desktop - Expanded State (w-64 / 16rem)
```
┌────────────────────────────────┐
│ 🏠 Club Name [◀] │ ← Header (Logo + Name + Toggle)
├────────────────────────────────┤
│ │
│ 👥 Members │
│ 👤 Users │
│ 💰 Beiträge ▼ │ ← Details/Summary (klappbar)
│ ├─ Beitragsarten │
│ └─ Einstellungen │
│ ⚙️ Settings │
│ │
│ │
│ (flex-1 - wächst) │
│ │
│ │
├────────────────────────────────┤
│ [Deutsch ▼] │ ← Language Selector
│ ☀️ [○] 🌙 │ ← Theme Toggle
│ (A) user@example.com [▼] │ ← User Menu (Avatar + Email)
└────────────────────────────────┘
16rem (256px)
```
### 2.2 Desktop - Collapsed State (w-16 / 4rem)
```
┌──────┐
│ 🏠 │ ← Logo zentriert
│ [▶] │ ← Toggle-Button
├──────┤
│ │
│ 👥 │ ← Tooltip: "Members"
│ 👤 │ ← Tooltip: "Users"
│ 💰 │ ← Dropdown öffnet rechts →
│ ⚙️ │ ← Tooltip: "Settings"
│ │
│ │
│ │ (flex-1)
│ │
│ │
├──────┤
│ │ ← Language Selector HIDDEN
│ ☀️○🌙│ ← Theme Toggle (icons kleiner)
│ (A) │ ← Avatar zentriert (kein Email)
└──────┘
4rem
```
**Dropdown-Verhalten (collapsed):**
```
┌──────┐ ┌─────────────────────┐
│ 👥 │ │ │
│ 👤 │ │ │
│ 💰 │────────────▶│ Beiträge │
│ ⚙️ │ │ ─────────────────── │
│ │ │ • Beitragsarten │
└──────┘ │ • Einstellungen │
└─────────────────────┘
```
### 2.3 Mobile mit Overlay (< lg / < 1024px)
**Geschlossen:**
```
┌─────────────────────────────┐
│ [☰] Club Name │ ← Mobile Header (außerhalb Sidebar)
├─────────────────────────────┤
│ │
│ Main Content │
│ │
└─────────────────────────────┘
```
**Geöffnet:**
```
┌────────────────────────────────┐
│ 🏠 Club Name │ ◀──────── Sidebar (w-64)
├────────────────────────────────┤ Overlay: bg-black/50
│ │
│ 👥 Members │
│ 👤 Users │
│ 💰 Beiträge ▼ │
│ ├─ Beitragsarten │
│ └─ Einstellungen │
│ ⚙️ Settings │
│ │
│ (flex-1) │
│ │
├────────────────────────────────┤
│ [Deutsch ▼] │
│ ☀️ [○] 🌙 │
│ (A) user@example.com [▼] │
└────────────────────────────────┘
└─ Kein Toggle-Button (nur expanded)
```
**Hinweis:** Mobile Sidebar ist IMMER expanded (kein collapsed state).
---
## 3. Benötigte DaisyUI-Komponenten
### 3.1 Layout-Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **drawer** | Basis-Container | `drawer`, `lg:drawer-open` |
| **drawer-toggle** | Hidden Checkbox (Mobile) | `drawer-toggle` |
| **drawer-content** | Main Content Area | `drawer-content`, `flex flex-col` |
| **drawer-side** | Sidebar Container | `drawer-side` |
| **drawer-overlay** | Mobile Overlay (Click to close) | `drawer-overlay`, `lg:hidden` |
### 3.2 Navigation-Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **menu** | Navigation Liste | `menu`, `flex-1`, `w-full`, `p-2` |
| **menu-title** | Abschnitts-Überschrift | `menu-title` |
| **tooltip** | Icon-Tooltips (collapsed) | `tooltip`, `tooltip-right` |
### 3.3 Interaktive Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **btn** | Toggle-Button | `btn`, `btn-ghost`, `btn-sm`, `btn-square` |
| **select** | Language Selector | `select`, `select-sm`, `w-full` |
| **toggle** | Theme Switch | `toggle`, `toggle-sm`, `theme-controller` |
| **dropdown** | User Menu & Nested Menu | `dropdown`, `dropdown-top`, `dropdown-end`, `dropdown-right` |
| **avatar** | User Avatar | `avatar`, `avatar-placeholder` |
| **navbar** | Mobile Header | `navbar`, `bg-base-100`, `shadow-sm`, `lg:hidden` |
### 3.4 Utility-Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **Flexbox** | Layout-Struktur | `flex`, `flex-col`, `flex-1`, `items-center`, `justify-center` |
| **Spacing** | Gaps & Padding | `gap-2`, `gap-3`, `gap-4`, `p-2`, `p-4`, `mt-auto` |
| **Sizing** | Widths & Heights | `w-64`, `w-16`, `w-8`, `h-8`, `size-5`, `size-8`, `min-h-screen` |
| **Colors** | Backgrounds | `bg-base-100`, `bg-base-200`, `bg-base-300`, `bg-neutral` |
| **Typography** | Text Styles | `text-lg`, `text-sm`, `font-bold`, `truncate` |
| **Borders** | Dividers | `border-t`, `border-b`, `border-base-300` |
| **Z-Index** | Layering | `z-10`, `z-20`, `z-50` |
### 3.5 Responsive Modifiers
| Modifier | Breakpoint | Verwendung |
|----------|------------|------------|
| **lg:** | ≥ 1024px | Desktop-spezifische Styles |
| **lg:hidden** | < 1024px | Mobile Header, Overlay |
| **lg:flex** | 1024px | Toggle-Button in Sidebar |
| **lg:drawer-open** | 1024px | Persistent Desktop Sidebar |
---
## 4. CSS-Strategie
### 4.1 Prinzipien
**Erlaubt:**
- Tailwind `@apply` Direktiven
- Standard DaisyUI Komponenten-Klassen
- CSS attribute selectors (`[data-*]`)
- Tailwind responsive modifiers (`lg:`, `md:`)
- Standard pseudo-classes (`:hover`, `:focus`)
**NICHT erlaubt:**
- Custom CSS Variants (`@custom-variant`)
- Custom Tailwind Plugin Variants
- Complex CSS Selectors (mehr als 3 Ebenen tief)
- `!important` (außer in extremen Edge-Cases)
### 4.2 State Management via Data-Attribute
**Selector Pattern:**
```css
[data-sidebar-expanded="false"] .sidebar .element {
/* Collapsed State Styles */
}
```
**Beispiel-Implementierung in `app.css`:**
```css
/* ============================================
Sidebar Base Styles
============================================ */
.sidebar {
@apply flex flex-col bg-base-200 min-h-screen;
@apply transition-[width] duration-300 ease-in-out;
width: 16rem; /* w-64 - Expanded */
}
/* Collapsed State */
[data-sidebar-expanded="false"] .sidebar {
width: 4rem; /* w-16 - Collapsed */
}
/* ============================================
Text Labels - Fade Out in Collapsed
============================================ */
.menu-label {
@apply transition-all duration-200 whitespace-nowrap;
@apply opacity-100;
}
[data-sidebar-expanded="false"] .sidebar .menu-label {
@apply opacity-0 w-0 overflow-hidden pointer-events-none;
}
/* ============================================
Toggle Button Icon Swap
============================================ */
.sidebar-expanded-icon {
@apply block;
}
.sidebar-collapsed-icon {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .sidebar-expanded-icon {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .sidebar-collapsed-icon {
@apply block;
}
/* ============================================
Nested Menu - Show/Hide Based on State
============================================ */
.expanded-menu-group {
@apply block;
}
.collapsed-menu-group {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .expanded-menu-group {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .collapsed-menu-group {
@apply block;
}
/* ============================================
Elements Only Visible in Expanded State
============================================ */
.expanded-only {
@apply block transition-opacity duration-200;
}
[data-sidebar-expanded="false"] .sidebar .expanded-only {
@apply hidden;
}
/* ============================================
Tooltips - Only Show in Collapsed State
============================================ */
.sidebar .tooltip::before,
.sidebar .tooltip::after {
@apply opacity-0 pointer-events-none;
}
[data-sidebar-expanded="false"] .sidebar .tooltip:hover::before,
[data-sidebar-expanded="false"] .sidebar .tooltip:hover::after {
@apply opacity-100;
}
/* ============================================
Menu Item Alignment - Center in Collapsed
============================================ */
[data-sidebar-expanded="false"] .sidebar .menu > li > a,
[data-sidebar-expanded="false"] .sidebar .menu > li > button {
@apply justify-center px-0;
}
```
### 4.3 Responsive Strategie
**Mobile (< lg):**
- Standard DaisyUI Drawer (checkbox-basiert)
- Keine custom State-Management
- Sidebar immer `w-64` wenn geöffnet
**Desktop (≥ lg):**
- `lg:drawer-open` für Persistence
- Custom State-Management via data-attribute
- Width-Transition: `w-64` `w-16`
**Keine Konflikte:**
- `lg:drawer-open` wird NICHT durch `data-sidebar-expanded` beeinflusst
- Collapsed State nur auf Desktop relevant
---
## 5. State-Management-Strategie
### 5.1 JavaScript Hook: `SidebarState`
**Datei:** `assets/js/app.js`
**Verantwortlichkeiten:**
1. **Mount:** Restore State aus localStorage
2. **Toggle:** Wechsle State, update localStorage + data-attribute
3. **ARIA:** Update `aria-expanded` auf Toggle-Button
4. **Global Function:** Expose `window.toggleSidebar()`
**Implementierung:**
```javascript
Hooks.SidebarState = {
mounted() {
// Restore state from localStorage
const expanded = localStorage.getItem('sidebar-expanded') !== 'false';
this.setSidebarState(expanded);
// Expose toggle function globally
window.toggleSidebar = () => {
const current = this.el.dataset.sidebarExpanded === 'true';
this.setSidebarState(!current);
};
},
setSidebarState(expanded) {
// Update data-attribute (CSS reacts to this)
this.el.dataset.sidebarExpanded = expanded;
// Persist to localStorage
localStorage.setItem('sidebar-expanded', expanded);
// Update ARIA for accessibility
const toggleBtn = document.getElementById('sidebar-toggle');
if (toggleBtn) {
toggleBtn.setAttribute('aria-expanded', expanded);
}
},
destroyed() {
// Cleanup
delete window.toggleSidebar;
}
};
```
### 5.2 localStorage Schema
**Key:** `sidebar-expanded`
**Values:**
- `"true"` (string) - Expanded
- `"false"` (string) - Collapsed
**Default:** `"true"` (wenn Key nicht existiert)
**Warum String?**
- localStorage speichert nur Strings
- Vermeidet Parsing-Fehler
- Einfache Vergleiche
### 5.3 CSS Reactions
**Automatisch via Attribute Selector:**
```html
<!-- Expanded -->
<div data-sidebar-expanded="true">
<aside class="sidebar"> <!-- width: 16rem -->
<span class="menu-label">Members</span> <!-- visible -->
</aside>
</div>
<!-- Collapsed -->
<div data-sidebar-expanded="false">
<aside class="sidebar"> <!-- width: 4rem -->
<span class="menu-label">Members</span> <!-- hidden -->
</aside>
</div>
```
**Vorteile:**
- Keine JavaScript DOM-Manipulation
- CSS Transitions funktionieren automatisch
- Performance: GPU-accelerated
- Wartbar: State ist zentral
### 5.4 Event Flow
```
User clicks Toggle-Button
onclick="toggleSidebar()"
JavaScript Hook: setSidebarState(!current)
├─▶ Update data-attribute
├─▶ Save to localStorage
└─▶ Update aria-expanded
CSS Reactions
├─▶ Sidebar width transition
├─▶ Labels fade out/in
├─▶ Icons show/hide
└─▶ Tooltips enable/disable
User sees smooth animation
```
**Keine Komplexität:**
- Kein tabindex management
- Kein focus management
- Keine event listener cleanup
- Keine checkbox manipulation
---
## 6. Komponentenstruktur
### 6.1 Hierarchie
```
app (layouts.ex)
├── Mobile Header (lg:hidden)
│ ├── Hamburger-Icon (drawer-toggle label)
│ └── Club Name
└── Sidebar (drawer-side)
├── sidebar_header
│ ├── Logo (size-8)
│ ├── Club Name (.menu-label)
│ └── Toggle-Button (hidden lg:flex)
│ ├── Icon: Chevron-Left (.sidebar-expanded-icon)
│ └── Icon: Chevron-Right (.sidebar-collapsed-icon)
├── sidebar_menu (flex-1)
│ ├── menu_item: Members
│ ├── menu_item: Users
│ ├── menu_group: Beiträge (.expanded-menu-group + .collapsed-menu-group)
│ │ ├── Expanded: <details><summary>
│ │ ├── Collapsed: <dropdown>
│ │ └── Submenu:
│ │ ├── menu_subitem: Beitragsarten
│ │ └── menu_subitem: Einstellungen
│ └── menu_item: Settings
└── sidebar_footer (mt-auto)
├── Language Selector (.expanded-only)
├── Theme Toggle (immer)
└── User Menu (dropdown-top dropdown-end)
├── Avatar + Email
└── Dropdown:
├── Profile
└── Logout
```
### 6.2 Elixir Komponenten
**Datei:** `lib/mv_web/components/layouts/sidebar.ex`
**Funktionen:**
1. `sidebar/1` - Root (deprecated, wird durch layouts.ex ersetzt)
2. `sidebar_content/1` - Wrapper für alle Sidebar-Inhalte
3. `sidebar_header/1` - Logo + Name + Toggle
4. `sidebar_menu/1` - Navigation Container
5. `menu_item/1` - Einfacher Menü-Eintrag
6. `menu_group/1` - Nested Menu Container
7. `menu_subitem/1` - Submenu-Eintrag
8. `sidebar_footer/1` - Footer Container
9. `theme_toggle/1` - Theme Switch
10. `user_menu/1` - User Dropdown
### 6.3 Attribute Definition
**Beispiel: `menu_item/1`**
```elixir
attr :href, :string, required: true, doc: "Navigation path"
attr :icon, :string, required: true, doc: "Heroicon name"
attr :label, :string, required: true, doc: "Menu item label"
attr :active, :boolean, default: false, doc: "Highlight as active"
defp menu_item(assigns) do
~H"""
<li role="none">
<.link
navigate={@href}
class={[
"flex items-center gap-3",
"tooltip tooltip-right",
@active && "active"
]}
data-tip={@label}
role="menuitem"
>
<.icon name={@icon} class="size-5 shrink-0" aria-hidden="true" />
<span class="menu-label">{@label}</span>
</.link>
</li>
"""
end
```
---
## 7. Accessibility (WCAG 2.1 Level AA)
### 7.1 Semantic HTML
| Element | Semantik | ARIA |
|---------|----------|------|
| `<nav>` | Hauptnavigation | `aria-label="Main navigation"` |
| `<ul role="menubar">` | Menu Container | `role="menubar"` |
| `<li role="none">` | List Item (deaktiviert) | `role="none"` |
| `<a role="menuitem">` | Menu Item | `role="menuitem"` |
| `<button aria-haspopup>` | Dropdown Trigger | `aria-haspopup="menu"` |
| `<details>` | Expandable Section | Native HTML (kein ARIA nötig) |
### 7.2 ARIA-Attribute
**Toggle-Button:**
```html
<button
aria-label="Toggle sidebar"
aria-expanded="true"
aria-controls="main-sidebar"
>
```
**User Menu:**
```html
<button
aria-label="User menu"
aria-haspopup="menu"
aria-expanded="false"
>
```
**Icons (dekorativ):**
```html
<.icon name="hero-users" aria-hidden="true" />
```
**Tooltips:**
```html
<a data-tip="Members" class="tooltip tooltip-right">
<!-- Tooltip ist visuell, nicht für Screen Reader -->
</a>
```
### 7.3 Keyboard Navigation
**Fokussierbare Elemente:**
- Toggle-Button (Space/Enter)
- Menu-Items (Enter)
- User-Menu-Button (Space/Enter, Arrows für Navigation)
- Dropdowns (Tab, Arrows, Escape)
- Theme-Toggle (Space)
- Language-Selector (Arrows, Enter)
**Focus-Indikatoren:**
```css
.focus:outline-none {
@apply ring-2 ring-primary ring-offset-2;
}
```
**Escape-Taste:**
- Schließt User-Menu-Dropdown
- Schließt Beiträge-Dropdown (collapsed)
- Schließt Mobile-Drawer
### 7.4 Color Contrast
**Mindestkontrast:** 4.5:1 (normal text), 3:1 (large text)
**DaisyUI Colors (geprüft):**
- `text-base-content` auf `bg-base-200`: 7.2:1
- `text-primary` auf `bg-base-100`: 5.1:1
- `text-neutral-content` auf `bg-neutral`: 8.5:1
### 7.5 Screen Reader Testing
**Tools:**
- NVDA (Windows)
- VoiceOver (Mac)
- JAWS (Windows)
**Erwartetes Verhalten:**
- "Main navigation, navigation landmark"
- "Members, link, menu item"
- "Toggle sidebar, button, expanded"
- "User menu, button, has popup"
---
## 8. Performance-Überlegungen
### 8.1 CSS Transitions
**Optimiert:**
- `transition-[width]` - GPU-accelerated
- `duration-300` - Wahrnehmbar, aber schnell
- `ease-in-out` - Smooth Start/Stop
**Vermeiden:**
- `transition-all` - Zu generisch, Performance-Impact
- Transitions auf `height: auto` - Nicht smooth
### 8.2 JavaScript
**Minimal:**
- Hook läuft nur beim Mount
- Toggle ist synchron (keine async Operations)
- Keine Event-Listener-Lawine
- Cleanup in `destroyed()`
**localStorage:**
- Synchron, aber schnell (Key/Value-Store)
- Nur bei Toggle geschrieben (nicht bei jedem Render)
### 8.3 CSS Paint
**Minimierte Repaints:**
- Width-Änderung triggert nur Reflow der Sidebar
- Content-Bereich passt sich automatisch an (Flexbox)
- Keine Layout-Thrashing
---
## 9. Browser-Kompatibilität
### 9.1 Unterstützte Browser
| Browser | Mindestversion | Notizen |
|---------|---------------|---------|
| **Chrome** | 90+ | Vollständig unterstützt |
| **Edge** | 90+ | Chromium-basiert |
| **Firefox** | 88+ | Vollständig unterstützt |
| **Safari** | 14+ | CSS-Transitions funktionieren |
### 9.2 Features mit Fallbacks
**CSS Grid/Flexbox:**
- Alle Browser unterstützen
**CSS Custom Properties:**
- Alle Browser unterstützen (DaisyUI Themes)
**localStorage:**
- Alle Browser unterstützen
- Fallback: State nur in Session
**data-attributes:**
- Alle Browser unterstützen
---
## 10. Testing-Strategie
### 10.1 Unit Tests
**Komponenten-Tests:**
```elixir
describe "sidebar_header/1" do
test "renders logo with correct size"
test "renders club name"
test "renders toggle button with both icons"
end
describe "menu_item/1" do
test "renders icon and label"
test "has tooltip with correct text"
test "has correct link"
end
describe "menu_group/1" do
test "renders expanded menu group"
test "renders collapsed menu group"
test "renders submenu items"
end
```
### 10.2 Integration Tests
**State-Management:**
```elixir
describe "sidebar state management" do
test "sidebar starts expanded by default"
test "toggle button changes state"
test "state persists across page reloads"
end
```
### 10.3 Visual Regression Tests
**Manuell (Checkliste):**
- [ ] Desktop Expanded: Screenshot
- [ ] Desktop Collapsed: Screenshot
- [ ] Mobile Closed: Screenshot
- [ ] Mobile Open: Screenshot
- [ ] Toggle Transition: Video
- [ ] Nested Menu (expanded): Screenshot
- [ ] Nested Menu (collapsed dropdown): Screenshot
- [ ] Footer Position: Screenshot
### 10.4 Accessibility Tests
**Tools:**
- Lighthouse (Chrome DevTools)
- axe DevTools Extension
- WAVE Extension
**Manuell:**
- Keyboard-Navigation (nur Tastatur)
- Screen-Reader (NVDA/VoiceOver)
- Zoom (200% / 400%)
- High-Contrast-Mode
---
## 11. Migrations-Plan (von aktueller Implementierung)
### 11.1 Zu entfernen
**CSS:**
- [ ] Alle `@custom-variant is-drawer-*` Definitionen
- [ ] Alle `.is-drawer-close:*` Klassen
- [ ] Alle `.is-drawer-open:*` Klassen
**HTML:**
- [ ] Inline `onclick` Handler (durch `toggleSidebar()` ersetzen)
- [ ] Magic IDs (durch konsistente Naming ersetzen)
**JavaScript:**
- [ ] Komplexe Tabindex-Management-Logik
- [ ] Event-Listener für Checkbox
- [ ] Focus-Management (außer ARIA)
### 11.2 Zu ergänzen
**CSS:**
- [ ] `[data-sidebar-expanded]` Selektoren
- [ ] `.menu-label` Klasse
- [ ] `.sidebar-expanded-icon` / `.sidebar-collapsed-icon`
- [ ] `.expanded-menu-group` / `.collapsed-menu-group`
- [ ] `.expanded-only`
**HTML:**
- [ ] Logo-Element
- [ ] Toggle-Button mit Icon-Swap
- [ ] `data-sidebar-expanded` auf root
- [ ] `phx-hook="SidebarState"` auf root
**JavaScript:**
- [ ] `Hooks.SidebarState` Hook
- [ ] `window.toggleSidebar()` Function
### 11.3 Zu prüfen/behalten
**DaisyUI:**
- [x] drawer Pattern (korrekt)
- [x] drawer-toggle (Mobile)
- [x] lg:drawer-open (Desktop)
- [x] drawer-overlay (Mobile)
**Accessibility:**
- [x] ARIA-Labels (größtenteils korrekt)
- [x] role Attributes (korrekt)
- [x] Focus-Indikatoren (korrekt)
---
## 12. Risiken und Mitigations
### 12.1 Identifizierte Risiken
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|--------|-------------------|--------|------------|
| **CSS-Transitions flackern** | Mittel | Mittel | Pre-testing, GPU-Acceleration |
| **localStorage nicht verfügbar** | Niedrig | Niedrig | Try/Catch, Fallback auf Session |
| **Nested Menu doppelter Hover** | Mittel | Niedrig | CSS Specificity prüfen |
| **Mobile Drawer schließt nicht** | Niedrig | Hoch | DaisyUI-Standard verwenden |
| **Tooltip überlappen Content** | Mittel | Niedrig | z-index Management |
### 12.2 Browser-spezifische Risks
**Safari:**
- Flex-Gap ältere Versionen: Fallback mit margin
**Firefox:**
- Scrollbar-Styling: Akzeptieren (nicht kritisch)
---
## 13. Wartung und Erweiterbarkeit
### 13.1 Neues Menu-Item hinzufügen
```elixir
# In sidebar_menu/1 hinzufügen:
<.menu_item
href={~p"/new-feature"}
icon="hero-sparkles"
label={gettext("New Feature")}
/>
```
### 13.2 Neues Nested Menu hinzufügen
```elixir
<.menu_group
icon="hero-folder"
label={gettext("Documents")}
>
<.menu_subitem href={~p"/docs/contracts"} label={gettext("Contracts")} />
<.menu_subitem href={~p"/docs/invoices"} label={gettext("Invoices")} />
</.menu_group>
```
### 13.3 CSS-Anpassungen
**Sidebar-Breite ändern:**
```css
.sidebar {
width: 18rem; /* statt 16rem */
}
[data-sidebar-expanded="false"] .sidebar {
width: 5rem; /* statt 4rem */
}
```
**Transition-Dauer ändern:**
```css
.sidebar {
@apply transition-[width] duration-500; /* statt 300ms */
}
```
---
## 14. Abnahmekriterien
### 14.1 Funktional
- [ ] Logo ist immer 32px groß (expanded & collapsed)
- [ ] Toggle-Button wechselt Icon (Chevron-Left Right)
- [ ] Menü-Items zeigen Icons + Labels (expanded)
- [ ] Menü-Items zeigen nur Icons mit Tooltips (collapsed)
- [ ] Nested Menu: Details (expanded), Dropdown (collapsed)
- [ ] Footer am unteren Ende (Flexbox)
- [ ] Language-Selector nur expanded
- [ ] Theme-Toggle immer sichtbar
- [ ] User-Avatar zentriert, erste Buchstabe
- [ ] State in localStorage gespeichert
- [ ] State bleibt nach Reload erhalten
- [ ] Mobile: Hamburger öffnet Overlay-Drawer
- [ ] Mobile: Overlay schließt Drawer
### 14.2 Technisch
- [ ] Keine Custom CSS Variants verwendet
- [ ] Nur Tailwind + DaisyUI Klassen
- [ ] `data-sidebar-expanded` auf root
- [ ] CSS reagiert auf data-attribute
- [ ] JavaScript Hook implementiert
- [ ] localStorage funktioniert
- [ ] Keine Console-Errors
- [ ] Keine Console-Warnings
- [ ] Keine duplicate IDs
### 14.3 Visuell
- [ ] Smooth width transition (300ms)
- [ ] Labels fade smooth out/in
- [ ] Keine Layout-Shifts
- [ ] Keine Horizontal-Scrollbar
- [ ] Kein Flicker bei Transitions
- [ ] Hover-Effekte einheitlich
- [ ] Footer klebt am unteren Ende
- [ ] Responsive Breakpoints funktionieren
### 14.4 Accessibility
- [ ] Alle ARIA-Attribute korrekt
- [ ] Keyboard-Navigation funktioniert
- [ ] Screen-Reader-Test bestanden
- [ ] Color-Contrast 4.5:1
- [ ] Focus-Indikatoren sichtbar
- [ ] Lighthouse Score 95
### 14.5 Performance
- [ ] Keine Performance-Warnungen
- [ ] CSS-Transitions GPU-accelerated
- [ ] localStorage synchron
- [ ] Keine Memory-Leaks
---
## 15. Referenzen
### 15.1 Interne Dokumente
- `docs/sidebar-analysis-current-state.md` - Ist-Zustand Analyse
- `docs/daisyui-drawer-pattern.md` - DaisyUI Pattern Docs
- `docs/umsetzung-sidebar.md` - Implementierungs-Anleitung
- `CODE_GUIDELINES.md` - Projekt-Konventionen
### 15.2 Externe Dokumentation
- [DaisyUI Drawer](https://daisyui.com/components/drawer/)
- [DaisyUI Menu](https://daisyui.com/components/menu/)
- [DaisyUI Dropdown](https://daisyui.com/components/dropdown/)
- [Tailwind CSS Transitions](https://tailwindcss.com/docs/transition-property)
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
- [MDN: data-* attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*)
### 15.3 Design-Referenzen
- [Phoenix LiveView Docs](https://hexdocs.pm/phoenix_live_view/) - Navigation Pattern
- [GitHub Sidebar](https://github.com/) - Collapsed Icon Pattern
- [Discord Sidebar](https://discord.com/) - Tooltip Pattern
- [VS Code Sidebar](https://code.visualstudio.com/) - Toggle Icon Pattern
---
## 16. Change Log
| Version | Datum | Änderungen | Autor |
|---------|-------|------------|-------|
| 2.0 | 2025-12-16 | Initiale Version nach Analyse | Cursor AI |
---
## 17. Appendix
### A. DaisyUI Drawer Pattern (Kurzreferenz)
```html
<div class="drawer lg:drawer-open">
<input id="drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content">
<!-- Mobile Header -->
<label for="drawer" class="lg:hidden">Open</label>
<!-- Main Content -->
</div>
<div class="drawer-side">
<label for="drawer" class="drawer-overlay lg:hidden"></label>
<aside class="sidebar">
<!-- Sidebar Content -->
</aside>
</div>
</div>
```
### B. CSS Selector Beispiele
```css
/* Expanded (default) */
.sidebar { width: 16rem; }
.menu-label { opacity: 1; }
.expanded-only { display: block; }
/* Collapsed */
[data-sidebar-expanded="false"] .sidebar { width: 4rem; }
[data-sidebar-expanded="false"] .sidebar .menu-label { opacity: 0; }
[data-sidebar-expanded="false"] .sidebar .expanded-only { display: none; }
```
### C. localStorage Beispiel
```javascript
// Save
localStorage.setItem('sidebar-expanded', 'true');
// Load
const expanded = localStorage.getItem('sidebar-expanded') !== 'false';
// Check
if (localStorage.getItem('sidebar-expanded') === 'false') {
// Collapsed
}
```
### D. ARIA Beispiele
```html
<!-- Toggle-Button -->
<button
id="sidebar-toggle"
aria-label="Toggle sidebar"
aria-expanded="true"
aria-controls="main-sidebar"
>
<!-- Menu -->
<nav id="main-sidebar" aria-label="Main navigation">
<ul role="menubar">
<li role="none">
<a role="menuitem">Members</a>
</li>
</ul>
</nav>
<!-- Dropdown -->
<button aria-haspopup="menu" aria-expanded="false">
User Menu
</button>
```
---
**Ende der Spezifikation**