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

34 KiB
Raw Blame History

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

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:

<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 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

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:

<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:

[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-64 wenn geöffnet

Desktop (≥ lg):

  • lg:drawer-open für Persistence
  • Custom State-Management via data-attribute
  • Width-Transition: w-64w-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:

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:

  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

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-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:

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 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:

  • 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-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

15.3 Design-Referenzen


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