Some checks failed
continuous-integration/drone/push Build is failing
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
1576 lines
39 KiB
Markdown
1576 lines
39 KiB
Markdown
# 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:
|
|
|
|
```heex
|
|
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):
|
|
|
|
```elixir
|
|
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`:
|
|
|
|
```heex
|
|
<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:
|
|
|
|
```elixir
|
|
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`:
|
|
```heex
|
|
<!-- 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 <details> mit <summary> 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:
|
|
|
|
```elixir
|
|
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:
|
|
|
|
```elixir
|
|
<!-- 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:
|
|
|
|
```css
|
|
/* 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)
|
|
|
|
## Footer-Komponenten:
|
|
|
|
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:
|
|
|
|
```elixir
|
|
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:
|
|
|
|
```heex
|
|
<!-- Footer -->
|
|
<%= if @current_user do %>
|
|
<.sidebar_footer current_user={@current_user} />
|
|
<% end %>
|
|
```
|
|
|
|
Prüfe, dass `.sidebar_menu` `flex-1` hat:
|
|
|
|
```heex
|
|
<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:
|
|
|
|
```javascript
|
|
Hooks.SidebarState = {
|
|
mounted() {
|
|
// Restore state from localStorage
|
|
const expanded = localStorage.getItem('sidebar-expanded') !== 'false';
|
|
this.setSidebarState(expanded);
|
|
|
|
// Expose toggle function globally
|
|
window.toggleSidebar = () => {
|
|
const current = this.el.dataset.sidebarExpanded === 'true';
|
|
this.setSidebarState(!current);
|
|
};
|
|
},
|
|
|
|
setSidebarState(expanded) {
|
|
// Update data-attribute (CSS reacts to this)
|
|
this.el.dataset.sidebarExpanded = expanded;
|
|
|
|
// Persist to localStorage
|
|
localStorage.setItem('sidebar-expanded', expanded);
|
|
|
|
// Update ARIA for accessibility
|
|
const toggleBtn = document.getElementById('sidebar-toggle');
|
|
if (toggleBtn) {
|
|
toggleBtn.setAttribute('aria-expanded', expanded);
|
|
}
|
|
},
|
|
|
|
destroyed() {
|
|
// Cleanup
|
|
delete window.toggleSidebar;
|
|
}
|
|
};
|
|
```
|
|
|
|
Stelle sicher, dass der Hook registriert ist:
|
|
|
|
```javascript
|
|
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:
|
|
```javascript
|
|
// 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:
|
|
|
|
```heex
|
|
<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:
|
|
|
|
```heex
|
|
<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:
|
|
|
|
```css
|
|
/* 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
|
|
|
|
```elixir
|
|
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
|
|
|
|
```elixir
|
|
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
|
|
|
|
```elixir
|
|
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
|
|
|
|
```elixir
|
|
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:
|
|
|
|
```bash
|
|
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-Positionierung
|
|
|
|
- [ ] 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
|
|
|