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
34 KiB
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-iconund.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-3zwischen Icon und Text
Collapsed State:
- Nur Icon (zentriert)
- Tooltip erscheint rechts (
tooltip-right) - Tooltip-Text aus
data-tipAttribut
Hover-Effekt:
- Einheitlich für expanded UND collapsed
- Standard DaisyUI:
hover:bg-base-300 - Keine Custom-Styles
Liste der Menü-Items:
- Members -
hero-users-/members - Users -
hero-user-circle-/users - Custom Fields -
hero-rectangle-group-/custom_fields - 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:
<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:
<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
dropdownmitdropdown-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-groupund.collapsed-menu-group - CSS zeigt/versteckt basierend auf
[data-sidebar-expanded]
Submenu-Items:
- Beitragsarten -
/contribution_types - Einstellungen -
/contribution_settings
1.5 Footer
Position:
- IMMER am unteren Ende der Sidebar
- Flexbox: Navigation mit
flex-1, Footer mitmt-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:
- Profile (
/users/:id) - Logout (
/sign-out)
- Profile (
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:
<div data-sidebar-expanded="true">
Verhalten:
- Beim Mount: Restore aus localStorage
- Beim Toggle: Update localStorage + data-attribute
- 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-openforciert 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
@applyDirektiven - 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:
[data-sidebar-expanded="false"] .sidebar .element {
/* Collapsed State Styles */
}
Beispiel-Implementierung in app.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-64wenn geöffnet
Desktop (≥ lg):
lg:drawer-openfür Persistence- Custom State-Management via data-attribute
- Width-Transition:
w-64↔w-16
Keine Konflikte:
lg:drawer-openwird NICHT durchdata-sidebar-expandedbeeinflusst- Collapsed State nur auf Desktop relevant
5. State-Management-Strategie
5.1 JavaScript Hook: SidebarState
Datei: assets/js/app.js
Verantwortlichkeiten:
- Mount: Restore State aus localStorage
- Toggle: Wechsle State, update localStorage + data-attribute
- ARIA: Update
aria-expandedauf Toggle-Button - Global Function: Expose
window.toggleSidebar()
Implementierung:
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:
<!-- 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:
sidebar/1- Root (deprecated, wird durch layouts.ex ersetzt)sidebar_content/1- Wrapper für alle Sidebar-Inhaltesidebar_header/1- Logo + Name + Togglesidebar_menu/1- Navigation Containermenu_item/1- Einfacher Menü-Eintragmenu_group/1- Nested Menu Containermenu_subitem/1- Submenu-Eintragsidebar_footer/1- Footer Containertheme_toggle/1- Theme Switchuser_menu/1- User Dropdown
6.3 Attribute Definition
Beispiel: menu_item/1
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:
<button
aria-label="Toggle sidebar"
aria-expanded="true"
aria-controls="main-sidebar"
>
User Menu:
<button
aria-label="User menu"
aria-haspopup="menu"
aria-expanded="false"
>
Icons (dekorativ):
<.icon name="hero-users" aria-hidden="true" />
Tooltips:
<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:
.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-contentaufbg-base-200: ✅ 7.2:1text-primaryaufbg-base-100: ✅ 5.1:1text-neutral-contentaufbg-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-acceleratedduration-300- Wahrnehmbar, aber schnellease-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:
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:
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
onclickHandler (durchtoggleSidebar()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-labelKlasse.sidebar-expanded-icon/.sidebar-collapsed-icon.expanded-menu-group/.collapsed-menu-group.expanded-only
HTML:
- Logo-Element
- Toggle-Button mit Icon-Swap
data-sidebar-expandedauf rootphx-hook="SidebarState"auf root
JavaScript:
Hooks.SidebarStateHookwindow.toggleSidebar()Function
11.3 Zu prüfen/behalten
DaisyUI:
- drawer Pattern (korrekt)
- drawer-toggle (Mobile)
- lg:drawer-open (Desktop)
- drawer-overlay (Mobile)
Accessibility:
- ARIA-Labels (größtenteils korrekt)
- role Attributes (korrekt)
- 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
# 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
<.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:
.sidebar {
width: 18rem; /* statt 16rem */
}
[data-sidebar-expanded="false"] .sidebar {
width: 5rem; /* statt 4rem */
}
Transition-Dauer ändern:
.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-expandedauf 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 Analysedocs/daisyui-drawer-pattern.md- DaisyUI Pattern Docsdocs/umsetzung-sidebar.md- Implementierungs-AnleitungCODE_GUIDELINES.md- Projekt-Konventionen
15.2 Externe Dokumentation
- DaisyUI Drawer
- DaisyUI Menu
- DaisyUI Dropdown
- Tailwind CSS Transitions
- WCAG 2.1 Guidelines
- MDN: data-* attributes
15.3 Design-Referenzen
- Phoenix LiveView Docs - Navigation Pattern
- GitHub Sidebar - Collapsed Icon Pattern
- Discord Sidebar - Tooltip Pattern
- VS Code Sidebar - 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)
<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
/* 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
// 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
<!-- 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