2005448c51
Reduce typography to 4 sizes (12/14/18/28) and 2 weights (400/600). Clock scaling declared as responsive exception, not standalone size. Destructive CTAs now include noun (Quelle loeschen / Delete source). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
324 lines
20 KiB
Markdown
324 lines
20 KiB
Markdown
---
|
|
phase: 5
|
|
slug: dashboard-calendar
|
|
status: draft
|
|
shadcn_initialized: false
|
|
preset: none
|
|
created: 2026-06-23
|
|
---
|
|
|
|
# Phase 05 — UI Design Contract
|
|
|
|
> Visual and interaction contract for the Dashboard & Calendar phase. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
|
|
|
|
---
|
|
|
|
## Design System
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| Tool | none (shadcn token convention used manually) |
|
|
| Preset | not applicable |
|
|
| Component library | Custom components following shadcn CSS variable naming (--background, --foreground, --card, --primary, etc.) |
|
|
| Icon library | Inline SVG (established pattern across all phases) |
|
|
| Font | Inter, system-ui, -apple-system, sans-serif |
|
|
|
|
**Note:** Project uses shadcn-compatible CSS variable tokens in OKLCH color space (established Phase 01) but does not use `components.json` or the shadcn CLI. All components are hand-written. Phase 05 continues this pattern for consistency.
|
|
|
|
---
|
|
|
|
## Spacing Scale
|
|
|
|
Declared values (must be multiples of 4):
|
|
|
|
| Token | Value | Usage |
|
|
|-------|-------|-------|
|
|
| xs | 4px | Icon gaps, inline padding, grid gap between widget cells |
|
|
| sm | 8px | Compact element spacing, widget internal padding minimum |
|
|
| md | 16px | Default element spacing, widget content padding |
|
|
| lg | 24px | Section padding, settings form group spacing |
|
|
| xl | 32px | Layout gaps, dashboard grid outer padding |
|
|
| 2xl | 48px | Empty state vertical spacing |
|
|
| 3xl | 64px | Page-level spacing (not used in this phase) |
|
|
|
|
Exceptions:
|
|
- Grid cell size: 40px per grid unit (react-grid-layout rowHeight). Derived from 8-point scale (5 x 8px).
|
|
- Grid margin/padding: 16px (consistent with `md` token)
|
|
- Touch target minimum: 44px for drag handles and widget action buttons (accessibility)
|
|
|
|
---
|
|
|
|
## Typography
|
|
|
|
| Role | Size | Weight | Line Height | Usage |
|
|
|------|------|--------|-------------|-------|
|
|
| Label | 12px | 400 (regular) | 1.4 | Widget type labels, category badges, timestamps, muted hints, markdown code blocks (monospace) |
|
|
| Body | 14px | 400 (regular) | 1.5 | Widget content, settings form labels, descriptions, Notes widget title (semibold variant) |
|
|
| Heading | 18px | 600 (semibold) | 1.3 | Settings section headings, widget catalog title |
|
|
| Display | 28px | 600 (semibold) | 1.2 | Clock widget time display |
|
|
|
|
**Exactly 2 weights used:** 400 (regular) for body/label text, 600 (semibold) for headings, display, and inline emphasis (bold, Notes title). Labels are differentiated from body by smaller size (12px) and `muted-foreground` color — no third weight needed.
|
|
|
|
**Clock widget responsive exception:** Digital clock uses Display size (28px) at weight 600. When widget is resized larger (4x4+), clock scales proportionally via CSS `clamp()` up to approximately 40px. This is NOT a fifth type size — it is the Display size with responsive scaling applied via `font-size: clamp(28px, 4vw, 40px)`. Analog clock face uses SVG, not text.
|
|
|
|
**Markdown editor (Notes widget):** Inherits Body typography (14px/400/1.5). Bold uses weight 600. Code blocks use Label size (12px) in monospace. Notes widget title uses Body size (14px) at weight 600 (semibold) — differentiated from content by weight, not a separate size.
|
|
|
|
---
|
|
|
|
## Color
|
|
|
|
All values reference existing CSS custom properties from `globals.css`.
|
|
|
|
| Role | Token | Light Value | Dark Value | Usage |
|
|
|------|-------|-------------|------------|-------|
|
|
| Dominant (60%) | `--background` | oklch(0.99 0 0) | oklch(0.23 0.01 260) | Dashboard background, settings page background |
|
|
| Secondary (30%) | `--card` / `--sidebar` | oklch(1 0 0) / oklch(0.97 0 0) | oklch(0.27 0.01 260) / oklch(0.20 0.01 260) | Widget cards, settings sub-sidebar, calendar event cards |
|
|
| Accent (10%) | `--primary` | oklch(0.91 0.19 102) | oklch(0.91 0.19 102) | See reserved-for list below |
|
|
| Destructive | `--destructive` | oklch(0.55 0.2 27) | oklch(0.55 0.2 27) | Widget delete button (in edit mode only) |
|
|
|
|
**Accent reserved for:**
|
|
- Edit-mode toggle button (active state) — pencil icon button top-right
|
|
- "Widget hinzufuegen" / "Add widget" button in edit mode
|
|
- Active/selected widget border highlight during drag
|
|
- Save confirmation indicator when leaving edit mode
|
|
- Settings navigation active item indicator
|
|
- Calendar event accent stripe (left border on event cards)
|
|
|
|
**Additional semantic colors for calendar:**
|
|
- Calendar source color dots: Each calendar source gets a user-assigned color from a fixed palette of 8 colors. Colors are rendered as small dots (8px circle) next to event entries. Palette: `oklch(0.65 0.2 27)` (red), `oklch(0.65 0.2 150)` (green), `oklch(0.65 0.2 260)` (blue), `oklch(0.65 0.2 310)` (purple), `oklch(0.75 0.15 70)` (orange), `oklch(0.65 0.15 200)` (teal), `oklch(0.65 0.15 340)` (pink), `oklch(0.55 0 0)` (gray).
|
|
|
|
---
|
|
|
|
## Component Inventory
|
|
|
|
### Dashboard Grid
|
|
|
|
| Component | Description | Source |
|
|
|-----------|-------------|--------|
|
|
| `DashboardGrid` | react-grid-layout wrapper. 12-column grid, rowHeight 40px, margin [16, 16]. Handles drag/drop and resize. | New component |
|
|
| `EditModeToggle` | Pencil icon button, top-right of dashboard area. Toggles `isEditing` state. Active state: primary background. | New component (D-01) |
|
|
| `WidgetCard` | Wrapper for all widgets. Rounded card with border, shadow-sm. In edit mode: shows drag handle (top), resize handle (bottom-right), delete button (top-right). | New component |
|
|
| `AddWidgetButton` | Visible only in edit mode. Opens widget catalog modal. Uses primary color. | New component (D-01) |
|
|
| `WidgetCatalog` | Modal dialog showing available widget types as selectable cards (4 types: Clock, Search, Calendar, Notes). Each card shows icon + name + brief description. Selecting adds widget to grid at next available position. | New component |
|
|
|
|
### Widgets
|
|
|
|
| Component | Min Size (cols x rows) | Description |
|
|
|-----------|----------------------|-------------|
|
|
| `ClockWidget` | 2x2 | Digital clock (default). Shows time in configured timezone. Optional date below. Configurable style (analog/digital) in settings. |
|
|
| `SearchWidget` | 3x2 | Left: provider dropdown. Center: search input. Right: search button. Opens new browser tab. |
|
|
| `CalendarWidget` | 3x3 | Shows upcoming events list from selected calendar sources. Read-only. Each event: time, title, source color dot. |
|
|
| `NotesWidget` | 2x3 | Markdown editor with compact toolbar (Bold, Italic, Underline, List, Checkbox, Link, Code). Autosave with 1s debounce. Editable title above content. |
|
|
|
|
### Settings Page
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `SettingsLayout` | Layout with sub-sidebar (left, 220px) and content area (right). Accessible via user avatar menu in header. |
|
|
| `SettingsSubSidebar` | Left navigation within settings. Phase 05 categories: "Dashboard" (top-level), with sub-items: "Widgets", "Kalender" / "Calendar". |
|
|
| `WidgetSettingsPanel` | Lists all placed widget instances. Each expandable to show widget-specific config (clock: timezone + date toggle + style; search: custom providers; notes: title). |
|
|
| `CalendarSettingsPanel` | Manages calendar sources. List of configured sources + "Quelle hinzufuegen" / "Add source" button. Source form: name, type (CalDAV/Exchange/ICS), URL, credentials (optional). Toggle per source for visibility in widget. |
|
|
|
|
---
|
|
|
|
## Interaction Contracts
|
|
|
|
### Edit Mode Flow (D-01, D-02, D-03)
|
|
|
|
1. **Default state:** Dashboard shows widgets in their saved layout. No drag handles visible. Widgets are interactive (clock ticks, notes editable, search functional, calendar scrollable).
|
|
2. **Enter edit mode:** User clicks pencil icon (top-right, positioned at `right: 16px, top: 16px` relative to dashboard content area). Icon transitions to checkmark icon. Background gets subtle overlay `oklch(0 0 0 / 0.02)` in light, `oklch(1 1 0 / 0.02)` in dark.
|
|
3. **In edit mode:** Grid lines become visible (dashed border, `--border` color at 30% opacity). Each widget shows: drag handle bar (top, 6px height, `--muted` color), delete X button (top-right corner, `--destructive` on hover), resize handle (bottom-right corner, diagonal grip lines). "Widget hinzufuegen" floating button appears bottom-right.
|
|
4. **Drag/resize:** Widgets snap to grid. Other widgets reflow automatically (react-grid-layout default behavior). Dragged widget gets `ring-2 ring-primary` highlight and slight scale `scale(1.02)` with `shadow-lg`.
|
|
5. **Exit edit mode:** User clicks checkmark icon. Layout saves via API call. Checkmark transitions back to pencil icon. Edit mode UI elements (handles, grid lines, add button) disappear with 150ms fade.
|
|
6. **Empty dashboard (D-02):** Shows centered empty state: grid icon (48x48, muted), heading "Keine Widgets aktiv" / "No active widgets", body text "Klicken Sie auf Bearbeiten, um Widgets hinzuzufuegen" / "Click edit to add widgets", and the edit-mode pencil button is visible.
|
|
|
|
### Widget Catalog Modal
|
|
|
|
1. **Trigger:** "Widget hinzufuegen" button (only in edit mode).
|
|
2. **Appearance:** Centered modal, 480px max-width, with `bg-card` background, border, shadow-xl. Title: "Widget hinzufuegen" / "Add widget".
|
|
3. **Content:** 2x2 grid of selectable widget type cards. Each card: 56px icon area (muted background), widget name (14px semibold), one-line description (12px muted-foreground).
|
|
4. **Selection:** Click on card immediately adds widget instance to dashboard at first available grid position. Modal closes. Dashboard remains in edit mode.
|
|
5. **Close:** X button top-right or click outside modal.
|
|
6. **Widget types shown:**
|
|
- Uhr / Clock: clock icon, "Zeigt die aktuelle Uhrzeit an" / "Shows the current time"
|
|
- Suchleiste / Search: search icon, "Schnellsuche im Web" / "Quick web search"
|
|
- Kalender / Calendar: calendar icon, "Kommende Termine" / "Upcoming events"
|
|
- Notizen / Notes: file-text icon, "Freitext-Notizen mit Markdown" / "Free-text notes with Markdown"
|
|
|
|
### Widget Delete (D-07)
|
|
|
|
1. **Trigger:** Delete X button on widget card (visible only in edit mode).
|
|
2. **Behavior:** Immediate removal from grid (no confirmation dialog -- user can re-add). Layout reflows.
|
|
3. **Persistence:** Deletion is persisted when user exits edit mode (saves layout).
|
|
|
|
### Settings Navigation (D-19, D-20)
|
|
|
|
1. **Entry point:** User clicks avatar in header -> dropdown menu -> "Einstellungen" / "Settings" link. Navigates to `/settings`.
|
|
2. **Layout:** Sub-sidebar (220px, `bg-sidebar` background, left border `border-sidebar-border`) with settings categories. Content area fills remaining width.
|
|
3. **Active indicator:** Active settings category has `bg-sidebar-accent text-sidebar-accent-foreground` background, matching main sidebar active state pattern.
|
|
4. **Back navigation:** Settings page header includes "Zurueck zum Dashboard" / "Back to Dashboard" link with left arrow icon.
|
|
|
|
### Calendar Source Management (D-08, D-09, D-11)
|
|
|
|
1. **Source list:** Each source shows: color dot, name, type badge (CalDAV/Exchange/ICS), toggle switch for widget visibility, edit/delete actions.
|
|
2. **Add source form:** Appears below list or in expandable section. Fields: Name (text), Type (select: CalDAV, Exchange, ICS), URL (text), Username (text, optional for CalDAV/Exchange), Password (password, optional for CalDAV/Exchange). Save button.
|
|
3. **Validation:** URL field validates URL format. Name is required. Type is required.
|
|
4. **Connection test:** After save, system attempts to fetch events. Success: green checkmark next to source. Failure: orange warning icon with tooltip showing error.
|
|
|
|
### Search Widget Interaction (D-14, D-15)
|
|
|
|
1. **Layout:** Horizontal. Left: dropdown (120px width, shows current provider name). Center: text input (flex-1). Right: search button (icon + "Suchen" / "Search" on wider sizes, icon-only on narrow).
|
|
2. **Behavior:** Enter key or button click opens `{provider_url}?q={query}` in new browser tab (`window.open`).
|
|
3. **Default providers:** Google, Bing, DuckDuckGo. Selected provider persists per widget instance.
|
|
|
|
### Notes Widget Interaction (D-16, D-17, D-18)
|
|
|
|
1. **Layout:** Top: editable title (14px semibold, contenteditable or input — Body size at weight 600). Below: compact toolbar (icon buttons: B, I, U, List, Checkbox, Link, Code). Below: editor area (fills remaining widget height, scrollable).
|
|
2. **Autosave:** Content saves to API 1000ms after last keystroke. No save indicator needed (saves silently). On API error: show small red dot in widget top-right corner.
|
|
3. **Markdown rendering:** Live WYSIWYG editing (not split-view). Toolbar buttons apply formatting inline.
|
|
|
|
### Responsive Behavior (D-21, D-22)
|
|
|
|
1. **Desktop (>= 768px):** 12-column grid. Grid scales proportionally with container width. `--current-sidebar-width` CSS variable adjusts available space.
|
|
2. **Mobile (< 768px):** Widgets stack vertically in a single column. Each widget takes full width. Order matches grid layout top-to-bottom, left-to-right. Edit mode is available but drag/resize is disabled -- only add/delete widgets.
|
|
|
|
---
|
|
|
|
## Copywriting Contract
|
|
|
|
### German (Primary)
|
|
|
|
| Element | Copy |
|
|
|---------|------|
|
|
| Primary CTA (edit mode) | "Widget hinzufuegen" |
|
|
| Edit mode toggle (enter) | aria-label: "Dashboard bearbeiten" |
|
|
| Edit mode toggle (exit/save) | aria-label: "Aenderungen speichern" |
|
|
| Empty state heading | "Keine Widgets aktiv" |
|
|
| Empty state body | "Klicken Sie auf Bearbeiten, um Widgets hinzuzufuegen." |
|
|
| Widget catalog title | "Widget hinzufuegen" |
|
|
| Widget delete tooltip | "Widget entfernen" |
|
|
| Settings link (header menu) | "Einstellungen" |
|
|
| Settings back link | "Zurueck zum Dashboard" |
|
|
| Settings category: widgets | "Widgets" |
|
|
| Settings category: calendar | "Kalender" |
|
|
| Calendar add source CTA | "Quelle hinzufuegen" |
|
|
| Calendar source empty state | "Keine Kalenderquellen eingerichtet. Fuegen Sie eine Quelle hinzu, um Termine anzuzeigen." |
|
|
| Calendar widget empty (no sources) | "Keine Kalenderquellen konfiguriert" |
|
|
| Calendar widget empty (no events) | "Keine anstehenden Termine" |
|
|
| Calendar connection success | "Verbindung erfolgreich" |
|
|
| Calendar connection error | "Verbindung fehlgeschlagen. Bitte ueberpruefen Sie die URL und Zugangsdaten." |
|
|
| Search widget placeholder | "Suchen..." |
|
|
| Notes widget default title | "Notiz" |
|
|
| Notes autosave error indicator | tooltip: "Speichern fehlgeschlagen" |
|
|
| Error state (layout load) | "Dashboard konnte nicht geladen werden. Bitte laden Sie die Seite neu." |
|
|
| Error state (widget save) | "Aenderungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut." |
|
|
| Clock widget date format | "dd. MMMM yyyy" (e.g. "23. Juni 2026") |
|
|
| Settings source delete confirm | "Moechten Sie diese Kalenderquelle wirklich loeschen?" |
|
|
| Settings source delete CTA | "Quelle loeschen" |
|
|
| Settings provider delete confirm | "Moechten Sie diesen Suchanbieter wirklich loeschen?" |
|
|
| Settings provider delete CTA | "Anbieter loeschen" |
|
|
| Destructive cancel button | "Abbrechen" |
|
|
|
|
### English
|
|
|
|
| Element | Copy |
|
|
|---------|------|
|
|
| Primary CTA (edit mode) | "Add widget" |
|
|
| Edit mode toggle (enter) | aria-label: "Edit dashboard" |
|
|
| Edit mode toggle (exit/save) | aria-label: "Save changes" |
|
|
| Empty state heading | "No active widgets" |
|
|
| Empty state body | "Click edit to add widgets to your dashboard." |
|
|
| Widget catalog title | "Add widget" |
|
|
| Widget delete tooltip | "Remove widget" |
|
|
| Settings link (header menu) | "Settings" |
|
|
| Settings back link | "Back to Dashboard" |
|
|
| Settings category: widgets | "Widgets" |
|
|
| Settings category: calendar | "Calendar" |
|
|
| Calendar add source CTA | "Add source" |
|
|
| Calendar source empty state | "No calendar sources configured. Add a source to display events." |
|
|
| Calendar widget empty (no sources) | "No calendar sources configured" |
|
|
| Calendar widget empty (no events) | "No upcoming events" |
|
|
| Calendar connection success | "Connection successful" |
|
|
| Calendar connection error | "Connection failed. Please check the URL and credentials." |
|
|
| Search widget placeholder | "Search..." |
|
|
| Notes widget default title | "Note" |
|
|
| Notes autosave error indicator | tooltip: "Save failed" |
|
|
| Error state (layout load) | "Could not load dashboard. Please reload the page." |
|
|
| Error state (widget save) | "Could not save changes. Please try again." |
|
|
| Clock widget date format | "MMMM dd, yyyy" (e.g. "June 23, 2026") |
|
|
| Settings source delete confirm | "Are you sure you want to delete this calendar source?" |
|
|
| Settings source delete CTA | "Delete source" |
|
|
| Settings provider delete confirm | "Are you sure you want to delete this search provider?" |
|
|
| Settings provider delete CTA | "Delete provider" |
|
|
| Destructive cancel button | "Cancel" |
|
|
|
|
### Destructive Actions
|
|
|
|
| Action | Confirmation Approach |
|
|
|--------|----------------------|
|
|
| Delete widget (edit mode) | No confirmation (instant, re-addable, not persisted until save) |
|
|
| Delete calendar source | Confirmation dialog: heading + body text + "Quelle loeschen"/"Delete source" button (destructive color) + "Abbrechen"/"Cancel" button |
|
|
| Delete custom search provider | Confirmation dialog: heading + body text + "Anbieter loeschen"/"Delete provider" button (destructive color) + "Abbrechen"/"Cancel" button |
|
|
|
|
---
|
|
|
|
## Registry Safety
|
|
|
|
| Registry | Blocks Used | Safety Gate |
|
|
|----------|-------------|-------------|
|
|
| shadcn official | not initialized — no CLI blocks used | not applicable |
|
|
| Third-party | none | not applicable |
|
|
|
|
**Note:** No component registry is used. All components are hand-written following shadcn CSS variable conventions. react-grid-layout is installed as an npm package (not a registry block). Markdown editor library selection is at executor discretion (recommended: lightweight option like @tiptap/starter-kit or similar, evaluated during planning).
|
|
|
|
---
|
|
|
|
## Widget Size Constraints (D-06)
|
|
|
|
| Widget Type | Min (cols x rows) | Default (cols x rows) | Max |
|
|
|-------------|-------------------|----------------------|-----|
|
|
| Clock | 2 x 2 | 2 x 2 | 4 x 4 |
|
|
| Search | 3 x 2 | 6 x 2 | 12 x 2 |
|
|
| Calendar | 3 x 3 | 4 x 6 | 12 x 12 |
|
|
| Notes | 2 x 3 | 3 x 4 | 12 x 12 |
|
|
|
|
---
|
|
|
|
## State Management
|
|
|
|
| Store | Purpose | Persistence |
|
|
|-------|---------|-------------|
|
|
| `dashboard-store` (Zustand) | Client-side edit mode state, current layout, widget instances, dirty flag | Memory only (layout fetched from API on mount) |
|
|
| API: `GET /dashboard/layout` | Fetch user's saved layout + widget configs | PostgreSQL via Prisma |
|
|
| API: `PUT /dashboard/layout` | Save layout + widget configs | PostgreSQL via Prisma |
|
|
| API: `GET /calendar/sources` | Fetch user's calendar sources | PostgreSQL via Prisma |
|
|
| API: `POST /calendar/sources` | Add calendar source | PostgreSQL via Prisma |
|
|
| API: `DELETE /calendar/sources/:id` | Delete calendar source | PostgreSQL via Prisma |
|
|
| API: `GET /calendar/events` | Fetch events from all active sources | Proxied from external calendar servers |
|
|
|
|
---
|
|
|
|
## Accessibility
|
|
|
|
| Concern | Implementation |
|
|
|---------|----------------|
|
|
| Edit mode toggle | `aria-label` updates based on state. `aria-pressed` attribute. |
|
|
| Drag and drop | react-grid-layout provides keyboard support via arrow keys when focused. Each widget has `role="article"` and `aria-label` with widget type + instance name. |
|
|
| Widget catalog modal | `role="dialog"`, `aria-modal="true"`, focus trap, Escape to close. Widget type cards are buttons with `aria-label`. |
|
|
| Color contrast | All text meets WCAG 2.1 AA (4.5:1 for body, 3:1 for large text). Calendar color dots are decorative only -- source name is always shown as text. |
|
|
| Settings navigation | Sub-sidebar uses `role="navigation"` with `aria-label="Einstellungen"` / `"Settings"`. Active item uses `aria-current="page"`. |
|
|
| Keyboard navigation | Tab order: edit toggle -> widgets (left-to-right, top-to-bottom) -> add widget button (in edit mode). Settings: sub-sidebar items -> form fields. |
|
|
|
|
---
|
|
|
|
## Checker Sign-Off
|
|
|
|
- [ ] Dimension 1 Copywriting: PASS
|
|
- [ ] Dimension 2 Visuals: PASS
|
|
- [ ] Dimension 3 Color: PASS
|
|
- [ ] Dimension 4 Typography: PASS
|
|
- [ ] Dimension 5 Spacing: PASS
|
|
- [ ] Dimension 6 Registry Safety: PASS
|
|
|
|
**Approval:** pending
|