mitgliederverwaltung/docs/umsetzung-sidebar.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

39 KiB

Sidebar Neuimplementierung - Schritt-für-Schritt Anleitung

Erstellt: 2025-12-16
Status: Bereit zur Umsetzung
Strategie: Sequenzielle Tasks mit frischem Kontext pro Task


Übersicht

Diese Anleitung zerlegt die komplexe Sidebar-Implementierung in 13 beherrschbare Subtasks. Jeder Task wird mit einem frischen Cursor-Agent im Auto-Mode (oder Sonnet 4.5 für komplexere Aufgaben) umgesetzt.

Ziel: Saubere, wartbare Sidebar-Implementierung nahe am DaisyUI-Standard ohne Custom-Variants oder Speziallösungen.


Task 1: Vorbereitung & Analyse

Agent: Sonnet 4.5
Geschätzte Dauer: 10 Minuten

Prompt:

Analysiere die aktuelle Sidebar-Implementierung in diesem Projekt und erstelle einen detaillierten Bericht:

1. Liste alle Dateien auf, die mit der Sidebar zusammenhängen
2. Dokumentiere die aktuelle Struktur (HTML, CSS, JavaScript)
3. Identifiziere alle Custom-CSS-Klassen und Variants
4. Dokumentiere die JavaScript-Hooks und ihre Funktionen
5. Erstelle eine Liste aller Dependencies (DaisyUI-Komponenten, Tailwind-Klassen)

Speichere den Bericht als `docs/sidebar-analysis-current-state.md`

Ziel: Vollständiges Verständnis des Ist-Zustands vor der Neuimplementierung.

Acceptance Criteria:

  • Alle Sidebar-bezogenen Dateien identifiziert
  • Aktuelle Implementierung dokumentiert
  • Custom CSS und JavaScript dokumentiert
  • Bericht als Markdown gespeichert

Task 2: DaisyUI Drawer Pattern Recherche

Agent: Sonnet 4.5 (wegen Web-Recherche)
Geschätzte Dauer: 15 Minuten

Prompt:

Recherchiere und dokumentiere das Standard DaisyUI Drawer Pattern:

1. Lies die DaisyUI Dokumentation für die `drawer` Komponente
2. Finde Best-Practice-Beispiele für responsive Sidebars mit DaisyUI
3. Dokumentiere, wie drawer-toggle funktioniert
4. Dokumentiere, wie drawer-open für Desktop funktioniert
5. Erstelle Code-Beispiele für:
   - Mobile Drawer (overlay)
   - Desktop Sidebar (persistent)
   - Kombination beider

Speichere die Dokumentation als `docs/daisyui-drawer-pattern.md`

Wichtig: Verwende KEINE custom CSS variants - nur Standard DaisyUI und Tailwind.

Acceptance Criteria:

  • DaisyUI Drawer Pattern dokumentiert
  • Mobile und Desktop Patterns verstanden
  • Code-Beispiele vorhanden
  • Dokumentation gespeichert

Task 3: Anforderungsdefinition & Design

Agent: Sonnet 4.5
Geschätzte Dauer: 20 Minuten

Prompt:

Erstelle eine präzise Anforderungsspezifikation für die Sidebar basierend auf folgenden Anforderungen:

## Funktionale Anforderungen:

1. **Logo:** 
   - Immer sichtbar, gleiche Größe (32px / size-8)
   - Sowohl im expanded als auch collapsed State
   - Keine zwei verschiedenen Logo-Elemente

2. **Toggle-Button:**
   - Nur auf Desktop sichtbar
   - Icon-Swap: Chevron-left (expanded) ↔ Chevron-right (collapsed)
   - Immer erreichbar

3. **Menü-Items:**
   - Expanded: Icons + Text-Labels
   - Collapsed: Nur Icons mit Tooltips (tooltip-right)
   - Einheitlicher Hover-Effekt

4. **Nested Menu "Beiträge":**
   - Expanded: Standard <details> mit <summary>
   - Collapsed: DaisyUI dropdown dropdown-right
   - Nur EIN Hover-Effekt, kein doppelter

5. **Footer:**
   - IMMER am unteren Ende der Sidebar (via Flexbox)
   - Theme-Toggle (immer sichtbar)
   - Language-Selector (nur expanded)
   - User-Menu mit Avatar (dropdown-top dropdown-end)
   - Avatar: Erste Buchstabe, zentriert

6. **State Persistence:**
   - localStorage: 'sidebar-expanded'
   - data-attribute: [data-sidebar-expanded="true"|"false"]

7. **Responsive:**
   - Mobile: Standard DaisyUI Drawer Overlay
   - Desktop: Fixed Sidebar mit smooth width transition

## Aufgaben:

1. Erstelle Wireframes (als ASCII-Art) für:
   - Desktop Expanded
   - Desktop Collapsed
   - Mobile mit Overlay

2. Liste alle benötigten DaisyUI-Komponenten auf

3. Definiere CSS-Strategie:
   - Nur Tailwind + DaisyUI
   - KEINE custom variants (@custom-variant)
   - State-Management via data-attribute selectors

4. Definiere State-Management-Strategie:
   - JavaScript Hook
   - localStorage
   - CSS reactions

Speichere als `docs/sidebar-requirements-v2.md`

Acceptance Criteria:

  • Anforderungen klar dokumentiert
  • Wireframes erstellt
  • Komponenten-Liste vorhanden
  • CSS-Strategie definiert
  • State-Management definiert

Task 4: CSS Foundation

Agent: Auto-Mode
Geschätzte Dauer: 15 Minuten

Prompt:

Erstelle die CSS-Grundlage für die neue Sidebar in `assets/css/app.css`:

## Schritt 1: Aufräumen
1. Entferne ALLE bestehenden Custom CSS Variants für Sidebar:
   - @custom-variant is-drawer-open
   - @custom-variant is-drawer-close
   - Alle .is-drawer-* Regeln

2. Entferne alte Sidebar-spezifische Custom-Klassen

## Schritt 2: Neue CSS-Regeln erstellen

Erstelle CSS basierend auf `[data-sidebar-expanded]` Attribut:

```css
/* Desktop Sidebar Base */
.sidebar {
  @apply flex flex-col bg-base-200 min-h-screen;
  @apply transition-[width] duration-300 ease-in-out;
  width: 16rem; /* Expanded: w-64 */
}

/* Collapsed State */
[data-sidebar-expanded="false"] .sidebar {
  width: 4rem; /* Collapsed: w-16 */
}

/* Text Labels - Hide in Collapsed State */
.menu-label {
  @apply transition-all duration-200 whitespace-nowrap;
}

[data-sidebar-expanded="false"] .sidebar .menu-label {
  @apply opacity-0 w-0 overflow-hidden pointer-events-none;
}

/* Toggle Button Icon Swap */
.sidebar-collapsed-icon {
  @apply hidden;
}

[data-sidebar-expanded="false"] .sidebar-expanded-icon {
  @apply hidden;
}

[data-sidebar-expanded="false"] .sidebar-collapsed-icon {
  @apply block;
}

/* Menu Groups - 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;
}

/* Tooltip - 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 */
[data-sidebar-expanded="false"] .sidebar .menu > li > a,
[data-sidebar-expanded="false"] .sidebar .menu > li > button {
  @apply justify-center px-0;
}

Schritt 3: Testen

  • Kompiliere CSS: mix assets.build
  • Prüfe auf Fehler
  • Stelle sicher, dass keine alten Custom-Variants mehr existieren

Verwende AUSSCHLIESSLICH:

  • Tailwind @apply
  • Standard DaisyUI Klassen
  • CSS attribute selectors für state

### Acceptance Criteria:
- ✅ Alte Custom Variants entfernt
- ✅ Neue CSS-Regeln erstellt
- ✅ CSS kompiliert ohne Fehler
- ✅ Nur Standard Tailwind/DaisyUI verwendet

---

## Task 5: Layout-Struktur

**Agent:** Auto-Mode  
**Geschätzte Dauer:** 20 Minuten

### Prompt:

Implementiere die grundlegende Layout-Struktur für die Sidebar in lib/mv_web/components/layouts.ex:

Anforderungen:

  1. Nutze DaisyUI drawer + drawer-open Pattern
  2. Ein Container für Mobile UND Desktop (keine Duplikate!)
  3. @inner_block darf nur EINMAL gerendert werden
  4. data-sidebar-expanded Attribut auf root
  5. phx-hook="SidebarState" auf root
  6. id="main-sidebar" auf main content (für Tests)

Implementierung:

Ersetze die bestehende app/1 Funktion mit:

def app(assigns) do
  club_name = get_club_name()
  assigns = assign(assigns, :club_name, club_name)

  ~H"""
  <%= if @current_user do %>
    <div id="app-layout" class="drawer lg:drawer-open" data-sidebar-expanded="true" phx-hook="SidebarState">
      <input id="mobile-drawer" type="checkbox" class="drawer-toggle" />
      
      <div class="drawer-content flex flex-col">
        <!-- Mobile Header (only visible on mobile) -->
        <header class="lg:hidden sticky top-0 z-10 navbar bg-base-100 shadow-sm">
          <label for="mobile-drawer" class="btn btn-square btn-ghost" aria-label={gettext("Open navigation menu")}>
            <.icon name="hero-bars-3" class="size-6" aria-hidden="true" />
          </label>
          <span class="font-bold">{@club_name}</span>
        </header>
        
        <!-- Main Content (shared between mobile and desktop) -->
        <main id="main-sidebar" class="px-4 py-8 sm:px-6 lg:px-8">
          <div class="mx-auto space-y-4 max-full">
            {render_slot(@inner_block)}
          </div>
        </main>
      </div>

      <div class="drawer-side z-20">
        <label for="mobile-drawer" aria-label="close sidebar" class="drawer-overlay lg:hidden"></label>
        <!-- Sidebar Content wird in nächstem Task implementiert -->
        <aside class="sidebar">
          <div class="p-4">
            <p>Sidebar Placeholder</p>
          </div>
        </aside>
      </div>
    </div>
  <% else %>
    <!-- Not logged in -->
    <main class="px-4 py-8 sm:px-6">
      <div class="mx-auto space-y-4 max-full">
        {render_slot(@inner_block)}
      </div>
    </main>
  <% end %>

  <.flash_group flash={@flash} />
  """
end

Wichtig:

  • @inner_block wird nur EINMAL gerendert (im drawer-content)
  • Mobile und Desktop teilen sich den gleichen main-content
  • Sidebar-Inhalt kommt in späteren Tasks

Testen:

  1. Kompiliere: mix compile
  2. Starte Server: mix phx.server
  3. Prüfe: Keine duplicate ID Fehler in Browser Console
  4. Prüfe: Layout funktioniert responsive
  5. Prüfe: Mobile Header erscheint nur auf Mobile

### Acceptance Criteria:
- ✅ Layout-Struktur implementiert
- ✅ Keine duplicate IDs
- ✅ @inner_block nur einmal gerendert
- ✅ Responsive funktioniert
- ✅ Kompiliert ohne Fehler

---

## Task 6: Sidebar Header Komponente

**Agent:** Auto-Mode  
**Geschätzte Dauer:** 20 Minuten

### Prompt:

Implementiere die Sidebar-Header-Komponente in lib/mv_web/components/layouts/sidebar.ex:

Anforderungen:

  1. Logo:

    • Immer size-8 (32px)
    • Immer sichtbar (kein Hide)
    • Nur EIN Logo-Element
    • Pfad: /images/mila.svg
  2. Club-Name:

    • Text-Label mit CSS-Klasse menu-label
    • Wird via CSS ausgeblendet (collapsed)
    • text-lg font-bold truncate
  3. Toggle-Button:

    • Nur auf Desktop sichtbar (responsive: hidden lg:flex oder lg:block)
    • DaisyUI-konforme Button-Variante wählen:
      • Option A: btn btn-ghost btn-sm btn-square (minimal, icon-only)
      • Option B: btn btn-ghost btn-sm (mit etwas Padding)
      • Option C: btn btn-ghost btn-circle (rund, falls besser zum Design passt)
    • Icon-Strategie (wähle die beste Variante):
      • Option A: Zwei Icons mit CSS-Klassen (.sidebar-expanded-icon / .sidebar-collapsed-icon)
      • Option B: Ein Icon mit CSS transform (rotate bei collapsed)
      • Option C: Ein Icon mit CSS content-swap (via ::before/::after)
    • Event-Handler (wähle passende Variante):
      • Option A: onclick="toggleSidebar()" (wenn global function vorhanden)
      • Option B: phx-click="toggle_sidebar" (wenn LiveView event)
      • Option C: phx-hook="SidebarToggle" (wenn Hook-basiert)
    • ARIA: aria-label={gettext("Toggle sidebar")} und aria-expanded (wird via JS gesetzt)

Design-Überlegungen:

  • Button sollte sich harmonisch in den Header einfügen
  • Position: Rechts im Header (ml-auto)
  • Icon-Größe: size-5 oder size-4 (je nach Button-Größe)
  • Icon-Typ: Chevron (left/right) oder Arrow (left/right) - wähle das passendere
  • Hover-Effekt: Standard DaisyUI btn-ghost hover

Implementierung:

Erstelle sidebar_header/1 Funktion mit:

  • Flexbox-Layout für Header (Logo + Name + Toggle)
  • Responsive Toggle-Button
  • Icon-Swap-Mechanismus (wähle beste Variante)
  • Korrekte ARIA-Attribute

Empfehlung:

Für DaisyUI-Konformität und Wartbarkeit:

  • Button: btn btn-ghost btn-sm btn-square (icon-only, minimal)
  • Icons: Zwei separate Icons mit CSS-Klassen (einfach, klar)
  • Event: onclick="toggleSidebar()" (wenn JS Hook vorhanden) ODER phx-click (wenn LiveView)

Beispiel-Struktur (als Orientierung):

defp sidebar_header(assigns) do
  ~H"""
  <div class="flex items-center gap-3 p-4 border-b border-base-300">
    <!-- Logo -->
    <img src={~p"/images/mila.svg"} alt="Mila Logo" class="size-8 shrink-0" />
    
    <!-- Club Name -->
    <span class="menu-label text-lg font-bold truncate">
      {@club_name}
    </span>
    
    <!-- Toggle Button (Desktop only) -->
    <%= unless @mobile do %>
      <button
        type="button"
        id="sidebar-toggle"
        class="hidden lg:flex ml-auto btn btn-ghost btn-sm btn-square"
        aria-label={gettext("Toggle sidebar")}
        onclick="toggleSidebar()"
      >
        <!-- Icon-Implementierung nach gewählter Strategie -->
      </button>
    <% end %>
  </div>
  """
end

Integration in layouts.ex:

Ersetze den Sidebar Placeholder in layouts.ex:

<aside class="sidebar">
  <.sidebar_content current_user={@current_user} club_name={@club_name} mobile={false} />
</aside>

Testen:

  1. Kompiliere: mix compile
  2. Starte Server: mix phx.server
  3. Prüfe:
    • Logo ist immer 32px groß
    • Toggle-Button erscheint nur auf Desktop
    • Button-Design passt zum Rest der Sidebar
    • Icon wechselt beim Toggle
    • Hover-Effekt funktioniert
    • ARIA-Attribute korrekt
    • Club-Name verschwindet beim Collapse (wenn toggleSidebar() funktioniert)

### Acceptance Criteria:
- ✅ sidebar_header Komponente erstellt
- ✅ Logo immer gleich groß
- ✅ Toggle-Button nur auf Desktop
- ✅ DaisyUI-konforme Button-Klassen verwendet
- ✅ Icon-Swap funktioniert (egal welche Variante gewählt wurde)
- ✅ Event-Handler funktioniert
- ✅ Design fügt sich harmonisch ein
- ✅ Keine Layout-Breaks

---

## Task 7: Sidebar Navigation - Flat Items

**Agent:** Auto-Mode  
**Geschätzte Dauer:** 25 Minuten

### Prompt:

Implementiere einfache Menü-Items (flat, ohne Nesting) in lib/mv_web/components/layouts/sidebar.ex:

Menü-Items:

  • Members (/members)
  • Users (/users)
  • Custom Fields (/custom_fields)
  • Settings (Placeholder)

Anforderungen:

  1. Expanded State:

    • Icon + Text-Label
    • Standard DaisyUI menu hover
  2. **Collapsed State:**gf

    • Nur Icon
    • Tooltip erscheint rechts (tooltip-right)
    • Tooltip-Text aus data-tip
  3. Hover-Effekt:

    • Einheitlich (Standard DaisyUI menu)
    • Keine custom hover-styles
  4. Active State:

    • Highlight für current_path (optional)

Implementierung:

Füge in sidebar.ex hinzu:

defp sidebar_menu(assigns) do
  ~H"""
  <ul class="menu flex-1 w-full p-2" role="menubar">
    <.menu_item 
      href={~p"/members"} 
      icon="hero-users" 
      label={gettext("Members")} 
    />
    <.menu_item 
      href={~p"/users"} 
      icon="hero-user-circle" 
      label={gettext("Users")} 
    />
    <.menu_item 
      href={~p"/custom_fields"} 
      icon="hero-rectangle-group" 
      label={gettext("Custom Fields")} 
    />
    <.menu_item 
      href="#" 
      icon="hero-cog-6-tooth" 
      label={gettext("Settings")} 
    />
  </ul>
  """
end

attr :href, :string, required: true
attr :icon, :string, required: true
attr :label, :string, required: true

defp menu_item(assigns) do
  ~H"""
  <li role="none">
    <.link
      navigate={@href}
      class="flex items-center gap-3 tooltip tooltip-right"
      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

Ersetze in sidebar_content:

<!-- Navigation -->
<%= if @current_user do %>
  <.sidebar_menu />
<% end %>

Testen:

  1. Kompiliere und starte Server
  2. Prüfe Expanded State:
    • Icons + Labels sichtbar
    • Hover funktioniert
  3. Toggle zu Collapsed:
    • Nur Icons sichtbar
    • Tooltips erscheinen bei Hover
    • Tooltips zeigen richtigen Text
  4. Prüfe:
    • Einheitlicher Hover-Effekt
    • Navigation funktioniert (Links klickbar)

### Acceptance Criteria:
- ✅ menu_item Komponente erstellt
- ✅ 4 Menü-Items implementiert
- ✅ Tooltips funktionieren (nur collapsed)
- ✅ Icons sichtbar in beiden States
- ✅ Hover-Effekt einheitlich
- ✅ Navigation funktioniert

---

## Task 8: Sidebar Navigation - Nested Menu

**Agent:** Sonnet 4.5 (komplexer)  
**Geschätzte Dauer:** 30 Minuten

### Prompt:

Implementiere das verschachtelte "Beiträge"-Menü (Contributions) mit ZWEI verschiedenen Darstellungen je nach State.

Problem:

Das Nested Menu muss sich anders verhalten als flat items:

  • Expanded: Nutzt
    mit für auf/zuklappbar
  • Collapsed: Nutzt DaisyUI dropdown für Flyout rechts vom Icon

Anforderungen:

  1. Nur EIN Hover-Effekt (nicht doppelt)
  2. Flyout erscheint rechts vom Icon im collapsed state
  3. Smooth transitions zwischen states
  4. Submenu-Items: Beitragsarten, Einstellungen

Implementierung:

Füge in sidebar.ex hinzu:

attr :icon, :string, required: true
attr :label, :string, required: true
slot :inner_block, required: true

defp menu_group(assigns) do
  ~H"""
  <li role="none" class="menu-group">
    <!-- Expanded Mode: Details/Summary -->
    <details class="expanded-menu-group">
      <summary class="flex items-center gap-3" role="menuitem" aria-haspopup="true">
        <.icon name={@icon} class="size-5 shrink-0" aria-hidden="true" />
        <span class="menu-label">{@label}</span>
      </summary>
      <ul role="menu" class="ml-4">
        {render_slot(@inner_block)}
      </ul>
    </details>
    
    <!-- Collapsed Mode: Dropdown -->
    <div class="collapsed-menu-group dropdown dropdown-right">
      <button 
        type="button"
        tabindex="0" 
        class="flex items-center justify-center w-full p-2 rounded-lg hover:bg-base-300 tooltip tooltip-right"
        data-tip={@label}
        aria-haspopup="menu"
        aria-label={@label}
      >
        <.icon name={@icon} class="size-5" aria-hidden="true" />
      </button>
      <ul 
        tabindex="0" 
        class="dropdown-content menu bg-base-100 rounded-box shadow-lg z-50 min-w-48 p-2"
        role="menu"
      >
        <li class="menu-title">{@label}</li>
        {render_slot(@inner_block)}
      </ul>
    </div>
  </li>
  """
end

attr :href, :string, required: true
attr :label, :string, required: true

defp menu_subitem(assigns) do
  ~H"""
  <li role="none">
    <.link navigate={@href} role="menuitem">
      {@label}
    </.link>
  </li>
  """
end

Füge in sidebar_menu nach den flat items hinzu:

<!-- Nested Menu: Contributions -->
<.menu_group 
  icon="hero-currency-dollar" 
  label={gettext("Contributions")}
>
  <.menu_subitem href="/contribution_types" label={gettext("Contribution Types")} />
  <.menu_subitem href="/contribution_settings" label={gettext("Settings")} />
</.menu_group>

CSS-Prüfung:

Stelle sicher, dass in app.css folgende Regeln existieren:

/* Expanded: Show details, hide dropdown */
.expanded-menu-group {
  @apply block;
}
.collapsed-menu-group {
  @apply hidden;
}

/* Collapsed: Hide details, show dropdown */
[data-sidebar-expanded="false"] .sidebar .expanded-menu-group {
  @apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .collapsed-menu-group {
  @apply block;
}

Testen:

  1. Expanded State:

    • Details/Summary erscheint
    • Klick öffnet/schließt Submenu
    • Submenu-Items sind klickbar
    • NUR EIN Hover-Effekt auf summary
  2. Collapsed State:

    • Dropdown erscheint
    • Flyout öffnet RECHTS vom Icon
    • Menu-Title "Contributions" erscheint
    • Submenu-Items sind klickbar
    • NUR EIN Hover-Effekt auf button
  3. Toggle zwischen States:

    • Smooth Transition
    • Keine Glitches
    • Keine doppelten Elemente sichtbar

Debugging:

Falls Probleme auftreten:

  • Browser DevTools: Prüfe, welche Elemente .expanded-menu-group oder .collapsed-menu-group haben
  • Prüfe data-sidebar-expanded Attribut im HTML
  • Prüfe z-index des Dropdowns (z-50)
  • Prüfe, ob dropdown-right funktioniert

### Acceptance Criteria:
- ✅ menu_group und menu_subitem implementiert
- ✅ Details funktioniert (expanded)
- ✅ Dropdown funktioniert (collapsed)
- ✅ Flyout erscheint rechts
- ✅ Nur EIN Hover-Effekt
- ✅ Keine visuellen Glitches

---

## Task 9: Sidebar Footer mit Flexbox

**Agent:** Auto-Mode  
**Geschätzte Dauer:** 25 Minuten

### Prompt:

Implementiere den Sidebar-Footer mit korrekter Positionierung am unteren Ende.

Flexbox-Struktur:

Die Sidebar muss als Flexbox-Container funktionieren:

  1. .sidebar: flex flex-col (bereits vorhanden)
  2. Navigation: flex-1 (nimmt verfügbaren Platz)
  3. Footer: mt-auto (wird nach unten geschoben)
  1. Language Selector:

    • Nur im expanded state sichtbar (.expanded-only)
    • DaisyUI select
    • Form mit POST zu /locale
  2. Theme Toggle:

    • IMMER sichtbar
    • Horizontal: Sun Icon + Toggle + Moon Icon
    • DaisyUI toggle + theme-controller
  3. User Menu:

    • DaisyUI dropdown
    • dropdown-top dropdown-end (öffnet nach oben)
    • Avatar: Erste Buchstabe, zentriert, rund
    • Email nur expanded
    • Dropdown: Profile + Logout

Implementierung:

defp sidebar_footer(assigns) do
  ~H"""
  <div class="mt-auto p-4 border-t border-base-300 space-y-4">
    <!-- Language Selector (nur expanded) -->
    <form method="post" action={~p"/locale"} class="expanded-only">
      <input type="hidden" name="_csrf_token" value={get_csrf_token()} />
      <select
        name="locale"
        onchange="this.form.submit()"
        class="select select-sm w-full"
        aria-label={gettext("Select language")}
      >
        <option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
        <option value="en" selected={Gettext.get_locale() == "en"}>English</option>
      </select>
    </form>
    
    <!-- Theme Toggle (immer sichtbar) -->
    <.theme_toggle />
    
    <!-- User Menu -->
    <.user_menu current_user={@current_user} />
  </div>
  """
end

defp theme_toggle(assigns) do
  ~H"""
  <label class="flex items-center gap-2 cursor-pointer justify-center">
    <.icon name="hero-sun" class="size-5" aria-hidden="true" />
    <input 
      type="checkbox" 
      value="dark" 
      class="toggle toggle-sm theme-controller"
      aria-label={gettext("Toggle dark mode")}
    />
    <.icon name="hero-moon" class="size-5" aria-hidden="true" />
  </label>
  """
end

defp user_menu(assigns) do
  ~H"""
  <div class="dropdown dropdown-top dropdown-end w-full">
    <button
      type="button"
      tabindex="0"
      class="btn btn-ghost w-full flex items-center gap-3"
      aria-label={gettext("User menu")}
      aria-haspopup="menu"
    >
      <!-- Avatar: Zentriert, erste Buchstabe -->
      <div class="avatar placeholder">
        <div class="w-8 h-8 rounded-full bg-neutral text-neutral-content flex items-center justify-center">
          <span class="text-sm font-medium">
            {String.first(String.to_string(@current_user.email)) |> String.upcase()}
          </span>
        </div>
      </div>
      <span class="menu-label truncate flex-1 text-left">{String.to_string(@current_user.email)}</span>
    </button>
    <ul 
      tabindex="0" 
      class="dropdown-content menu bg-base-100 rounded-box shadow-lg z-50 w-52 p-2"
      role="menu"
    >
      <li role="none">
        <.link navigate={~p"/users/#{@current_user.id}"} role="menuitem">
          {gettext("Profile")}
        </.link>
      </li>
      <li role="none">
        <.link href={~p"/sign-out"} role="menuitem">
          {gettext("Logout")}
        </.link>
      </li>
    </ul>
  </div>
  """
end

Ersetze in sidebar_content den Footer-Placeholder:

<!-- Footer -->
<%= if @current_user do %>
  <.sidebar_footer current_user={@current_user} />
<% end %>

Prüfe, dass .sidebar_menu flex-1 hat:

<ul class="menu flex-1 w-full p-2" role="menubar">

Testen:

  1. Positionierung:

    • Footer klebt am unteren Ende
    • Auch bei wenigen Menu-Items
    • Navigation wächst/schrumpft dynamisch
  2. Language Selector:

    • Erscheint nur expanded
    • Verschwindet collapsed
    • Form funktioniert (Sprache wechselt)
  3. Theme Toggle:

    • Immer sichtbar
    • Icons horizontal ausgerichtet
    • Toggle funktioniert
  4. User Menu:

    • Avatar zentriert (auch collapsed)
    • Erste Buchstabe korrekt
    • Email verschwindet collapsed
    • Dropdown öffnet nach oben
    • Profile + Logout Links funktionieren
  5. Collapsed State:

    • Footer bleibt am unteren Ende
    • Avatar zentriert
    • Theme Toggle funktioniert

### Acceptance Criteria:
- ✅ Footer am unteren Ende (Flexbox)
- ✅ Language Selector nur expanded
- ✅ Theme Toggle immer sichtbar
- ✅ User Menu mit Avatar
- ✅ Avatar zentriert in beiden States
- ✅ Dropdown öffnet nach oben
- ✅ Alle Links funktionieren

---

## Task 10: JavaScript State Management

**Agent:** Auto-Mode  
**Geschätzte Dauer:** 15 Minuten

### Prompt:

Implementiere den vereinfachten SidebarState Hook in assets/js/app.js.

Ziel:

Einfaches, robustes State-Management OHNE komplexe Event-Listener oder DOM-Manipulation.

Anforderungen:

  1. State in localStorage speichern
  2. State via data-attribute auf HTML-Element setzen
  3. Global toggleSidebar() Funktion bereitstellen
  4. aria-expanded auf Toggle-Button aktualisieren
  5. KEINE Checkbox-Manipulation
  6. KEINE Tab-Index-Verwaltung
  7. KEINE komplexen Event-Listener

Implementierung:

Füge in assets/js/app.js zu den Hooks hinzu:

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

Stelle sicher, dass der Hook registriert ist:

let liveSocket = new LiveSocket("/live", Socket, {
  // ... existing config ...
  hooks: Hooks
})

Testen:

  1. Initial State:

    • Sidebar ist expanded (default)
    • localStorage wird gesetzt
  2. Toggle:

    • Klick auf Toggle-Button
    • Sidebar animiert zu collapsed
    • localStorage wird aktualisiert
    • Icon wechselt (Chevron-left ↔ Chevron-right)
  3. Persistence:

    • Toggle zu collapsed
    • Seite neu laden
    • Sidebar bleibt collapsed
  4. Browser Console:

    • Keine Fehler
    • window.toggleSidebar() ist aufrufbar
    • localStorage.getItem('sidebar-expanded') zeigt korrekten Wert
  5. ARIA:

    • Toggle-Button hat aria-expanded="true" oder "false"
    • Wert ändert sich beim Toggle

Debugging:

Falls Probleme:

// In Browser Console:
console.log(document.documentElement.dataset.sidebarExpanded);
console.log(localStorage.getItem('sidebar-expanded'));
window.toggleSidebar(); // Manuell testen

### Acceptance Criteria:
- ✅ Hook implementiert
- ✅ State wird gespeichert (localStorage)
- ✅ Toggle funktioniert
- ✅ CSS reagiert auf data-attribute
- ✅ ARIA korrekt gesetzt
- ✅ Keine Console-Fehler
- ✅ Persistence funktioniert

---

## Task 11: Mobile Header Integration

**Agent:** Auto-Mode  
**Geschätzte Dauer:** 10 Minuten

### Prompt:

Prüfe und optimiere die Mobile-Header-Integration.

Anforderungen:

  1. Mobile Header ist bereits in layouts.ex implementiert
  2. Hamburger-Menü öffnet Sidebar
  3. Header nur auf Mobile sichtbar (lg:hidden)
  4. Overlay schließt Sidebar

Zu prüfen:

Stelle sicher, dass in layouts.ex folgendes korrekt ist:

<header class="lg:hidden sticky top-0 z-10 navbar bg-base-100 shadow-sm">
  <label for="mobile-drawer" class="btn btn-square btn-ghost" aria-label={gettext("Open navigation menu")}>
    <.icon name="hero-bars-3" class="size-6" aria-hidden="true" />
  </label>
  <span class="font-bold">{@club_name}</span>
</header>

Und im drawer-side:

<label for="mobile-drawer" aria-label="close sidebar" class="drawer-overlay lg:hidden"></label>

Testen:

  1. Mobile View (< 1024px):

    • Header erscheint am oberen Rand
    • Hamburger-Icon sichtbar
    • Club-Name neben Hamburger
  2. Hamburger-Click:

    • Sidebar schiebt von links rein
    • Overlay verdunkelt Content
    • Sidebar ist vollständig sichtbar
  3. Overlay-Click:

    • Sidebar schließt sich
    • Overlay verschwindet
    • Content wieder normal
  4. Desktop View (≥ 1024px):

    • Header verschwindet
    • Sidebar ist persistent sichtbar
    • Kein Hamburger-Icon
    • Toggle-Button erscheint in Sidebar
  5. Responsive Breakpoint:

    • Smooth Transition bei Resize
    • Keine Layout-Breaks
    • Sidebar-State bleibt erhalten

Optional: CSS-Verbesserungen

Falls Mobile-Sidebar zu schmal ist, füge in app.css hinzu:

/* Mobile Drawer Width */
@media (max-width: 1023px) {
  .drawer-side .sidebar {
    width: 16rem; /* w-64 auch auf Mobile */
  }
}

### Acceptance Criteria:
- ✅ Mobile Header funktioniert
- ✅ Hamburger öffnet Sidebar
- ✅ Overlay schließt Sidebar
- ✅ Header nur auf Mobile
- ✅ Responsive funktioniert
- ✅ Keine Layout-Breaks

---

## Task 12: Tests & Accessibility

**Agent:** Sonnet 4.5  
**Geschätzte Dauer:** 30 Minuten

### Prompt:

Schreibe und aktualisiere Tests für die neue Sidebar-Implementierung in test/mv_web/components/layouts/sidebar_test.exs.

Test-Kategorien:

1. Component Tests

describe "sidebar_header/1" do
  test "renders logo with correct size" do
    # Logo ist size-8 (32px)
    # Logo ist immer sichtbar
  end
  
  test "renders club name" do
    # Club name ist vorhanden
  end
  
  test "renders toggle button for desktop" do
    # Toggle button hat beide Icons
    # Toggle button ist nicht mobile
  end
end

describe "menu_item/1" do
  test "renders icon and label" do
    # Icon ist sichtbar
    # Label ist vorhanden
  end
  
  test "has tooltip with correct text" do
    # data-tip attribute gesetzt
  end
  
  test "has correct link" do
    # navigate attribute korrekt
  end
end

describe "menu_group/1" do
  test "renders expanded menu group" do
    # details/summary vorhanden
  end
  
  test "renders collapsed menu group" do
    # dropdown vorhanden
  end
  
  test "renders submenu items" do
    # Inner_block items gerendert
  end
end

describe "sidebar_footer/1" do
  test "renders at bottom of sidebar" do
    # mt-auto vorhanden
  end
  
  test "renders theme toggle" do
    # Toggle ist immer sichtbar
  end
  
  test "renders language selector in expanded only" do
    # expanded-only Klasse
  end
  
  test "renders user menu with avatar" do
    # Avatar vorhanden
    # Erste Buchstabe korrekt
  end
end

2. Integration Tests

describe "sidebar state management" do
  test "sidebar starts expanded by default" do
    # data-sidebar-expanded="true"
  end
  
  test "toggle button changes state" do
    # Simulate toggle
    # data-sidebar-expanded ändert sich
  end
  
  test "no duplicate IDs in layout" do
    # field-visibility-dropdown nur einmal
    # Keine duplicate IDs
  end
end

describe "mobile drawer" do
  test "mobile header renders on small screens" do
    # lg:hidden Klasse
  end
  
  test "drawer toggle opens sidebar" do
    # drawer-toggle funktioniert
  end
end

3. Accessibility Tests

describe "accessibility" do
  test "sidebar has aria-label" do
    # aria-label="Main navigation"
  end
  
  test "toggle button has aria-label and aria-expanded" do
    # aria-label vorhanden
    # aria-expanded="true" or "false"
  end
  
  test "menu items have role attributes" do
    # role="menubar", "menuitem", "menu"
  end
  
  test "icons have aria-hidden" do
    # Dekorative Icons: aria-hidden="true"
  end
  
  test "user menu has aria-haspopup" do
    # aria-haspopup="menu"
  end
end

4. Regression Tests

describe "regression tests" do
  test "no duplicate profile links" do
    # Nur ein Profile-Link im DOM
  end
  
  test "nested menu has only one hover effect" do
    # Nicht doppelter Hover
  end
  
  test "tooltips only visible when collapsed" do
    # CSS opacity rules
  end
end

Testen:

Führe alle Tests aus:

mix test test/mv_web/components/layouts/sidebar_test.exs

Prüfe:

  • Alle Tests bestehen
  • Keine Warnungen
  • Test-Coverage akzeptabel

Falls Tests fehlschlagen:

  1. Analysiere Fehler
  2. Fixe Code oder Test
  3. Wiederhole bis alle grün sind

Accessibility Tools:

Optional: Nutze Browser DevTools

  • Lighthouse Accessibility Audit
  • Axe DevTools Extension
  • WAVE Extension

### Acceptance Criteria:
- ✅ Component Tests geschrieben
- ✅ Integration Tests geschrieben
- ✅ Accessibility Tests geschrieben
- ✅ Regression Tests geschrieben
- ✅ Alle Tests bestehen
- ✅ Keine Test-Warnungen

---

## Task 13: Visual Testing & Bug Fixes

**Agent:** Manuell (+ Sonnet 4.5 für Fixes)  
**Geschätzte Dauer:** 30 Minuten

### Checklist für manuelle Tests:

Desktop - Expanded State

  • Logo ist 32px groß (size-8)
  • Logo ist zentriert vertikal
  • Club-Name ist sichtbar neben Logo
  • Toggle-Button ist sichtbar
  • Toggle-Button zeigt Chevron-Left Icon
  • Alle Menü-Items zeigen Icon + Text
  • Beiträge-Menü ist als Details/Summary dargestellt
  • Footer ist am unteren Ende
  • Language-Selector ist sichtbar
  • Theme-Toggle ist horizontal (Sun + Toggle + Moon)
  • User-Avatar ist rund und zentriert
  • Email ist neben Avatar sichtbar

Desktop - Collapsed State

  • Logo ist 32px groß (gleich wie expanded!)
  • Logo ist zentriert vertikal UND horizontal
  • Club-Name ist NICHT sichtbar
  • Toggle-Button ist sichtbar
  • Toggle-Button zeigt Chevron-Right Icon
  • Alle Menü-Items zeigen nur Icons (zentriert)
  • Tooltips erscheinen bei Hover über Icons
  • Tooltips zeigen korrekten Text
  • Beiträge-Menü ist als Dropdown dargestellt
  • Dropdown öffnet RECHTS vom Icon
  • Dropdown enthält "Beiträge" als Titel
  • Footer ist am unteren Ende
  • Language-Selector ist NICHT sichtbar
  • Theme-Toggle ist sichtbar und funktioniert
  • User-Avatar ist zentriert (horizontal)
  • Email ist NICHT sichtbar
  • Sidebar-Breite ist 4rem (w-16)

Toggle-Funktion

  • Click auf Toggle-Button wechselt State
  • Transition ist smooth (300ms)
  • Keine Glitches während Transition
  • Icon wechselt smooth
  • State wird in localStorage gespeichert
  • Nach Reload bleibt State erhalten

Mobile (< 1024px)

  • Header mit Hamburger-Icon erscheint oben
  • Club-Name neben Hamburger
  • Desktop Toggle-Button ist NICHT sichtbar
  • Click auf Hamburger öffnet Drawer
  • Sidebar schiebt von links rein
  • Overlay verdunkelt Hintergrund
  • Click auf Overlay schließt Drawer
  • Sidebar-Content ist vollständig sichtbar
  • Alle Menü-Items funktionieren
  • Footer ist am unteren Ende

Hover-Effekte

  • Menü-Items: Einheitlicher Hover (hellerer Hintergrund)
  • Beiträge-Menü (expanded): Nur EIN Hover auf Summary
  • Beiträge-Menü (collapsed): Nur EIN Hover auf Button
  • Toggle-Button: Hover-Effekt vorhanden
  • User-Menu: Hover-Effekt vorhanden
  • Keine doppelten oder konflikthaften Hovers

Nested Menu "Beiträge"

  • Expanded: Details/Summary funktioniert
  • Expanded: Click öffnet/schließt Submenu
  • Expanded: Submenu-Items eingerückt (ml-4)
  • Collapsed: Dropdown-Button zentriert
  • Collapsed: Tooltip zeigt "Beiträge"
  • Collapsed: Click öffnet Dropdown RECHTS
  • Collapsed: Dropdown hat "Beiträge" als Titel
  • Collapsed: Submenu-Items klickbar
  • Collapsed: Dropdown schließt nach Click außerhalb
  • Footer klebt am unteren Ende (auch bei wenigen Items)
  • Navigation (flex-1) wächst/schrumpft korrekt
  • Kein Leerraum zwischen Navigation und Footer
  • Footer bleibt am unteren Ende beim Scroll

Transitions & Animations

  • Width-Transition: smooth, 300ms
  • Label Fade-out: smooth, 200ms
  • Icon-Swap: instant (kein Flicker)
  • Keine Layout-Shifts
  • Keine Horizontal-Scrollbar während Transition

Browser-Kompatibilität

  • Chrome/Edge: Alles funktioniert
  • Firefox: Alles funktioniert
  • Safari: Alles funktioniert (falls verfügbar)

Accessibility (Screen Reader)

  • Toggle-Button: aria-expanded wird angesagt
  • Menü-Items: Links werden korrekt angesagt
  • User-Menu: Dropdown wird als Menü erkannt
  • Icons: Nicht vom Screen Reader angesagt (aria-hidden)

Performance

  • Keine Console-Errors
  • Keine Console-Warnings
  • localStorage funktioniert
  • Keine Memory-Leaks (DevTools Memory Profiler)

### Bug-Fixing Prompt (falls Bugs gefunden):

Ich habe folgende Bugs bei der visuellen Prüfung der Sidebar gefunden:

[LISTE DER BUGS HIER EINFÜGEN]

Bitte analysiere und fixe diese Bugs:

  1. Identifiziere die Ursache für jeden Bug
  2. Erstelle Fixes in den betroffenen Dateien
  3. Teste, dass der Fix funktioniert
  4. Stelle sicher, dass keine Regression entsteht

Betroffene Dateien könnten sein:

  • lib/mv_web/components/layouts.ex
  • lib/mv_web/components/layouts/sidebar.ex
  • assets/css/app.css
  • assets/js/app.js

Prüfe besonders:

  • CSS-Selektoren ([data-sidebar-expanded])
  • Flexbox-Layout (flex-col, flex-1, mt-auto)
  • Tailwind-Klassen (lg:hidden, lg:flex, etc.)
  • DaisyUI-Komponenten (dropdown, menu, tooltip)

### Acceptance Criteria:
- ✅ Alle Checkboxen abgehakt
- ✅ Keine visuellen Bugs
- ✅ Alle Anforderungen erfüllt
- ✅ Performance akzeptabel
- ✅ Accessibility getestet

---

## Finale Dokumentation

Nach erfolgreicher Umsetzung aller Tasks:

### Prompt für Dokumentations-Update:

Aktualisiere die Projekt-Dokumentation basierend auf der neuen Sidebar-Implementierung:

  1. Update CODE_GUIDELINES.md:

    • Sektion "Frontend: Tailwind CSS & DaisyUI"
    • Beschreibe das Sidebar Pattern
    • Dokumentiere State-Management Ansatz
  2. Erstelle docs/sidebar-architecture.md:

    • Übersicht über Komponenten
    • State-Management Erklärung
    • CSS-Strategie
    • Troubleshooting Guide
  3. Update docs/feature-roadmap.md:

    • Markiere Sidebar als abgeschlossen
    • Dokumentiere Learnings
  4. Erstelle docs/sidebar-maintenance.md:

    • Wie man neue Menü-Items hinzufügt
    • Wie man Nested Menus hinzufügt
    • Wie man Styling anpasst
    • Häufige Probleme und Lösungen

---

## Zusammenfassung

**Gesamtdauer:** ~4-5 Stunden (mit Testing & Bug Fixing)

**Kritische Tasks (Sonnet 4.5):**
- Task 1: Analyse
- Task 2: Recherche
- Task 3: Design
- Task 8: Nested Menu
- Task 12: Testing

**Auto-Mode Tasks:**
- Task 4-7: Foundation & Basic Components
- Task 9-11: Footer & Integration

**Manuell:**
- Task 13: Visual QA

**Erfolgskriterien:**
- ✅ Alle 10 ursprünglichen Anforderungen erfüllt
- ✅ Keine Custom CSS Variants
- ✅ 100% DaisyUI Standard
- ✅ Keine duplicate IDs
- ✅ Accessibility WCAG 2.1 AA
- ✅ Alle Tests grün
- ✅ Performance gut
- ✅ Wartbar und dokumentiert

---

**Version:** 1.0  
**Letzte Aktualisierung:** 2025-12-16