* feat(ST-1109.2): add PbiStatus enum and status field to Pbi model - New PbiStatus enum (READY/BLOCKED/DONE) for PBI lifecycle tracking - Pbi.status PbiStatus @default(READY) - Index on (product_id, status) for filter queries - Migration: 20260429150643_add_pbi_status - ERD regenerated via prisma generate Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ST-1109.3): add PBI status API mappers - pbiStatusToApi / pbiStatusFromApi following same pattern as task/story - PbiStatusApi type derived from PBI_DB_TO_API - PBI_STATUS_API_VALUES export for downstream Zod schemas - Lowercase API surface (ready/blocked/done), DB stays UPPER_SNAKE Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ST-1109.4): support status in PBI create/update actions - Optional status field in Zod schemas (lowercase API: ready/blocked/done) - pbiStatusFromApi() maps to DB enum before persistence - Status omitted on create => Prisma @default(READY) takes effect - Update preserves existing status when not provided - Demo-check unchanged Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ST-1109.5): auto-mark PBI as DONE when all its stories are DONE on sprint close Extends completeSprintAction's $transaction with PBI status cascade: - Pre-transaction: identify PBIs touched by this close (via stories.pbi_id), fetch each with all its stories - Skip PBIs already DONE; skip PBIs with 0 stories - Mark PBI DONE only when every story (post-decision) is DONE — stories outside the sprint are evaluated against their current DB status - Promote-only: never demotes a PBI that becomes "incomplete" again Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ST-1109.6): add Popover primitive (base-ui wrapper) - Mirrors the Tooltip pattern: render-prop composition, data-slot attrs - Exports Popover (Root), PopoverTrigger, PopoverContent (Portal+Positioner+Popup) - MD3 popover/popover-foreground tokens, animated open/close states - Will be used to consolidate the backlog filter UI in ST-1109.8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ST-1109.7): add status select to PBI dialog - New components/shared/pbi-status-select.tsx mirrors PrioritySelect: PBI_STATUS_LABELS (NL), PBI_STATUS_COLORS, PbiStatusSelect component - Reuses existing --status-todo/blocked/done MD3 tokens - PbiDialog: status state with sync-on-open; default 'ready' for create, pbi.status for edit; hidden input submits lowercase API value - Priority + Status sit side-by-side in 2-col grid - PbiDialogPbi.status is optional; pbi-list.tsx will populate in ST-1109.8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ST-1109.8): show PBI status badge and consolidate filters into popover - Pbi.status (lowercase API) flows from page.tsx via pbiStatusToApi - Status badge rendered in BacklogCard's badge slot using PBI_STATUS_COLORS - Two old Select dropdowns replaced by single Popover with three pill-button sections (Sorteren, Prioriteit, Status) and a "Wis filters" footer - Filter trigger shows active count "(n)" badge in label - Active priority/status filters still surface as dismissable chips next to the trigger for at-a-glance feedback - onEdit passes the full Pbi (incl. status) so the dialog opens with the correct current status — closes the data flow loop opened in ST-1109.7 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(ST-1109.9): cover PBI status mappers and sprint-close cascade - __tests__/lib/task-status.test.ts: 11 cases incl. round-trip + invalid input for task/story/pbi mappers; verifies PBI_STATUS_API_VALUES shape - __tests__/actions/sprints-cascade.test.ts: 8 cases for completeSprintAction: promote on all-DONE, no promote on partial OPEN, respect out-of-sprint story status, skip already-DONE PBIs, multi-PBI cascade, 0-story guard, demo-user block - Full vitest run: 170/170 green across 21 files Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ST-1109.10): document PbiStatus enum, sprint-close cascade, and filter UI - docs/scrum4me-architecture.md: pbis-table updated with status column + index; PbiStatus enum + Pbi model in the Prisma schema sample; cascade-on-sprint-close rule documented inline - docs/scrum4me-styling.md: short note pointing to PBI_STATUS_LABELS / PBI_STATUS_COLORS in components/shared/pbi-status-select.tsx so future components don't ad-hoc-copy the color map - docs/plans/ST-1109-pbi-status.md: in-repo mirror of the approved plan (per feedback_plan_location memory) with cascade pseudo-code and end-to-end verification checklist Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ST-1109.11): persist backlog filters in localStorage Filters reset op reload was verwarrend. Nu net als sortMode: - scrum4me:pbi_filter_priority — 'all' | '1' | '2' | '3' | '4' - scrum4me:pbi_filter_status — 'all' | 'ready' | 'blocked' | 'done' useState-init met SSR-guard; ongeldige waarden vallen terug op 'all'. Wis filters reset alle drie de keys correct (sortMode -> 'priority', beide filters -> 'all'), waardoor de localStorage-staat consistent wordt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
670 lines
18 KiB
Markdown
670 lines
18 KiB
Markdown
# Scrum4Me — Styling & Design System
|
|
|
|
**Versie:** 0.1 — april 2026
|
|
**Onderdeel van:** CLAUDE.md context-set
|
|
|
|
---
|
|
|
|
## Overzicht
|
|
|
|
Scrum4Me gebruikt **Material Design 3 (MD3)** als kleurfilosofie, geïmplementeerd via CSS custom properties in `theme.css` en direct bruikbaar als Tailwind utility classes. **shadcn/ui** levert alle UI-primitieven (Button, Dialog, Sheet, Badge, etc.) en is volledig compatibel met het MD3-kleurensysteem via de legacy-token-mapping.
|
|
|
|
Lees dit document voordat je een component schrijft. Gebruik **nooit** willekeurige Tailwind-kleuren zoals `bg-blue-500` of `bg-green-600` — gebruik altijd de semantische tokens uit dit systeem.
|
|
|
|
---
|
|
|
|
## Setup
|
|
|
|
### 1. theme.css plaatsen
|
|
|
|
Kopieer het meegeleverde `theme.css` bestand naar:
|
|
|
|
```
|
|
app/globals.css ← importeer theme.css hier, of plak de inhoud direct
|
|
```
|
|
|
|
Of als apart bestand:
|
|
```
|
|
styles/theme.css
|
|
```
|
|
|
|
Importeer in `app/globals.css`:
|
|
```css
|
|
@import './styles/theme.css';
|
|
```
|
|
|
|
### 2. shadcn/ui initialiseren
|
|
|
|
```bash
|
|
npx shadcn@latest init
|
|
```
|
|
|
|
Kies bij de setup:
|
|
- Style: **Default**
|
|
- Base color: **Slate** (wordt overschreven door theme.css)
|
|
- CSS variables: **Yes**
|
|
|
|
De `theme.css` overschrijft alle shadcn default-kleuren via CSS custom properties. Geen extra configuratie nodig.
|
|
|
|
### 3. Tailwind configuratie
|
|
|
|
`theme.css` registreert alle tokens via `@theme inline` — ze zijn direct beschikbaar als Tailwind utility classes:
|
|
|
|
```tsx
|
|
// Werkt direct:
|
|
className="bg-primary text-primary-foreground"
|
|
className="bg-surface-container-low"
|
|
className="bg-status-done"
|
|
className="bg-priority-critical"
|
|
```
|
|
|
|
### 4. Dark mode
|
|
|
|
Dark mode werkt via de `.dark` class op `<html>`:
|
|
|
|
```tsx
|
|
// components/theme-toggle.tsx
|
|
'use client'
|
|
import { useState, useEffect } from 'react'
|
|
|
|
export function ThemeToggle() {
|
|
const [isDark, setIsDark] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const stored = localStorage.getItem('theme')
|
|
if (stored === 'dark') {
|
|
document.documentElement.classList.add('dark')
|
|
setIsDark(true)
|
|
}
|
|
}, [])
|
|
|
|
const toggle = () => {
|
|
document.documentElement.classList.toggle('dark')
|
|
const next = !isDark
|
|
setIsDark(next)
|
|
localStorage.setItem('theme', next ? 'dark' : 'light')
|
|
}
|
|
|
|
return (
|
|
<button onClick={toggle} className="text-muted-foreground hover:text-foreground">
|
|
{isDark ? '☀️' : '🌙'}
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Kleurfilosofie
|
|
|
|
Drie hoofdrollen, elk met een semantische betekenis voor een Scrum-planner:
|
|
|
|
| Rol | Kleur | Betekenis | Gebruik in Scrum4Me |
|
|
|---|---|---|---|
|
|
| **Primary** | Blauw `#0061a4` | Productiviteit, vertrouwen | Primaire knoppen, actieve navigatie, Sprint Goal |
|
|
| **Secondary** | Paars `#5b5e71` | Planning, organisatie | Secundaire acties, filters, toolbar-items |
|
|
| **Tertiary** | Teal `#006874` | Voortgang, data | Voortgangsindicatoren, story-tellers, metrics |
|
|
|
|
Diepte wordt gecreëerd via **tonal elevation** (lichtere/donkerdere oppervlakken), niet via schaduwen.
|
|
|
|
---
|
|
|
|
## Surface Elevation System
|
|
|
|
Gebruik deze hiërarchie consequent — nooit `shadow-lg` voor diepte:
|
|
|
|
```
|
|
HOOGSTE ELEVATIE (voorgrond)
|
|
surface-container-lowest → dialogs, modals, popovers
|
|
surface-container-low → kaarten, panelen
|
|
surface-container → standaard container
|
|
surface-container-high → geneste containers
|
|
surface-container-highest → achtergrondcontainers
|
|
LAAGSTE ELEVATIE (achtergrond)
|
|
background → app-achtergrond
|
|
```
|
|
|
|
### In Scrum4Me specifiek
|
|
|
|
| Element | Surface token |
|
|
|---|---|
|
|
| App achtergrond | `bg-background` |
|
|
| Navigatiebalk | `bg-surface-container-low` |
|
|
| Gesplitst scherm (elk paneel) | `bg-surface-container-low` |
|
|
| PBI-rij | `bg-surface-container` |
|
|
| Geselecteerde PBI-rij | `bg-primary-container` |
|
|
| Story-blok | `bg-surface-container-low border border-border` |
|
|
| Story-blok (geselecteerd) | `bg-primary-container border border-primary` |
|
|
| Taakregel | `bg-surface-container` |
|
|
| Dialogs / modals | `bg-surface-container-lowest` |
|
|
| Slide-over (story detail) | `bg-surface-container-lowest` |
|
|
| Todo-item | `bg-surface-container` |
|
|
| Navigatiebar per paneel | `bg-surface-container-highest` |
|
|
|
|
---
|
|
|
|
## Statuskleur mapping
|
|
|
|
### Story- en taakstatus
|
|
|
|
Gebruik **altijd** icoon + tekst naast kleur (toegankelijkheid):
|
|
|
|
```tsx
|
|
// Status badge component
|
|
const statusConfig = {
|
|
OPEN: {
|
|
label: 'Open',
|
|
className: 'bg-status-todo text-white',
|
|
},
|
|
IN_SPRINT: {
|
|
label: 'In Sprint',
|
|
className: 'bg-status-in-progress text-white',
|
|
},
|
|
DONE: {
|
|
label: 'Done',
|
|
className: 'bg-status-done text-white',
|
|
},
|
|
}
|
|
|
|
// Taakstatus
|
|
const taskStatusConfig = {
|
|
TO_DO: {
|
|
label: 'To Do',
|
|
className: 'bg-status-todo text-white',
|
|
},
|
|
IN_PROGRESS: {
|
|
label: 'In Progress',
|
|
className: 'bg-status-in-progress text-white',
|
|
},
|
|
DONE: {
|
|
label: 'Done',
|
|
className: 'bg-status-done text-white',
|
|
},
|
|
}
|
|
```
|
|
|
|
### Prioriteitskleur mapping
|
|
|
|
```tsx
|
|
const priorityConfig = {
|
|
1: {
|
|
label: 'Kritiek',
|
|
className: 'bg-priority-critical text-white',
|
|
borderClassName: 'border-l-4 border-priority-critical',
|
|
},
|
|
2: {
|
|
label: 'Hoog',
|
|
className: 'bg-priority-high text-white',
|
|
borderClassName: 'border-l-4 border-priority-high',
|
|
},
|
|
3: {
|
|
label: 'Middel',
|
|
className: 'bg-priority-medium text-white',
|
|
borderClassName: 'border-l-4 border-priority-medium',
|
|
},
|
|
4: {
|
|
label: 'Laag',
|
|
className: 'bg-priority-low text-white',
|
|
borderClassName: 'border-l-4 border-priority-low',
|
|
},
|
|
}
|
|
```
|
|
|
|
### Story-activiteitenlog
|
|
|
|
```tsx
|
|
const logTypeConfig = {
|
|
IMPLEMENTATION_PLAN: {
|
|
label: 'Implementatieplan',
|
|
className: 'bg-info-container text-info-container-foreground border-l-4 border-info',
|
|
},
|
|
TEST_RESULT: {
|
|
PASSED: {
|
|
label: 'Tests geslaagd',
|
|
className: 'bg-success-container text-success-container-foreground border-l-4 border-success',
|
|
},
|
|
FAILED: {
|
|
label: 'Tests mislukt',
|
|
className: 'bg-error-container text-error-container-foreground border-l-4 border-error',
|
|
},
|
|
},
|
|
COMMIT: {
|
|
label: 'Commit',
|
|
className: 'bg-secondary-container text-secondary-container-foreground border-l-4 border-secondary',
|
|
},
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## shadcn/ui componenten — gebruik in Scrum4Me
|
|
|
|
Alle shadcn-componenten gebruiken automatisch het MD3-kleurensysteem. Hieronder de aanbevolen varianten per context.
|
|
|
|
### Button
|
|
|
|
```tsx
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
// Primaire actie (Sprint starten, PBI aanmaken, Opslaan)
|
|
<Button>Sprint starten</Button>
|
|
|
|
// Secundaire actie (Annuleren, Filters, Exporteren)
|
|
<Button variant="secondary">Annuleren</Button>
|
|
|
|
// Destructieve actie (Verwijderen, Archiveren)
|
|
<Button variant="destructive">Verwijderen</Button>
|
|
|
|
// Ghost (icon-knoppen in navigatiebar)
|
|
<Button variant="ghost" size="icon">
|
|
<PlusIcon className="h-4 w-4" />
|
|
</Button>
|
|
|
|
// Outline (minder urgente acties)
|
|
<Button variant="outline">Details bekijken</Button>
|
|
```
|
|
|
|
### Badge (status en prioriteit)
|
|
|
|
```tsx
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
// Gebruik custom className voor MD3-kleuren
|
|
// shadcn Badge variant="secondary" is ook bruikbaar voor neutrale badges
|
|
|
|
<Badge className="bg-status-done text-white">Done</Badge>
|
|
<Badge className="bg-priority-critical text-white">Kritiek</Badge>
|
|
<Badge className="bg-status-in-progress text-white">In Sprint</Badge>
|
|
|
|
// Neutrale info badge (bijv. "3 taken")
|
|
<Badge variant="secondary">3 taken</Badge>
|
|
```
|
|
|
|
**PBI-status (READY / BLOCKED / DONE):** hergebruikt bestaande tokens —
|
|
`status-todo` voor READY, `status-blocked` voor BLOCKED, `status-done` voor
|
|
DONE. Centraal gedefinieerd in `components/shared/pbi-status-select.tsx`
|
|
(`PBI_STATUS_LABELS`, `PBI_STATUS_COLORS`); importeer die in plaats van
|
|
kleuren ad-hoc te kopiëren.
|
|
|
|
### Dialog (bevestigingsdialogen)
|
|
|
|
```tsx
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from '@/components/ui/alert-dialog'
|
|
|
|
// Standaard bevestigingsdialoog voor verwijderacties
|
|
<AlertDialogContent className="bg-surface-container-lowest">
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>PBI verwijderen?</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
Dit verwijdert ook alle gekoppelde stories en taken. Deze actie is niet ongedaan te maken.
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>Annuleren</AlertDialogCancel>
|
|
<AlertDialogAction className="bg-destructive text-destructive-foreground">
|
|
Verwijderen
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
```
|
|
|
|
### Sheet (story detail slide-over)
|
|
|
|
```tsx
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'
|
|
|
|
<Sheet>
|
|
<SheetContent
|
|
side="right"
|
|
className="w-[480px] bg-surface-container-lowest border-l border-border"
|
|
>
|
|
<SheetHeader>
|
|
<SheetTitle className="text-foreground">{story.title}</SheetTitle>
|
|
</SheetHeader>
|
|
{/* story detail inhoud */}
|
|
</SheetContent>
|
|
</Sheet>
|
|
```
|
|
|
|
### Input en Textarea
|
|
|
|
```tsx
|
|
import { Input } from '@/components/ui/input'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
|
|
// shadcn Input gebruikt --input-background automatisch uit theme.css
|
|
<Input
|
|
placeholder="PBI titel"
|
|
className="bg-input-background border-border focus:ring-primary"
|
|
/>
|
|
|
|
<Textarea
|
|
placeholder="Omschrijving (optioneel)"
|
|
className="bg-input-background border-border focus:ring-primary resize-none"
|
|
/>
|
|
```
|
|
|
|
### Select (prioriteit dropdown)
|
|
|
|
```tsx
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select'
|
|
|
|
<Select>
|
|
<SelectTrigger className="bg-input-background border-border">
|
|
<SelectValue placeholder="Prioriteit" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-surface-container-lowest border-border">
|
|
<SelectItem value="1">
|
|
<span className="flex items-center gap-2">
|
|
<span className="w-2 h-2 rounded-full bg-priority-critical" />
|
|
Kritiek
|
|
</span>
|
|
</SelectItem>
|
|
<SelectItem value="2">
|
|
<span className="flex items-center gap-2">
|
|
<span className="w-2 h-2 rounded-full bg-priority-high" />
|
|
Hoog
|
|
</span>
|
|
</SelectItem>
|
|
<SelectItem value="3">
|
|
<span className="flex items-center gap-2">
|
|
<span className="w-2 h-2 rounded-full bg-priority-medium" />
|
|
Middel
|
|
</span>
|
|
</SelectItem>
|
|
<SelectItem value="4">
|
|
<span className="flex items-center gap-2">
|
|
<span className="w-2 h-2 rounded-full bg-priority-low" />
|
|
Laag
|
|
</span>
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
```
|
|
|
|
### Skeleton (loading states)
|
|
|
|
```tsx
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
|
|
// PBI lijst skeleton
|
|
function PbiListSkeleton() {
|
|
return (
|
|
<div className="space-y-2 p-4">
|
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
<Skeleton key={i} className="h-12 w-full bg-surface-container-high" />
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Story blokken skeleton
|
|
function StoryGridSkeleton() {
|
|
return (
|
|
<div className="flex flex-wrap gap-3 p-4">
|
|
{Array.from({ length: 6 }).map((_, i) => (
|
|
<Skeleton key={i} className="h-24 w-[10%] min-w-[100px] bg-surface-container-high" />
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Tooltip (demo-gebruiker write-protection)
|
|
|
|
```tsx
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
|
|
// Gebruik voor alle uitgeschakelde knoppen bij demo-gebruiker
|
|
function DemoProtectedButton({ children, isDemo, onClick, ...props }) {
|
|
if (!isDemo) {
|
|
return <Button onClick={onClick} {...props}>{children}</Button>
|
|
}
|
|
|
|
return (
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span>
|
|
<Button disabled {...props}>{children}</Button>
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent className="bg-surface-container-lowest border-border">
|
|
<p className="text-sm text-muted-foreground">Niet beschikbaar in demo-modus</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Scrum4Me component patronen
|
|
|
|
### PBI-rij
|
|
|
|
```tsx
|
|
// Geselecteerd PBI heeft primary-container achtergrond
|
|
<div
|
|
className={cn(
|
|
"flex items-center gap-3 px-4 py-3 rounded-lg cursor-pointer transition-colors border-l-4",
|
|
priorityConfig[pbi.priority].borderClassName,
|
|
isSelected
|
|
? "bg-primary-container text-primary-container-foreground"
|
|
: "bg-surface-container hover:bg-surface-container-high"
|
|
)}
|
|
onClick={() => setSelectedPbi(pbi.id)}
|
|
>
|
|
<span className="flex-1 text-sm font-medium truncate">{pbi.title}</span>
|
|
<Badge className={priorityConfig[pbi.priority].className}>
|
|
{priorityConfig[pbi.priority].label}
|
|
</Badge>
|
|
</div>
|
|
```
|
|
|
|
### Story-blok
|
|
|
|
```tsx
|
|
// ~10% schermbreedte, compacte weergave
|
|
<div
|
|
className={cn(
|
|
"relative flex flex-col gap-1 p-3 rounded-lg border cursor-pointer",
|
|
"min-w-[100px] w-[10%] h-24 text-xs",
|
|
"transition-colors hover:border-primary",
|
|
"bg-surface-container-low border-border"
|
|
)}
|
|
>
|
|
<span className="font-medium leading-tight line-clamp-2">{story.title}</span>
|
|
<div className="mt-auto flex items-center justify-between">
|
|
<span className={cn("px-1.5 py-0.5 rounded text-[10px] font-medium", statusConfig[story.status].className)}>
|
|
{statusConfig[story.status].label}
|
|
</span>
|
|
<span className={cn("w-2 h-2 rounded-full", `bg-priority-${priorityLabel}`)}>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Prioriteitsgroep scheidingslijn
|
|
|
|
```tsx
|
|
// Visuele scheiding per prioriteitsgroep in PBI-lijst en story-grid
|
|
<div className="mt-4 mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className={cn(
|
|
"text-xs font-semibold uppercase tracking-wider",
|
|
priority === 1 && "text-priority-critical",
|
|
priority === 2 && "text-priority-high",
|
|
priority === 3 && "text-priority-medium",
|
|
priority === 4 && "text-priority-low",
|
|
)}>
|
|
{priorityConfig[priority].label}
|
|
</span>
|
|
<div className={cn(
|
|
"flex-1 h-px",
|
|
priority === 1 && "bg-priority-critical/30",
|
|
priority === 2 && "bg-priority-high/30",
|
|
priority === 3 && "bg-priority-medium/30",
|
|
priority === 4 && "bg-priority-low/30",
|
|
)} />
|
|
<span className="text-xs text-muted-foreground">{count}</span>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Voortgangsindicator (story → taken)
|
|
|
|
```tsx
|
|
// Gebruikt tertiary kleur voor voortgang
|
|
function StoryProgress({ done, total }: { done: number; total: number }) {
|
|
const pct = total === 0 ? 0 : Math.round((done / total) * 100)
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 text-xs">
|
|
<div className="flex-1 h-1.5 bg-surface-container-highest rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-tertiary rounded-full transition-all"
|
|
style={{ width: `${pct}%` }}
|
|
/>
|
|
</div>
|
|
<span className="text-muted-foreground tabular-nums">
|
|
{done}/{total}
|
|
</span>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Activiteitenlog entry
|
|
|
|
```tsx
|
|
function LogEntry({ entry }: { entry: StoryLog }) {
|
|
const config = entry.type === 'TEST_RESULT'
|
|
? logTypeConfig.TEST_RESULT[entry.status ?? 'PASSED']
|
|
: logTypeConfig[entry.type]
|
|
|
|
return (
|
|
<div className={cn("rounded-lg p-3 text-sm", config.className)}>
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="font-medium text-xs uppercase tracking-wide">
|
|
{config.label}
|
|
</span>
|
|
<span className="text-xs opacity-70">
|
|
{formatDate(entry.created_at)}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
|
{entry.content}
|
|
</p>
|
|
{entry.commit_hash && (
|
|
<a
|
|
href={`${repoUrl}/commit/${entry.commit_hash}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="mt-1 inline-flex items-center gap-1 text-xs font-mono opacity-80 hover:opacity-100 underline"
|
|
>
|
|
{entry.commit_hash.slice(0, 7)} — {entry.commit_message}
|
|
</a>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Sprint Goal banner
|
|
|
|
```tsx
|
|
// Prominent bovenaan Sprint-schermen
|
|
<div className="bg-primary-container text-primary-container-foreground rounded-lg px-4 py-3 mb-4">
|
|
<span className="text-xs font-semibold uppercase tracking-wider opacity-70">
|
|
Sprint Goal
|
|
</span>
|
|
<p className="mt-0.5 font-medium">{sprint.sprint_goal}</p>
|
|
</div>
|
|
```
|
|
|
|
### Toast notificaties (Sonner)
|
|
|
|
```tsx
|
|
import { toast } from 'sonner'
|
|
|
|
// Success (aanmaken, opslaan)
|
|
toast.success('PBI aangemaakt')
|
|
toast.success('Story toegevoegd aan Sprint')
|
|
|
|
// Error (mislukte Server Action)
|
|
toast.error('Opslaan mislukt. Probeer opnieuw.')
|
|
|
|
// Info (neutrale melding)
|
|
toast.info('Sprint afgerond')
|
|
|
|
// Geen toast bij drag-and-drop (te frequent)
|
|
```
|
|
|
|
---
|
|
|
|
## Regels (nooit overtreden)
|
|
|
|
```
|
|
❌ bg-blue-500, bg-green-600, bg-red-400 → gebruik semantische tokens
|
|
❌ shadow-lg, shadow-md → gebruik surface elevation
|
|
❌ opacity-50 op een primary button → gebruik -container variant
|
|
❌ Kleur alleen voor status (geen tekst) → altijd tekst + kleur
|
|
❌ Hardcoded hex-waarden in className → altijd via CSS token
|
|
❌ bg-white of bg-black → bg-background of bg-foreground
|
|
|
|
✅ bg-primary text-primary-foreground
|
|
✅ bg-surface-container-low
|
|
✅ bg-status-done + tekst "Done"
|
|
✅ bg-error-container text-error-container-foreground
|
|
✅ border-l-4 border-priority-critical
|
|
```
|
|
|
|
---
|
|
|
|
## Bestandslocaties
|
|
|
|
```
|
|
styles/
|
|
theme.css ← bronbestand, niet aanpassen
|
|
app/
|
|
globals.css ← importeert theme.css
|
|
components/
|
|
ui/ ← shadcn/ui (auto-gegenereerd, niet aanpassen)
|
|
shared/
|
|
status-badge.tsx ← herbruikbare status badge
|
|
priority-badge.tsx ← herbruikbare prioriteit badge
|
|
demo-button.tsx ← Button met demo-protection tooltip
|
|
story-log.tsx ← activiteitenlog entries
|
|
story-progress.tsx ← voortgangsindicator
|
|
priority-group.tsx ← prioriteitsgroep scheidingslijn
|
|
```
|
|
|
|
---
|
|
|
|
## Toegankelijkheid
|
|
|
|
- Alle kleurcombinaties voldoen aan **WCAG AA** (contrast ratio ≥ 4.5:1 voor normale tekst)
|
|
- Gebruik **altijd** tekst + kleur voor statusindicatoren, nooit kleur alleen
|
|
- Alle interactieve elementen hebben een zichtbare `focus:ring-primary`
|
|
- Dark mode is volledig ondersteund via de `.dark` class
|
|
|
|
---
|
|
|
|
*Bijlage bij CLAUDE.md — lees beide voor je begint met bouwen.*
|