8.6 KiB
Frontend — Shared Components
Component Categories
Components fall into three tiers:
@lilith/ui-*packages — Design system primitives (Button, Card, Modal, etc.). Never redefined.- App-level shared components — LMS-specific components used across multiple pages (DomainBadge, EnergyBadge, etc.).
- Page-level components — Components scoped to a single page (MorningAssessmentCard, SprintBoard, etc.). Defined inside the page directory, not in
components/.
This doc covers tier 2 — app-level shared components.
Source Structure
src/components/
├── badges/
│ ├── DomainBadge.tsx
│ ├── EnergyBadge.tsx
│ ├── PriorityBadge.tsx
│ ├── StatusBadge.tsx
│ └── UrgencyIndicator.tsx
├── domain/
│ ├── DomainColorStripe.tsx
│ └── DomainIcon.tsx
├── habits/
│ └── StreakCounter.tsx
├── tasks/
│ ├── TaskListItem.tsx
│ └── TaskStatusToggle.tsx
├── scheduling/
│ ├── TimeBlockCard.tsx
│ └── BreakTimer.tsx
├── chat/
│ └── MiniChatWidget.tsx
├── voice/
│ ├── VoiceFAB.tsx
│ └── WaveformIndicator.tsx
└── animations/
└── CompletionAnimation.tsx
Badge Components
DomainBadge
Colored dot + domain name. Used everywhere a domain is referenced.
Props:
domain: { name: string, color: string, slug: string }
size?: 'sm' | 'md' # default 'sm'
linkable?: boolean # wraps in <Link> to /domains/:slug
Renders:
[●] Christine's Startup
^-- color from domain.color
EnergyBadge
Visual indicator for task energy level requirement.
Props:
level: 'high' | 'medium' | 'low'
Renders:
high → ⚡⚡⚡ (red-orange)
medium → ⚡⚡ (amber)
low → ⚡ (green)
PriorityBadge
Task priority indicator.
Props:
priority: 'critical' | 'high' | 'medium' | 'low'
Renders:
critical → [!!!] red background
high → [!!] orange background
medium → [!] neutral
low → [-] muted
StatusBadge
Task status as a colored chip.
Props:
status: 'backlog' | 'todo' | 'in_progress' | 'blocked' | 'done' | 'cancelled'
Color mapping:
backlog → grey
todo → blue
in_progress → amber
blocked → purple
done → green
cancelled → muted with strikethrough
UrgencyIndicator
Time-based urgency for tasks with due dates.
Props:
dueDate: string | null
status: string
Logic:
If status is done/cancelled → no indicator
If dueDate is null → no indicator
If overdue (dueDate < today) → red dot + "Overdue"
If due today → amber dot + "Today"
If due this week → no dot, subtle text
If isQuickWin → green dot + "Quick win"
Renders:
🔴 Overdue (red)
🟡 Today (amber)
🟢 Quick win (green)
Domain Components
DomainColorStripe
Horizontal color bar at the top of domain dashboards and cards.
Props:
color: string # hex color from domain
Renders:
4px tall colored bar, full width
DomainIcon
Renders the domain's icon from the icon identifier.
Props:
icon: string # icon name from domain entity
size?: number # pixel size, default 24
color?: string # override color
Maps icon names to @lilith/ui-icons:
briefcase, code, flask, home, camera, heart, film, dumbbell
Habit Components
StreakCounter
Fire icon with streak count, styled by milestone tier.
Props:
current: number
best: number
Visual tiers:
0 → grey, no flame
1-6 → small flame
7-13 → bronze flame
14-29 → silver flame
30-59 → gold flame
60-99 → platinum flame
100+ → special animated flame
Renders:
🔥 14 (with tooltip: "Best: 30")
Task Components
TaskListItem
Expandable task row used in task lists across all pages.
Props:
task: Task
onStatusChange: (status) => void
onExpand?: () => void
showDomain?: boolean # show DomainBadge (true in cross-domain views)
compact?: boolean # reduced info (for embedded lists)
Renders:
[checkbox] [urgency] Title — [domain?] [energy] [priority] [estimate] [→ expand]
└── Subtasks (if expanded):
[checkbox] Subtask 1
[checkbox] Subtask 2
Interactions:
Click checkbox → optimistic status toggle (todo ↔ done)
Click expand → show subtasks + description
Swipe right (mobile) → complete
TaskStatusToggle
Checkbox that animates between todo/done states.
Props:
status: string
onChange: (newStatus) => void
When toggled to 'done':
1. Checkbox fills with checkmark animation
2. CompletionAnimation fires
3. Row briefly highlights green
4. After 300ms, row fades to muted style
Scheduling Components
TimeBlockCard
A single time block in the timeline or calendar.
Props:
block: TimeBlock
domain?: Domain # for color
draggable?: boolean
onClick?: () => void
Renders:
┌─────────────────────────────┐
│ 09:00–10:30 │ ← times
│ ████████████████████████████ │ ← domain color bar
│ Christine standup │ ← title
│ work │ ← block type badge
└─────────────────────────────┘
When draggable=true:
Drag handle visible on hover
Uses @lilith/ui-dnd
BreakTimer
Persistent countdown in the navigation bar area.
Props:
(reads from BreakTimerContext)
States:
Not running → Show "Start timer" icon button
Running (work) → "🍅 18:32" countdown
Running (break) → "☕ 3:21" countdown
Paused → "⏸ 18:32" paused indicator
Click → Toggle start/pause
Long press → Reset
Right-click → Open config popover
Chat Components
MiniChatWidget
Collapsible chat panel, fixed bottom-right. Hidden on /chat page.
States:
Collapsed → Chat FAB icon with unread badge
Expanded → 400x500px floating panel
Expanded layout:
┌──────────────────────────┐
│ Quick Chat [×] │
├──────────────────────────┤
│ │
│ (ChatFeed, last 20 msgs)│
│ │
├──────────────────────────┤
│ [message input] [⏎] │
└──────────────────────────┘
Uses:
@lilith/ui-messaging ChatFeed + MessageComposer (compact mode)
@lilith/websocket-client useChat hook
Voice Components
VoiceFAB
Floating action button for voice mode. Uses @lilith/ui-fab.
States:
idle → Mic icon, neutral color
listening → Pulsing blue ring
processing → Spinning indicator overlay
speaking → Green animated waveform
Interactions:
Tap → Start/stop voice session
Activation → Play sound via @lilith/ui-effects-sound
Deactivation → Play deactivation sound
Position: Bottom-right, above MiniChatWidget
WaveformIndicator
Animated bars showing audio activity.
Props:
state: 'idle' | 'listening' | 'processing' | 'speaking'
size?: 'sm' | 'md' | 'lg'
Renders:
idle → —— (flat lines)
listening → ▁▃▅▃▁ (low amplitude pulsing, blue)
processing → ▂▂▂▂▂ (even bars, rotating, amber)
speaking → ▁▃▇▅▃▁ (high amplitude animation, green)
Animation Components
CompletionAnimation
Plays on task completion or habit check-in. Uses @lilith/ui-motion.
Props:
type: 'task' | 'habit' | 'goal' | 'all_done'
onComplete?: () => void
Animations:
task → Checkmark draws itself + subtle particle burst
habit → Flame grows briefly + streak number bumps
goal → Progress ring fills to 100% + shimmer
all_done → Confetti burst (all tasks done for the day)
Respects prefers-reduced-motion:
reduced → Instant state change, no animation
Design Tokens Used
All shared components reference theme tokens, not hardcoded colors:
// Colors pulled from theme
theme.colors.danger // overdue (red)
theme.colors.warning // due today (amber)
theme.colors.success // quick win, completed (green)
theme.colors.info // in progress, listening (blue)
theme.colors.muted // cancelled, inactive
theme.colors.accent // blocked (purple)
// Domain colors are dynamic, not from theme
domain.color // per-domain hex color