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
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:
- Nutze DaisyUI
drawer+drawer-openPattern - Ein Container für Mobile UND Desktop (keine Duplikate!)
@inner_blockdarf nur EINMAL gerendert werdendata-sidebar-expandedAttribut auf rootphx-hook="SidebarState"auf rootid="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:
- Kompiliere:
mix compile - Starte Server:
mix phx.server - Prüfe: Keine duplicate ID Fehler in Browser Console
- Prüfe: Layout funktioniert responsive
- 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:
-
Logo:
- Immer
size-8(32px) - Immer sichtbar (kein Hide)
- Nur EIN Logo-Element
- Pfad:
/images/mila.svg
- Immer
-
Club-Name:
- Text-Label mit CSS-Klasse
menu-label - Wird via CSS ausgeblendet (collapsed)
text-lg font-bold truncate
- Text-Label mit CSS-Klasse
-
Toggle-Button:
- Nur auf Desktop sichtbar (responsive:
hidden lg:flexoderlg: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)
- Option A:
- 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)
- Option A: Zwei Icons mit CSS-Klassen (
- 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)
- Option A:
- ARIA:
aria-label={gettext("Toggle sidebar")}undaria-expanded(wird via JS gesetzt)
- Nur auf Desktop sichtbar (responsive:
Design-Überlegungen:
- Button sollte sich harmonisch in den Header einfügen
- Position: Rechts im Header (
ml-auto) - Icon-Größe:
size-5odersize-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) ODERphx-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:
- Kompiliere:
mix compile - Starte Server:
mix phx.server - 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:
-
Expanded State:
- Icon + Text-Label
- Standard DaisyUI menu hover
-
**Collapsed State:**gf
- Nur Icon
- Tooltip erscheint rechts (
tooltip-right) - Tooltip-Text aus
data-tip
-
Hover-Effekt:
- Einheitlich (Standard DaisyUI menu)
- Keine custom hover-styles
-
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:
- Kompiliere und starte Server
- Prüfe Expanded State:
- Icons + Labels sichtbar
- Hover funktioniert
- Toggle zu Collapsed:
- Nur Icons sichtbar
- Tooltips erscheinen bei Hover
- Tooltips zeigen richtigen Text
- 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:
- Nur EIN Hover-Effekt (nicht doppelt)
- Flyout erscheint rechts vom Icon im collapsed state
- Smooth transitions zwischen states
- 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:
-
Expanded State:
- Details/Summary erscheint
- Klick öffnet/schließt Submenu
- Submenu-Items sind klickbar
- NUR EIN Hover-Effekt auf summary
-
Collapsed State:
- Dropdown erscheint
- Flyout öffnet RECHTS vom Icon
- Menu-Title "Contributions" erscheint
- Submenu-Items sind klickbar
- NUR EIN Hover-Effekt auf button
-
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:
.sidebar:flex flex-col(bereits vorhanden)- Navigation:
flex-1(nimmt verfügbaren Platz) - Footer:
mt-auto(wird nach unten geschoben)
Footer-Komponenten:
-
Language Selector:
- Nur im expanded state sichtbar (
.expanded-only) - DaisyUI select
- Form mit POST zu
/locale
- Nur im expanded state sichtbar (
-
Theme Toggle:
- IMMER sichtbar
- Horizontal: Sun Icon + Toggle + Moon Icon
- DaisyUI toggle + theme-controller
-
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:
-
Positionierung:
- Footer klebt am unteren Ende
- Auch bei wenigen Menu-Items
- Navigation wächst/schrumpft dynamisch
-
Language Selector:
- Erscheint nur expanded
- Verschwindet collapsed
- Form funktioniert (Sprache wechselt)
-
Theme Toggle:
- Immer sichtbar
- Icons horizontal ausgerichtet
- Toggle funktioniert
-
User Menu:
- Avatar zentriert (auch collapsed)
- Erste Buchstabe korrekt
- Email verschwindet collapsed
- Dropdown öffnet nach oben
- Profile + Logout Links funktionieren
-
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:
- State in localStorage speichern
- State via data-attribute auf HTML-Element setzen
- Global
toggleSidebar()Funktion bereitstellen - aria-expanded auf Toggle-Button aktualisieren
- KEINE Checkbox-Manipulation
- KEINE Tab-Index-Verwaltung
- 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:
-
Initial State:
- Sidebar ist expanded (default)
- localStorage wird gesetzt
-
Toggle:
- Klick auf Toggle-Button
- Sidebar animiert zu collapsed
- localStorage wird aktualisiert
- Icon wechselt (Chevron-left ↔ Chevron-right)
-
Persistence:
- Toggle zu collapsed
- Seite neu laden
- Sidebar bleibt collapsed
-
Browser Console:
- Keine Fehler
window.toggleSidebar()ist aufrufbarlocalStorage.getItem('sidebar-expanded')zeigt korrekten Wert
-
ARIA:
- Toggle-Button hat
aria-expanded="true"oder"false" - Wert ändert sich beim Toggle
- Toggle-Button hat
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:
- Mobile Header ist bereits in
layouts.eximplementiert - Hamburger-Menü öffnet Sidebar
- Header nur auf Mobile sichtbar (
lg:hidden) - 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:
-
Mobile View (< 1024px):
- Header erscheint am oberen Rand
- Hamburger-Icon sichtbar
- Club-Name neben Hamburger
-
Hamburger-Click:
- Sidebar schiebt von links rein
- Overlay verdunkelt Content
- Sidebar ist vollständig sichtbar
-
Overlay-Click:
- Sidebar schließt sich
- Overlay verschwindet
- Content wieder normal
-
Desktop View (≥ 1024px):
- Header verschwindet
- Sidebar ist persistent sichtbar
- Kein Hamburger-Icon
- Toggle-Button erscheint in Sidebar
-
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:
- Analysiere Fehler
- Fixe Code oder Test
- 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:
- Identifiziere die Ursache für jeden Bug
- Erstelle Fixes in den betroffenen Dateien
- Teste, dass der Fix funktioniert
- 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:
-
Update
CODE_GUIDELINES.md:- Sektion "Frontend: Tailwind CSS & DaisyUI"
- Beschreibe das Sidebar Pattern
- Dokumentiere State-Management Ansatz
-
Erstelle
docs/sidebar-architecture.md:- Übersicht über Komponenten
- State-Management Erklärung
- CSS-Strategie
- Troubleshooting Guide
-
Update
docs/feature-roadmap.md:- Markiere Sidebar als abgeschlossen
- Dokumentiere Learnings
-
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