odd-studio 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,356 @@
1
+ # ODD UI Component Guide
2
+ ## Selecting the Right Component for Every Outcome
3
+
4
+ This guide maps common user interface needs — as described in outcome walkthroughs — to the specific shadcn/ui components that serve them best. Use this when briefing the UI agent for any outcome.
5
+
6
+ ---
7
+
8
+ ## How to Use This Guide
9
+
10
+ When you have a UI outcome walkthrough, read each step and ask: "What does the persona *see* here, and what can they *do*?" Match the answer to a pattern below. The UI agent will implement the component — your job is to name the right one.
11
+
12
+ ---
13
+
14
+ ## Data Display Patterns
15
+
16
+ ### "She sees a list of [things]"
17
+ **Component: Table or Card Grid**
18
+
19
+ Use **Table** when:
20
+ - The persona needs to compare values across rows (e.g. all open bookings, all volunteers sorted by availability)
21
+ - There are 4+ columns of data
22
+ - The persona needs to sort or filter
23
+ - Example: "Rachel sees the twelve shifts sorted by fill status"
24
+
25
+ ```
26
+ shadcn: Table, TableHeader, TableRow, TableCell
27
+ Add: sortable columns via @tanstack/react-table
28
+ Tailwind: table-auto w-full
29
+ ```
30
+
31
+ Use **Card Grid** when:
32
+ - Each item has a visual identity (photo, icon, colour indicator)
33
+ - The persona is browsing, not comparing
34
+ - Items have a clear primary action (Book, Assign, Edit)
35
+ - Example: "Helen sees the upcoming events with cover images and remaining capacity"
36
+
37
+ ```
38
+ shadcn: Card, CardHeader, CardContent, CardFooter
39
+ Tailwind: grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4
40
+ ```
41
+
42
+ ---
43
+
44
+ ### "She sees a status indicator"
45
+ **Component: Badge**
46
+
47
+ Map domain statuses to Badge variants:
48
+ - **default** (neutral grey) — pending, draft, not started
49
+ - **secondary** (light) — in progress, assigned, scheduled
50
+ - **destructive** (red) — overdue, failed, blocked, urgent
51
+ - **outline** (bordered) — completed, verified, confirmed
52
+
53
+ ```
54
+ shadcn: Badge variant="destructive" | "default" | "secondary" | "outline"
55
+ ```
56
+
57
+ Never use raw colour classes for status — always use Badge so status is consistent everywhere.
58
+
59
+ ---
60
+
61
+ ### "She sees a summary at the top of the page"
62
+ **Component: Stat Cards**
63
+
64
+ For dashboards showing counts, totals, or key metrics:
65
+ ```
66
+ shadcn: Card with large number + label + optional trend indicator
67
+ Pattern: 3–4 stat cards in a responsive grid above the main table
68
+ Tailwind: grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6
69
+ ```
70
+
71
+ ---
72
+
73
+ ### "She sees the full record for one [thing]"
74
+ **Component: Detail Panel or Drawer**
75
+
76
+ Use **Drawer** when:
77
+ - The persona stays in context (doesn't leave the list view)
78
+ - The detail is a quick reference or quick action
79
+ - Example: "She clicks a booking and sees the full details without leaving the dashboard"
80
+
81
+ ```
82
+ shadcn: Sheet (right-side drawer)
83
+ Trigger: clicking a row in the Table
84
+ ```
85
+
86
+ Use **dedicated page** when:
87
+ - There's a complex workflow attached to the record
88
+ - The persona will spend significant time here
89
+ - Example: "She opens the event page and manages all its shifts"
90
+
91
+ ```
92
+ Next.js: app/[resource]/[id]/page.tsx
93
+ shadcn: Tabs to organise sections of a complex record
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Form Patterns
99
+
100
+ ### "She fills in [information]"
101
+ **Component: Form with react-hook-form + zod**
102
+
103
+ ```
104
+ shadcn: Form, FormField, FormItem, FormLabel, FormControl, FormMessage
105
+ Validation: zod schema matching the outcome's domain rules
106
+ ```
107
+
108
+ **Field type selection:**
109
+ | Domain need | Component |
110
+ |-------------|-----------|
111
+ | Short text (name, title) | Input |
112
+ | Long text (notes, description) | Textarea |
113
+ | Choose from fixed options (< 8) | Select or RadioGroup |
114
+ | Choose from fixed options (> 8, searchable) | Combobox |
115
+ | Yes/no toggle | Switch |
116
+ | Agree/acknowledge | Checkbox |
117
+ | Date selection | Calendar + Popover (shadcn DatePicker pattern) |
118
+ | File upload | custom Input type="file" with drag-drop zone |
119
+ | Multiple selections | Multi-select Combobox or CheckboxGroup |
120
+
121
+ **Always:**
122
+ - Every form field has a visible label (not just placeholder text)
123
+ - Error messages are field-specific ("Enter a valid email address" not "Invalid")
124
+ - Required fields are marked (asterisk + screen-reader text)
125
+ - Submit button is disabled while submitting (shows loading state)
126
+ - Success state is clear — either navigate away or show a success Toast
127
+
128
+ ---
129
+
130
+ ### "She submits and sees confirmation"
131
+ **Component: Toast (Sonner)**
132
+
133
+ ```
134
+ shadcn: Toaster + toast() from sonner
135
+ Success: toast.success("Booking confirmed — Marcus has been notified")
136
+ Error: toast.error("Could not save — Helen has been alerted")
137
+ ```
138
+
139
+ Toasts are for transient confirmations. Use a dedicated success screen or redirect for significant completions (payment confirmed, account created).
140
+
141
+ ---
142
+
143
+ ### "She fills in a multi-step process"
144
+ **Component: Step wizard with Progress indicator**
145
+
146
+ When a walkthrough has 3+ distinct stages:
147
+ ```
148
+ shadcn: no built-in wizard — use Steps pattern:
149
+ - State machine with current step index
150
+ - Progress bar: Progress component showing % complete
151
+ - Navigation: Back/Next buttons with validation before advancing
152
+ - Each step is a separate component
153
+ - State persists across steps (react-hook-form with persist)
154
+ Tailwind: divide the form into clearly named sections
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Navigation Patterns
160
+
161
+ ### "She moves between sections of the platform"
162
+ **Component: Sidebar navigation**
163
+
164
+ For authenticated apps with multiple sections:
165
+ ```
166
+ shadcn: no built-in sidebar — use AppSidebar pattern:
167
+ - Fixed left sidebar on desktop (w-64)
168
+ - Sheet (drawer) on mobile (hamburger trigger)
169
+ - Active route highlighted
170
+ - User profile at bottom
171
+ - Collapsed icon-only mode on medium screens
172
+ Tailwind: hidden md:flex for desktop, Sheet for mobile
173
+ ```
174
+
175
+ ---
176
+
177
+ ### "She moves between views of the same record"
178
+ **Component: Tabs**
179
+
180
+ ```
181
+ shadcn: Tabs, TabsList, TabsTrigger, TabsContent
182
+ Max 5 tabs — more than 5 means the record needs a different structure
183
+ Keep the active tab in the URL (searchParams) so refresh preserves position
184
+ ```
185
+
186
+ ---
187
+
188
+ ### "She returns to where she was"
189
+ **Component: Breadcrumb**
190
+
191
+ ```
192
+ shadcn: Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator
193
+ Show: Home > Section > Record name
194
+ Always include a link back to the list view
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Feedback & State Patterns
200
+
201
+ ### "She waits while the system processes"
202
+ **Component: Skeleton + Spinner**
203
+
204
+ Use **Skeleton** for initial page/data loading:
205
+ ```
206
+ shadcn: Skeleton
207
+ Match the shape of the content that will appear
208
+ Show 3–5 skeleton items in a list, not a spinner
209
+ Tailwind: animate-pulse on Skeleton
210
+ ```
211
+
212
+ Use **Spinner/Loading state** for button actions (submitting, saving):
213
+ ```
214
+ Button: disabled + loading state while action runs
215
+ shadcn: Button disabled with Loader2 icon (lucide-react) + "Saving..." text
216
+ Never remove the button — disable and show progress
217
+ ```
218
+
219
+ ---
220
+
221
+ ### "Nothing is here yet"
222
+ **Component: Empty State**
223
+
224
+ Every list view needs an empty state — what the persona sees when there's no data yet:
225
+ ```
226
+ Pattern: centred illustration/icon + heading + description + primary action button
227
+ Framer Motion: subtle fade-in (opacity 0 → 1, y 10 → 0, duration 0.3s)
228
+ Content: explain WHY it's empty and WHAT to do — not just "No items found"
229
+ Example: "No shifts assigned yet — click Add Shift to get started"
230
+ ```
231
+
232
+ ---
233
+
234
+ ### "Something went wrong"
235
+ **Component: Alert (inline) or Toast (transient)**
236
+
237
+ Use **Alert** for persistent errors that block the workflow:
238
+ ```
239
+ shadcn: Alert variant="destructive"
240
+ Include: what went wrong (domain language) + what to do next
241
+ ```
242
+
243
+ Use **Toast** for transient failures that don't block:
244
+ ```
245
+ toast.error("Could not send confirmation — check your email settings")
246
+ ```
247
+
248
+ Never show raw error messages to users. Translate every technical error into domain language before displaying it.
249
+
250
+ ---
251
+
252
+ ### "She needs to confirm a destructive action"
253
+ **Component: AlertDialog**
254
+
255
+ For irreversible actions (delete, cancel, archive):
256
+ ```
257
+ shadcn: AlertDialog, AlertDialogTrigger, AlertDialogContent,
258
+ AlertDialogTitle, AlertDialogDescription,
259
+ AlertDialogAction, AlertDialogCancel
260
+ Title: "Delete this booking?"
261
+ Description: Explain the consequence in domain terms
262
+ Action button: "destructive" variant ("Yes, delete booking")
263
+ Cancel: prominent — make it easy to back out
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Animation Patterns (Framer Motion)
269
+
270
+ ### When to animate
271
+
272
+ | Situation | Animation | Duration |
273
+ |-----------|-----------|----------|
274
+ | Page/route transition | Fade in (opacity 0→1) | 200ms |
275
+ | List item appears | Slide up + fade (y:10→0, opacity:0→1) | 150ms |
276
+ | Modal/drawer opens | Already handled by shadcn/Radix | |
277
+ | Success state | Scale pulse (scale:1→1.05→1) | 300ms |
278
+ | Empty state | Gentle fade in | 250ms |
279
+ | Error shake | Horizontal shake (x: 0,−8,8,−8,8,0) | 400ms |
280
+ | Loading→content | Fade out skeleton, fade in content | 150ms |
281
+
282
+ ### Always respect reduced motion
283
+ ```tsx
284
+ import { useReducedMotion } from "framer-motion"
285
+
286
+ const prefersReducedMotion = useReducedMotion()
287
+
288
+ const variants = {
289
+ initial: { opacity: 0, y: prefersReducedMotion ? 0 : 10 },
290
+ animate: { opacity: 1, y: 0 },
291
+ }
292
+ ```
293
+
294
+ ### What NOT to animate
295
+ - Navigation between pages on slow connections (delays feel like bugs)
296
+ - Form field focus states (use CSS :focus-visible instead)
297
+ - Data loading (use Skeleton, not animated spinners)
298
+ - Anything that moves continuously without user interaction
299
+
300
+ ---
301
+
302
+ ## Responsive Breakpoints (Tailwind)
303
+
304
+ ```
305
+ sm: 640px — most phones landscape
306
+ md: 768px — tablets, small laptops
307
+ lg: 1024px — standard laptops
308
+ xl: 1280px — desktop
309
+
310
+ Mobile-first: write base styles for 375px, use sm:/md:/lg: to add complexity
311
+ ```
312
+
313
+ **Common responsive patterns:**
314
+ ```
315
+ Navigation: hidden md:flex (sidebar) | flex md:hidden (mobile menu)
316
+ Grid: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
317
+ Text size: text-sm md:text-base
318
+ Padding: p-4 md:p-6 lg:p-8
319
+ Full-width CTA: w-full sm:w-auto (button fills screen on mobile)
320
+ ```
321
+
322
+ ---
323
+
324
+ ## The UI Outcome Brief Format
325
+
326
+ When handing a UI outcome to the UI agent, structure the brief as:
327
+
328
+ ```
329
+ Build Outcome [N]: [name] — UI layer
330
+
331
+ OUTCOME WALKTHROUGH: [paste full walkthrough]
332
+
333
+ COMPONENT SELECTIONS:
334
+ Screen 1 — [name]:
335
+ Layout: [dashboard / form / list+detail / wizard]
336
+ Components: [list from this guide]
337
+ Data from contracts: [which fields from which upstream outcome]
338
+ Mobile behaviour: [how this adapts at 375px]
339
+
340
+ Screen 2 — [name]:
341
+ [same structure]
342
+
343
+ VERIFICATION (UI-specific additions):
344
+ [ ] Verify at 375px — all content readable, no horizontal scroll
345
+ [ ] Keyboard tab order makes sense on every screen
346
+ [ ] Loading states visible for every async action
347
+ [ ] Empty states present for every list
348
+ [ ] Error states present for every form
349
+
350
+ UI STANDARDS:
351
+ - shadcn/ui components only (no custom component library)
352
+ - Tailwind classes only (no inline styles)
353
+ - Framer Motion for: [list the specific animations needed]
354
+ - WCAG 2.1 AA required
355
+ - Reduce motion respected via useReducedMotion()
356
+ ```