```
**Verhalten:**
1. Beim Mount: Restore aus localStorage
2. Beim Toggle: Update localStorage + data-attribute
3. CSS reagiert auf data-attribute änderung
### 1.7 Responsive Verhalten
**Mobile (< lg / < 1024px):**
- Standard DaisyUI Drawer Overlay
- Hamburger-Icon in Mobile Header (außerhalb Sidebar)
- Overlay verdunkelt Hintergrund
- Click auf Overlay schließt Drawer
- Sidebar: volle Breite `w-64` (16rem)
- Toggle-Button in Sidebar: NICHT sichtbar
**Desktop (≥ lg / ≥ 1024px):**
- Fixed Sidebar (persistent)
- `lg:drawer-open` forciert Drawer als sichtbar
- Expanded: `w-64` (16rem)
- Collapsed: `w-16` (4rem)
- Smooth width transition: `300ms ease-in-out`
- Toggle-Button in Sidebar: sichtbar
- Mobile Header: NICHT sichtbar (`lg:hidden`)
---
## 2. Wireframes (ASCII-Art)
### 2.1 Desktop - Expanded State (w-64 / 16rem)
```
┌────────────────────────────────┐
│ 🏠 Club Name [◀] │ ← Header (Logo + Name + Toggle)
├────────────────────────────────┤
│ │
│ 👥 Members │
│ 👤 Users │
│ 💰 Beiträge ▼ │ ← Details/Summary (klappbar)
│ ├─ Beitragsarten │
│ └─ Einstellungen │
│ ⚙️ Settings │
│ │
│ │
│ (flex-1 - wächst) │
│ │
│ │
├────────────────────────────────┤
│ [Deutsch ▼] │ ← Language Selector
│ ☀️ [○] 🌙 │ ← Theme Toggle
│ (A) user@example.com [▼] │ ← User Menu (Avatar + Email)
└────────────────────────────────┘
16rem (256px)
```
### 2.2 Desktop - Collapsed State (w-16 / 4rem)
```
┌──────┐
│ 🏠 │ ← Logo zentriert
│ [▶] │ ← Toggle-Button
├──────┤
│ │
│ 👥 │ ← Tooltip: "Members"
│ 👤 │ ← Tooltip: "Users"
│ 💰 │ ← Dropdown öffnet rechts →
│ ⚙️ │ ← Tooltip: "Settings"
│ │
│ │
│ │ (flex-1)
│ │
│ │
├──────┤
│ │ ← Language Selector HIDDEN
│ ☀️○🌙│ ← Theme Toggle (icons kleiner)
│ (A) │ ← Avatar zentriert (kein Email)
└──────┘
4rem
```
**Dropdown-Verhalten (collapsed):**
```
┌──────┐ ┌─────────────────────┐
│ 👥 │ │ │
│ 👤 │ │ │
│ 💰 │────────────▶│ Beiträge │
│ ⚙️ │ │ ─────────────────── │
│ │ │ • Beitragsarten │
└──────┘ │ • Einstellungen │
└─────────────────────┘
```
### 2.3 Mobile mit Overlay (< lg / < 1024px)
**Geschlossen:**
```
┌─────────────────────────────┐
│ [☰] Club Name │ ← Mobile Header (außerhalb Sidebar)
├─────────────────────────────┤
│ │
│ Main Content │
│ │
└─────────────────────────────┘
```
**Geöffnet:**
```
┌────────────────────────────────┐
│ 🏠 Club Name │ ◀──────── Sidebar (w-64)
├────────────────────────────────┤ Overlay: bg-black/50
│ │
│ 👥 Members │
│ 👤 Users │
│ 💰 Beiträge ▼ │
│ ├─ Beitragsarten │
│ └─ Einstellungen │
│ ⚙️ Settings │
│ │
│ (flex-1) │
│ │
├────────────────────────────────┤
│ [Deutsch ▼] │
│ ☀️ [○] 🌙 │
│ (A) user@example.com [▼] │
└────────────────────────────────┘
│
└─ Kein Toggle-Button (nur expanded)
```
**Hinweis:** Mobile Sidebar ist IMMER expanded (kein collapsed state).
---
## 3. Benötigte DaisyUI-Komponenten
### 3.1 Layout-Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **drawer** | Basis-Container | `drawer`, `lg:drawer-open` |
| **drawer-toggle** | Hidden Checkbox (Mobile) | `drawer-toggle` |
| **drawer-content** | Main Content Area | `drawer-content`, `flex flex-col` |
| **drawer-side** | Sidebar Container | `drawer-side` |
| **drawer-overlay** | Mobile Overlay (Click to close) | `drawer-overlay`, `lg:hidden` |
### 3.2 Navigation-Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **menu** | Navigation Liste | `menu`, `flex-1`, `w-full`, `p-2` |
| **menu-title** | Abschnitts-Überschrift | `menu-title` |
| **tooltip** | Icon-Tooltips (collapsed) | `tooltip`, `tooltip-right` |
### 3.3 Interaktive Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **btn** | Toggle-Button | `btn`, `btn-ghost`, `btn-sm`, `btn-square` |
| **select** | Language Selector | `select`, `select-sm`, `w-full` |
| **toggle** | Theme Switch | `toggle`, `toggle-sm`, `theme-controller` |
| **dropdown** | User Menu & Nested Menu | `dropdown`, `dropdown-top`, `dropdown-end`, `dropdown-right` |
| **avatar** | User Avatar | `avatar`, `avatar-placeholder` |
| **navbar** | Mobile Header | `navbar`, `bg-base-100`, `shadow-sm`, `lg:hidden` |
### 3.4 Utility-Komponenten
| Komponente | Verwendung | Klassen |
|------------|------------|---------|
| **Flexbox** | Layout-Struktur | `flex`, `flex-col`, `flex-1`, `items-center`, `justify-center` |
| **Spacing** | Gaps & Padding | `gap-2`, `gap-3`, `gap-4`, `p-2`, `p-4`, `mt-auto` |
| **Sizing** | Widths & Heights | `w-64`, `w-16`, `w-8`, `h-8`, `size-5`, `size-8`, `min-h-screen` |
| **Colors** | Backgrounds | `bg-base-100`, `bg-base-200`, `bg-base-300`, `bg-neutral` |
| **Typography** | Text Styles | `text-lg`, `text-sm`, `font-bold`, `truncate` |
| **Borders** | Dividers | `border-t`, `border-b`, `border-base-300` |
| **Z-Index** | Layering | `z-10`, `z-20`, `z-50` |
### 3.5 Responsive Modifiers
| Modifier | Breakpoint | Verwendung |
|----------|------------|------------|
| **lg:** | ≥ 1024px | Desktop-spezifische Styles |
| **lg:hidden** | < 1024px | Mobile Header, Overlay |
| **lg:flex** | ≥ 1024px | Toggle-Button in Sidebar |
| **lg:drawer-open** | ≥ 1024px | Persistent Desktop Sidebar |
---
## 4. CSS-Strategie
### 4.1 Prinzipien
✅ **Erlaubt:**
- Tailwind `@apply` Direktiven
- Standard DaisyUI Komponenten-Klassen
- CSS attribute selectors (`[data-*]`)
- Tailwind responsive modifiers (`lg:`, `md:`)
- Standard pseudo-classes (`:hover`, `:focus`)
❌ **NICHT erlaubt:**
- Custom CSS Variants (`@custom-variant`)
- Custom Tailwind Plugin Variants
- Complex CSS Selectors (mehr als 3 Ebenen tief)
- `!important` (außer in extremen Edge-Cases)
### 4.2 State Management via Data-Attribute
**Selector Pattern:**
```css
[data-sidebar-expanded="false"] .sidebar .element {
/* Collapsed State Styles */
}
```
**Beispiel-Implementierung in `app.css`:**
```css
/* ============================================
Sidebar Base Styles
============================================ */
.sidebar {
@apply flex flex-col bg-base-200 min-h-screen;
@apply transition-[width] duration-300 ease-in-out;
width: 16rem; /* w-64 - Expanded */
}
/* Collapsed State */
[data-sidebar-expanded="false"] .sidebar {
width: 4rem; /* w-16 - Collapsed */
}
/* ============================================
Text Labels - Fade Out in Collapsed
============================================ */
.menu-label {
@apply transition-all duration-200 whitespace-nowrap;
@apply opacity-100;
}
[data-sidebar-expanded="false"] .sidebar .menu-label {
@apply opacity-0 w-0 overflow-hidden pointer-events-none;
}
/* ============================================
Toggle Button Icon Swap
============================================ */
.sidebar-expanded-icon {
@apply block;
}
.sidebar-collapsed-icon {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .sidebar-expanded-icon {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .sidebar-collapsed-icon {
@apply block;
}
/* ============================================
Nested Menu - Show/Hide Based on State
============================================ */
.expanded-menu-group {
@apply block;
}
.collapsed-menu-group {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .expanded-menu-group {
@apply hidden;
}
[data-sidebar-expanded="false"] .sidebar .collapsed-menu-group {
@apply block;
}
/* ============================================
Elements Only Visible in Expanded State
============================================ */
.expanded-only {
@apply block transition-opacity duration-200;
}
[data-sidebar-expanded="false"] .sidebar .expanded-only {
@apply hidden;
}
/* ============================================
Tooltips - Only Show in Collapsed State
============================================ */
.sidebar .tooltip::before,
.sidebar .tooltip::after {
@apply opacity-0 pointer-events-none;
}
[data-sidebar-expanded="false"] .sidebar .tooltip:hover::before,
[data-sidebar-expanded="false"] .sidebar .tooltip:hover::after {
@apply opacity-100;
}
/* ============================================
Menu Item Alignment - Center in Collapsed
============================================ */
[data-sidebar-expanded="false"] .sidebar .menu > li > a,
[data-sidebar-expanded="false"] .sidebar .menu > li > button {
@apply justify-center px-0;
}
```
### 4.3 Responsive Strategie
**Mobile (< lg):**
- Standard DaisyUI Drawer (checkbox-basiert)
- Keine custom State-Management
- Sidebar immer `w-64` wenn geöffnet
**Desktop (≥ lg):**
- `lg:drawer-open` für Persistence
- Custom State-Management via data-attribute
- Width-Transition: `w-64` ↔ `w-16`
**Keine Konflikte:**
- `lg:drawer-open` wird NICHT durch `data-sidebar-expanded` beeinflusst
- Collapsed State nur auf Desktop relevant
---
## 5. State-Management-Strategie
### 5.1 JavaScript Hook: `SidebarState`
**Datei:** `assets/js/app.js`
**Verantwortlichkeiten:**
1. **Mount:** Restore State aus localStorage
2. **Toggle:** Wechsle State, update localStorage + data-attribute
3. **ARIA:** Update `aria-expanded` auf Toggle-Button
4. **Global Function:** Expose `window.toggleSidebar()`
**Implementierung:**
```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;
}
};
```
### 5.2 localStorage Schema
**Key:** `sidebar-expanded`
**Values:**
- `"true"` (string) - Expanded
- `"false"` (string) - Collapsed
**Default:** `"true"` (wenn Key nicht existiert)
**Warum String?**
- localStorage speichert nur Strings
- Vermeidet Parsing-Fehler
- Einfache Vergleiche
### 5.3 CSS Reactions
**Automatisch via Attribute Selector:**
```html
```
**Vorteile:**
- Keine JavaScript DOM-Manipulation
- CSS Transitions funktionieren automatisch
- Performance: GPU-accelerated
- Wartbar: State ist zentral
### 5.4 Event Flow
```
User clicks Toggle-Button
│
▼
onclick="toggleSidebar()"
│
▼
JavaScript Hook: setSidebarState(!current)
│
├─▶ Update data-attribute
├─▶ Save to localStorage
└─▶ Update aria-expanded
│
▼
CSS Reactions
│
├─▶ Sidebar width transition
├─▶ Labels fade out/in
├─▶ Icons show/hide
└─▶ Tooltips enable/disable
│
▼
User sees smooth animation
```
**Keine Komplexität:**
- ❌ Kein tabindex management
- ❌ Kein focus management
- ❌ Keine event listener cleanup
- ❌ Keine checkbox manipulation
---
## 6. Komponentenstruktur
### 6.1 Hierarchie
```
app (layouts.ex)
├── Mobile Header (lg:hidden)
│ ├── Hamburger-Icon (drawer-toggle label)
│ └── Club Name
│
└── Sidebar (drawer-side)
├── sidebar_header
│ ├── Logo (size-8)
│ ├── Club Name (.menu-label)
│ └── Toggle-Button (hidden lg:flex)
│ ├── Icon: Chevron-Left (.sidebar-expanded-icon)
│ └── Icon: Chevron-Right (.sidebar-collapsed-icon)
│
├── sidebar_menu (flex-1)
│ ├── menu_item: Members
│ ├── menu_item: Users
│ ├── menu_group: Beiträge (.expanded-menu-group + .collapsed-menu-group)
│ │ ├── Expanded:
│ │ ├── Collapsed:
│ │ └── Submenu:
│ │ ├── menu_subitem: Beitragsarten
│ │ └── menu_subitem: Einstellungen
│ └── menu_item: Settings
│
└── sidebar_footer (mt-auto)
├── Language Selector (.expanded-only)
├── Theme Toggle (immer)
└── User Menu (dropdown-top dropdown-end)
├── Avatar + Email
└── Dropdown:
├── Profile
└── Logout
```
### 6.2 Elixir Komponenten
**Datei:** `lib/mv_web/components/layouts/sidebar.ex`
**Funktionen:**
1. `sidebar/1` - Root (deprecated, wird durch layouts.ex ersetzt)
2. `sidebar_content/1` - Wrapper für alle Sidebar-Inhalte
3. `sidebar_header/1` - Logo + Name + Toggle
4. `sidebar_menu/1` - Navigation Container
5. `menu_item/1` - Einfacher Menü-Eintrag
6. `menu_group/1` - Nested Menu Container
7. `menu_subitem/1` - Submenu-Eintrag
8. `sidebar_footer/1` - Footer Container
9. `theme_toggle/1` - Theme Switch
10. `user_menu/1` - User Dropdown
### 6.3 Attribute Definition
**Beispiel: `menu_item/1`**
```elixir
attr :href, :string, required: true, doc: "Navigation path"
attr :icon, :string, required: true, doc: "Heroicon name"
attr :label, :string, required: true, doc: "Menu item label"
attr :active, :boolean, default: false, doc: "Highlight as active"
defp menu_item(assigns) do
~H"""
<.link
navigate={@href}
class={[
"flex items-center gap-3",
"tooltip tooltip-right",
@active && "active"
]}
data-tip={@label}
role="menuitem"
>
<.icon name={@icon} class="size-5 shrink-0" aria-hidden="true" />
"""
end
```
---
## 7. Accessibility (WCAG 2.1 Level AA)
### 7.1 Semantic HTML
| Element | Semantik | ARIA |
|---------|----------|------|
| `