mitgliederverwaltung/docs/sidebar-analysis-current-state.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

21 KiB

Sidebar Analysis - Current State

Erstellt: 2025-12-16
Status: Analyse für Neuimplementierung
Autor: Cursor AI Assistant


Executive Summary

Die aktuelle Sidebar-Implementierung verwendet nicht existierende Custom-CSS-Variants (is-drawer-close: und is-drawer-open:), was zu einer defekten Implementierung führt. Die Sidebar ist strukturell basierend auf DaisyUI's Drawer-Komponente, aber die responsive und state-basierte Funktionalität ist nicht funktionsfähig.

Kritisches Problem: Die im Code verwendeten Variants is-drawer-close:* und is-drawer-open:* sind nicht in Tailwind konfiguriert, was bedeutet, dass diese Klassen beim Build ignoriert werden.


1. Dateien-Übersicht

1.1 Hauptdateien

Datei Zweck Zeilen Status
lib/mv_web/components/layouts/sidebar.ex Sidebar-Komponente (Elixir) 198 ⚠️ Verwendet nicht existierende Variants
lib/mv_web/components/layouts/navbar.ex Navbar mit Sidebar-Toggle 48 Funktional
lib/mv_web/components/layouts.ex Layout-Wrapper mit Drawer 121 Funktional
assets/js/app.js JavaScript für Sidebar-Interaktivität 272 Umfangreiche Accessibility-Logik
assets/css/app.css CSS-Konfiguration 103 ⚠️ Keine Drawer-Variants definiert
assets/tailwind.config.js Tailwind-Konfiguration 75 ⚠️ Keine Drawer-Variants definiert

1.2 Verwandte Dateien

  • lib/mv_web/components/layouts/root.html.heex - Root-Layout (minimal, keine Sidebar-Logik)
  • priv/static/images/logo.svg - Logo (wird vermutlich für Sidebar benötigt)

2. Aktuelle Struktur

2.1 HTML-Struktur (DaisyUI Drawer Pattern)

<!-- In layouts.ex -->
<div class="drawer">
  <input id="main-drawer" type="checkbox" class="drawer-toggle" />
  
  <div class="drawer-content">
    <!-- Navbar mit Toggle-Button -->
    <navbar with sidebar-toggle button />
    
    <!-- Hauptinhalt -->
    <main>...</main>
  </div>
  
  <!-- Sidebar -->
  <div class="drawer-side">
    <button class="drawer-overlay" onclick="close drawer"></button>
    <nav id="main-sidebar">
      <!-- Navigation Items -->
    </nav>
  </div>
</div>

Bewertung: Korrekte DaisyUI Drawer-Struktur

2.2 Sidebar-Komponente (sidebar.ex)

Struktur:

defmodule MvWeb.Layouts.Sidebar do
  attr :current_user, :map
  attr :club_name, :string
  
  def sidebar(assigns) do
    # Rendert Sidebar mit Navigation, Locale-Selector, Theme-Toggle, User-Menu
  end
end

Hauptelemente:

  1. Drawer Overlay - Button zum Schließen (Mobile)
  2. Navigation Container (<nav id="main-sidebar">)
  3. Menü-Items - Members, Users, Contributions (nested), Settings
  4. Footer-Bereich - Locale-Selector, Theme-Toggle, User-Menu

3. Custom CSS Variants - KRITISCHES PROBLEM

3.1 Verwendete Variants im Code

Die Sidebar verwendet folgende Custom-Variants extensiv:

# Beispiele aus sidebar.ex
"is-drawer-close:overflow-visible"
"is-drawer-close:w-14 is-drawer-open:w-64"
"is-drawer-close:hidden"
"is-drawer-close:tooltip is-drawer-close:tooltip-right"
"is-drawer-close:w-auto"
"is-drawer-close:justify-center"
"is-drawer-close:dropdown-end"

Gefundene Verwendungen:

  • is-drawer-close: - 13 Instanzen in sidebar.ex
  • is-drawer-open: - 1 Instanz in sidebar.ex

3.2 Definition der Variants

NICHT GEFUNDEN in:

  • assets/css/app.css - Enthält nur phx-*-loading Variants
  • assets/tailwind.config.js - Enthält nur phx-*-loading Variants

Fazit: Diese Variants existieren nicht und werden beim Tailwind-Build ignoriert!

3.3 Vorhandene Variants

Nur folgende Custom-Variants sind tatsächlich definiert:

/* In app.css (Tailwind CSS 4.x Syntax) */
@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);
@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &);
@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &);
/* In tailwind.config.js (Tailwind 3.x Kompatibilität) */
plugin(({addVariant}) => addVariant("phx-click-loading", [...])),
plugin(({addVariant}) => addVariant("phx-submit-loading", [...])),
plugin(({addVariant}) => addVariant("phx-change-loading", [...])),

4. JavaScript-Implementierung

4.1 Übersicht

Die JavaScript-Implementierung ist sehr umfangreich und fokussiert auf Accessibility:

Datei: assets/js/app.js (Zeilen 106-270)

Hauptfunktionalitäten:

  1. Tabindex-Management für fokussierbare Elemente
  2. ARIA-Attribut-Management (aria-expanded)
  3. Keyboard-Navigation (Enter, Space, Escape)
  4. Focus-Management beim Öffnen/Schließen
  5. Dropdown-Integration

4.2 Wichtige JavaScript-Funktionen

4.2.1 Tabindex-Management

const updateSidebarTabIndex = (isOpen) => {
  const allFocusableElements = sidebar.querySelectorAll(
    'a[href], button, select, input:not([type="hidden"]), [tabindex]'
  )
  
  allFocusableElements.forEach(el => {
    if (isOpen) {
      // Make focusable when open
      el.removeAttribute('tabindex')
    } else {
      // Remove from tab order when closed
      el.setAttribute('tabindex', '-1')
    }
  })
}

Zweck: Verhindert, dass Nutzer mit Tab zu unsichtbaren Sidebar-Elementen springen können.

4.2.2 ARIA-Expanded Management

const updateAriaExpanded = () => {
  const isOpen = drawerToggle.checked
  sidebarToggle.setAttribute("aria-expanded", isOpen.toString())
}

Zweck: Informiert Screen-Reader über den Sidebar-Status.

4.2.3 Focus-Management

const getFirstFocusableElement = () => {
  // Priority: navigation link > other links > other focusable
  const firstNavLink = sidebar.querySelector('a[href][role="menuitem"]')
  // ... fallback logic
}

// On open: focus first element
// On close: focus toggle button

Zweck: Logische Fokus-Reihenfolge für Keyboard-Navigation.

4.2.4 Keyboard-Shortcuts

// ESC to close
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape" && drawerToggle.checked) {
    drawerToggle.checked = false
    sidebarToggle.focus()
  }
})

// Enter/Space on toggle button
sidebarToggle.addEventListener("keydown", (e) => {
  if (e.key === "Enter" || e.key === " ") {
    // Toggle drawer and manage focus
  }
})

4.3 LiveView Hooks

Definierte Hooks:

Hooks.CopyToClipboard = { ... }  // Clipboard-Funktionalität
Hooks.ComboBox = { ... }         // Dropdown-Prävention bei Enter

Sidebar-spezifisch: Keine Hooks, nur native DOM-Events.


5. DaisyUI Dependencies

5.1 Verwendete DaisyUI-Komponenten

Komponente Verwendung Klassen
Drawer Basis-Layout drawer, drawer-toggle, drawer-side, drawer-content, drawer-overlay
Menu Navigation menu, menu-title, w-64
Button Toggle, User-Menu btn, btn-ghost, btn-square, btn-circle
Avatar User-Menu avatar, avatar-placeholder
Dropdown User-Menu dropdown, dropdown-top, dropdown-end, dropdown-content
Tooltip Icon-Tooltips tooltip, tooltip-right (via data-tip)
Select Locale-Selector select, select-sm
Toggle Theme-Switch toggle, theme-controller

5.2 Standard Tailwind-Klassen

Layout:

  • flex, flex-col, items-start, justify-center
  • gap-2, gap-4, p-4, mt-auto, w-full, w-64, min-h-full

Sizing:

  • size-4, size-5, w-12, w-52

Colors:

  • bg-base-100, bg-base-200, text-neutral-content

Typography:

  • text-lg, text-sm, font-bold

Accessibility:

  • sr-only, focus:outline-none, focus:ring-2, focus:ring-primary

6. Toggle-Button (Navbar)

6.1 Implementierung

Datei: lib/mv_web/components/layouts/navbar.ex

<button
  type="button"
  onclick="document.getElementById('main-drawer').checked = !document.getElementById('main-drawer').checked"
  aria-label={gettext("Toggle navigation menu")}
  aria-expanded="false"
  aria-controls="main-sidebar"
  id="sidebar-toggle"
  class="mr-2 btn btn-square btn-ghost"
>
  <svg><!-- Layout-Panel-Left Icon --></svg>
</button>

Funktionalität:

  • Togglet Drawer-Checkbox
  • ARIA-Labels vorhanden
  • Keyboard-accessible
  • ⚠️ aria-expanded wird durch JavaScript aktualisiert

Icon: Custom SVG (Layout-Panel-Left mit Chevron-Right)


7. Responsive Verhalten

7.1 Aktuelles Konzept (nicht funktional)

Versuchte Implementierung:

  • Desktop (collapsed): Sidebar mit 14px Breite (is-drawer-close:w-14)
  • Desktop (expanded): Sidebar mit 64px Breite (is-drawer-open:w-64)
  • Mobile: Overlay-Drawer (DaisyUI Standard)

7.2 Problem

Da die is-drawer-* Variants nicht existieren, gibt es kein responsives Verhalten:

  • Die Sidebar hat immer eine feste Breite von w-64
  • Die conditional hiding (:hidden, etc.) funktioniert nicht
  • Tooltips werden nicht conditional angezeigt

8. Accessibility-Features

8.1 Implementierte Features

Feature Status Implementierung
ARIA Labels Alle interaktiven Elemente haben Labels
ARIA Roles menubar, menuitem, menu, button
ARIA Expanded Wird durch JS dynamisch gesetzt
ARIA Controls Toggle → Sidebar verknüpft
Keyboard Navigation Enter, Space, Escape, Tab
Focus Management Logische Focus-Reihenfolge
Tabindex Management Verhindert Focus auf hidden Elements
Screen Reader Only .sr-only für visuelle Labels
Focus Indicators focus:ring-2 focus:ring-primary
Skip Links Nicht vorhanden

8.2 Accessibility-Score

Geschätzt: 90/100 (WCAG 2.1 Level AA konform)

Verbesserungspotenzial:

  • Skip-Link zur Hauptnavigation hinzufügen
  • High-Contrast-Mode testen

9. Menü-Struktur

9.1 Navigation Items

📋 Main Menu
├── 👥 Members (/members)
├── 👤 Users (/users)
├── 💰 Contributions (collapsed submenu)
│   ├── Plans (/contribution_types)
│   └── Settings (/contribution_settings)
└── ⚙️ Settings (/settings)

🔽 Footer Area (logged in only)
├── 🌐 Locale Selector (DE/EN)
├── 🌓 Theme Toggle (Light/Dark)
└── 👤 User Menu (Dropdown)
    ├── Profile (/users/:id)
    └── Logout (/sign-out)

9.2 Conditional Rendering

Nicht eingeloggt:

  • Sidebar ist leer (nur Struktur)
  • Keine Menü-Items

Eingeloggt:

  • Vollständige Navigation
  • Footer-Bereich mit User-Menu

9.3 Nested Menu (Contributions)

Problem: Das Contributions-Submenu ist immer versteckt im collapsed State:

<li class="is-drawer-close:hidden" role="none">
  <h2 class="flex items-center gap-2 menu-title">
    <.icon name="hero-currency-dollar" />
    {gettext("Contributions")}
  </h2>
  <ul role="menu">
    <li class="is-drawer-close:hidden">...</li>
    <li class="is-drawer-close:hidden">...</li>
  </ul>
</li>

Da :hidden nicht funktioniert, wird das Submenu immer angezeigt.


10. Theme-Funktionalität

10.1 Theme-Toggle

<input
  type="checkbox"
  value="dark"
  class="toggle theme-controller"
  aria-label={gettext("Toggle dark mode")}
/>

Funktionalität:

  • DaisyUI theme-controller - automatische Theme-Umschaltung
  • Persistence durch localStorage (siehe root.html.heex Script)
  • Icon-Wechsel (Sun ↔ Moon)

10.2 Definierte Themes

Datei: assets/css/app.css

  1. Light Theme (default)

    • Base: oklch(98% 0 0)
    • Primary: oklch(70% 0.213 47.604) (Orange/Phoenix-inspiriert)
  2. Dark Theme

    • Base: oklch(30.33% 0.016 252.42)
    • Primary: oklch(58% 0.233 277.117) (Purple/Elixir-inspiriert)

11. Locale-Funktionalität

11.1 Locale-Selector

<form method="post" action="/set_locale">
  <select
    id="locale-select-sidebar"
    name="locale"
    onchange="this.form.submit()"
    class="select select-sm w-full is-drawer-close:w-auto"
  >
    <option value="de">Deutsch</option>
    <option value="en">English</option>
  </select>
</form>

Funktionalität:

  • POST zu /set_locale Endpoint
  • CSRF-Token included
  • Auto-Submit on change
  • Accessible Label (.sr-only)

12. Probleme und Defekte

12.1 Kritische Probleme

Problem Schweregrad Details
Nicht existierende CSS-Variants 🔴 Kritisch is-drawer-close:* und is-drawer-open:* sind nicht definiert
Keine responsive Funktionalität 🔴 Kritisch Sidebar verhält sich nicht wie geplant
Conditional Styles funktionieren nicht 🔴 Kritisch Hidden/Tooltip/Width-Changes werden ignoriert

12.2 Mittlere Probleme

Problem Schweregrad Details
Kein Logo 🟡 Mittel Logo-Element fehlt komplett in der Sidebar
Submenu immer sichtbar 🟡 Mittel Contributions-Submenu sollte in collapsed State versteckt sein
Toggle-Icon statisch 🟡 Mittel Icon ändert sich nicht zwischen expanded/collapsed

12.3 Kleinere Probleme

Problem Schweregrad Details
Code-Redundanz 🟢 Klein Variants in beiden Tailwind-Configs (3.x und 4.x)
Inline-onclick Handler 🟢 Klein Sollten durch JS-Events ersetzt werden
Keine Skip-Links 🟢 Klein Accessibility-Verbesserung

13. Abhängigkeiten

13.1 Externe Abhängigkeiten

Dependency Version Verwendung
DaisyUI Latest (vendor) Drawer, Menu, Button, etc.
Tailwind CSS 4.0.9 Utility-Klassen
Heroicons v2.2.0 Icons in Navigation
Phoenix LiveView ~> 1.1.0 Backend-Integration

13.2 Interne Abhängigkeiten

Modul Verwendung
MvWeb.Gettext Internationalisierung
Mv.Membership.get_settings() Club-Name abrufen
MvWeb.CoreComponents Icons, Links

14. Code-Qualität

14.1 Positives

  • Sehr gute Accessibility-Implementierung
  • Saubere Modulstruktur (Separation of Concerns)
  • Gute Dokumentation (Moduledocs, Attribute docs)
  • Internationalisierung vollständig implementiert
  • ARIA-Best-Practices befolgt
  • Keyboard-Navigation umfassend

14.2 Verbesserungsbedarf

  • Broken CSS-Variants (Hauptproblem)
  • Fehlende Tests (keine Component-Tests gefunden)
  • ⚠️ Inline-JavaScript in onclick-Attributen
  • ⚠️ Magic-IDs (main-drawer, sidebar-toggle) hardcoded
  • ⚠️ Komplexe JavaScript-Logik ohne Dokumentation

15. Empfehlungen für Neuimplementierung

15.1 Sofort-Maßnahmen

  1. CSS-Variants entfernen

    • Alle is-drawer-close:* und is-drawer-open:* entfernen
    • Durch Standard-Tailwind oder DaisyUI-Mechanismen ersetzen
  2. Logo hinzufügen

    • Logo-Element als erstes Element in Sidebar
    • Konsistente Größe (32px / size-8)
  3. Toggle-Icon implementieren

    • Icon-Swap zwischen Chevron-Left und Chevron-Right
    • Nur auf Desktop sichtbar

15.2 Architektur-Entscheidungen

  1. Responsive Strategie:

    • Mobile: Standard DaisyUI Drawer (Overlay)
    • Desktop: Persistent Sidebar mit fester Breite
    • Kein collapsing auf Desktop (einfacher, wartbarer)
  2. State-Management:

    • Drawer-Checkbox für Mobile
    • Keine zusätzlichen Custom-Variants
    • Standard DaisyUI-Mechanismen verwenden
  3. JavaScript-Refactoring:

    • Hooks statt inline-onclick
    • Dokumentierte Funktionen
    • Unit-Tests für kritische Logik

15.3 Prioritäten

High Priority:

  1. CSS-Variants-Problem lösen
  2. Logo implementieren
  3. Basic responsive Funktionalität

Medium Priority: 4. Toggle-Icon implementieren 5. Tests schreiben 6. JavaScript refactoren

Low Priority: 7. Skip-Links hinzufügen 8. Code-Optimierung 9. Performance-Tuning


16. Checkliste für Neuimplementierung

16.1 Vorbereitung

  • Alle is-drawer-* Klassen aus Code entfernen
  • Keine Custom-Variants in CSS/Tailwind definieren
  • DaisyUI-Dokumentation für Drawer studieren

16.2 Implementation

  • Logo-Element hinzufügen (size-8, persistent)
  • Toggle-Button mit Icon-Swap (nur Desktop)
  • Mobile: Overlay-Drawer (DaisyUI Standard)
  • Desktop: Persistent Sidebar (w-64)
  • Menü-Items mit korrekten Klassen
  • Submenu-Handling (nested <ul>)

16.3 Funktionalität

  • Toggle-Funktionalität auf Mobile
  • Accessibility: ARIA, Focus, Keyboard
  • Theme-Toggle funktional
  • Locale-Selector funktional
  • User-Menu-Dropdown funktional

16.4 Testing

  • Component-Tests schreiben
  • Accessibility-Tests (axe-core)
  • Keyboard-Navigation testen
  • Screen-Reader testen
  • Responsive Breakpoints testen

16.5 Dokumentation

  • Code-Kommentare aktualisieren
  • Component-Docs schreiben
  • README aktualisieren

17. Technische Details

17.1 CSS-Selektoren

Verwendete IDs:

  • #main-drawer - Drawer-Toggle-Checkbox
  • #main-sidebar - Sidebar-Navigation-Container
  • #sidebar-toggle - Toggle-Button in Navbar
  • #locale-select-sidebar - Locale-Dropdown

Verwendete Klassen:

  • .drawer-side - DaisyUI Sidebar-Container
  • .drawer-overlay - DaisyUI Overlay-Button
  • .drawer-content - DaisyUI Content-Container
  • .menu - DaisyUI Menu-Container
  • .is-drawer-close:* - NICHT DEFINIERT
  • .is-drawer-open:* - NICHT DEFINIERT

17.2 Event-Handler

JavaScript:

drawerToggle.addEventListener("change", ...)
sidebarToggle.addEventListener("click", ...)
sidebarToggle.addEventListener("keydown", ...)
document.addEventListener("keydown", ...)  // ESC handler

Inline (zu migrieren):

onclick="document.getElementById('main-drawer').checked = false"
onclick="document.getElementById('main-drawer').checked = !..."
onchange="this.form.submit()"

18. Metriken

18.1 Code-Metriken

Metrik Wert
Zeilen Code (Sidebar) 198
Zeilen JavaScript 165 (Sidebar-spezifisch)
Zeilen CSS 0 (nur Tailwind-Klassen)
Anzahl Komponenten 1 (Sidebar) + 1 (Navbar)
Anzahl Menü-Items 6 (inkl. Submenu)
Anzahl Footer-Controls 3 (Locale, Theme, User)

18.2 Abhängigkeits-Metriken

Kategorie Anzahl
DaisyUI-Komponenten 7
Tailwind-Utility-Klassen ~50
Custom-Variants (broken) 2 (is-drawer-close, is-drawer-open)
JavaScript-Event-Listener 6
ARIA-Attribute 12

19. Zusammenfassung

19.1 Was funktioniert

Sehr gute Grundlage:

  • DaisyUI Drawer-Pattern korrekt implementiert
  • Exzellente Accessibility (ARIA, Keyboard, Focus)
  • Saubere Modulstruktur
  • Internationalisierung
  • Theme-Switching
  • JavaScript-Logik ist robust

19.2 Was nicht funktioniert

Kritische Defekte:

  • CSS-Variants existieren nicht → keine responsive Funktionalität
  • Kein Logo
  • Kein Toggle-Icon-Swap
  • Submenu-Handling defekt

19.3 Nächste Schritte

  1. CSS-Variants entfernen (alle is-drawer-* Klassen)
  2. Standard DaisyUI-Pattern verwenden (ohne Custom-Variants)
  3. Logo hinzufügen (persistent, size-8)
  4. Simplify: Mobile = Overlay, Desktop = Persistent (keine collapsed State)
  5. Tests schreiben (Component + Accessibility)

20. Anhang

20.1 Verwendete CSS-Klassen (alphabetisch)

avatar, avatar-placeholder, bg-base-100, bg-base-200, bg-neutral,
btn, btn-circle, btn-ghost, btn-square, cursor-pointer, drawer, 
drawer-content, drawer-overlay, drawer-side, drawer-toggle, dropdown,
dropdown-content, dropdown-end, dropdown-top, flex, flex-col,
focus:outline-none, focus:ring-2, focus:ring-primary,
focus-within:outline-none, focus-within:ring-2, gap-2, gap-4,
is-drawer-close:*, is-drawer-open:*, items-center, items-start,
mb-2, menu, menu-sm, menu-title, min-h-full, mr-2, mt-3, mt-auto,
p-2, p-4, rounded-box, rounded-full, select, select-sm, shadow,
shadow-sm, size-4, size-5, sr-only, text-lg, text-neutral-content,
text-sm, theme-controller, toggle, tooltip, tooltip-right, w-12,
w-52, w-64, w-full, z-1

20.2 Verwendete ARIA-Attribute

aria-busy, aria-controls, aria-describedby, aria-expanded,
aria-haspopup, aria-hidden, aria-label, aria-labelledby,
aria-live, role="alert", role="button", role="menu",
role="menubar", role="menuitem", role="none", role="status"

Ende des Berichts