ux-toolkit 0.1.0 → 0.4.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.
Files changed (44) hide show
  1. package/README.md +113 -7
  2. package/agents/card-reviewer.md +173 -0
  3. package/agents/comparison-reviewer.md +143 -0
  4. package/agents/density-reviewer.md +207 -0
  5. package/agents/detail-page-reviewer.md +143 -0
  6. package/agents/editor-reviewer.md +165 -0
  7. package/agents/form-reviewer.md +156 -0
  8. package/agents/game-ui-reviewer.md +181 -0
  9. package/agents/list-page-reviewer.md +132 -0
  10. package/agents/navigation-reviewer.md +145 -0
  11. package/agents/panel-reviewer.md +182 -0
  12. package/agents/replay-reviewer.md +174 -0
  13. package/agents/settings-reviewer.md +166 -0
  14. package/agents/ux-auditor.md +145 -45
  15. package/agents/ux-engineer.md +211 -38
  16. package/dist/cli.js +172 -5
  17. package/dist/cli.js.map +1 -1
  18. package/dist/index.cjs +172 -5
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +128 -4
  21. package/dist/index.d.ts +128 -4
  22. package/dist/index.js +172 -5
  23. package/dist/index.js.map +1 -1
  24. package/package.json +6 -4
  25. package/skills/canvas-grid-patterns/SKILL.md +367 -0
  26. package/skills/comparison-patterns/SKILL.md +354 -0
  27. package/skills/data-density-patterns/SKILL.md +493 -0
  28. package/skills/detail-page-patterns/SKILL.md +522 -0
  29. package/skills/drag-drop-patterns/SKILL.md +406 -0
  30. package/skills/editor-workspace-patterns/SKILL.md +552 -0
  31. package/skills/event-timeline-patterns/SKILL.md +542 -0
  32. package/skills/form-patterns/SKILL.md +608 -0
  33. package/skills/info-card-patterns/SKILL.md +531 -0
  34. package/skills/keyboard-shortcuts-patterns/SKILL.md +365 -0
  35. package/skills/list-page-patterns/SKILL.md +351 -0
  36. package/skills/modal-patterns/SKILL.md +750 -0
  37. package/skills/navigation-patterns/SKILL.md +476 -0
  38. package/skills/page-structure-patterns/SKILL.md +271 -0
  39. package/skills/playback-replay-patterns/SKILL.md +695 -0
  40. package/skills/react-ux-patterns/SKILL.md +434 -0
  41. package/skills/split-panel-patterns/SKILL.md +609 -0
  42. package/skills/status-visualization-patterns/SKILL.md +635 -0
  43. package/skills/toast-notification-patterns/SKILL.md +207 -0
  44. package/skills/turn-based-ui-patterns/SKILL.md +506 -0
@@ -0,0 +1,531 @@
1
+ # Info Card UI Patterns
2
+
3
+ Patterns for card-based information displays including compact list cards, standard detail cards, and expanded views. Applies to entity displays, stat blocks, profile cards, and any summarized data presentation.
4
+
5
+ ## When to Use This Skill
6
+
7
+ - Entity/item cards (users, products, units)
8
+ - Stat blocks (RPG characters, game units)
9
+ - Profile/contact cards
10
+ - Product comparison cards
11
+ - Dashboard summary cards
12
+ - Search result cards
13
+ - Notification cards
14
+
15
+ ---
16
+
17
+ ## Core Concepts
18
+
19
+ ### Card Variants
20
+
21
+ | Variant | Height | Use Case | Information Density |
22
+ |---------|--------|----------|---------------------|
23
+ | **Compact** | 48-64px | List views, search results | Name + 2-3 key stats |
24
+ | **Standard** | 120-200px | Grid layouts, medium detail | Name + image + stats + actions |
25
+ | **Expanded** | 300px+ | Detail views, full info | Everything including secondary data |
26
+
27
+ ### Information Hierarchy
28
+
29
+ 1. **Primary**: Name/title, main identifier
30
+ 2. **Secondary**: Key stats, status badges
31
+ 3. **Tertiary**: Metadata, timestamps, secondary attributes
32
+ 4. **Actions**: Buttons, menus, links
33
+
34
+ ---
35
+
36
+ ## Audit Checklist
37
+
38
+ ### Compact Card (List Item)
39
+ - [ ] [CRITICAL] Primary identifier readable at glance
40
+ - [ ] [CRITICAL] Consistent height across all cards
41
+ - [ ] [MAJOR] Key stats visible without hover
42
+ - [ ] [MAJOR] Status indicator (badge/dot) if applicable
43
+ - [ ] [MAJOR] Click target is entire card
44
+ - [ ] [MINOR] Hover state indicates interactivity
45
+ - [ ] [MINOR] Truncation for long text with ellipsis
46
+
47
+ ### Standard Card
48
+ - [ ] [CRITICAL] Clear visual hierarchy (title > stats > metadata)
49
+ - [ ] [CRITICAL] Actions accessible without opening detail view
50
+ - [ ] [MAJOR] Image/avatar if applicable
51
+ - [ ] [MAJOR] Consistent card dimensions in grid
52
+ - [ ] [MAJOR] Status clearly indicated
53
+ - [ ] [MINOR] Secondary actions in overflow menu
54
+ - [ ] [MINOR] Loading skeleton matches card shape
55
+
56
+ ### Expanded Card / Detail View
57
+ - [ ] [CRITICAL] All relevant information accessible
58
+ - [ ] [MAJOR] Grouped by category/section
59
+ - [ ] [MAJOR] Collapsible sections for dense data
60
+ - [ ] [MAJOR] Print-friendly layout option
61
+ - [ ] [MINOR] Copy-to-clipboard for key values
62
+ - [ ] [MINOR] Share/export functionality
63
+
64
+ ### Information Display
65
+ - [ ] [CRITICAL] Labels clearly associated with values
66
+ - [ ] [CRITICAL] Units displayed for numeric values
67
+ - [ ] [MAJOR] Consistent alignment (labels left, values right)
68
+ - [ ] [MAJOR] Color coding for status/quality values
69
+ - [ ] [MINOR] Tooltips for abbreviated labels
70
+ - [ ] [MINOR] Relative values (e.g., "+10%") where meaningful
71
+
72
+ ### Status Badges
73
+ - [ ] [CRITICAL] Status immediately recognizable
74
+ - [ ] [CRITICAL] Color + text (not color alone)
75
+ - [ ] [MAJOR] Consistent badge styling across app
76
+ - [ ] [MAJOR] Badge size appropriate to context
77
+ - [ ] [MINOR] Badge tooltip with full status description
78
+
79
+ ### Responsive Behavior
80
+ - [ ] [CRITICAL] Cards readable on mobile
81
+ - [ ] [MAJOR] Grid adapts column count to viewport
82
+ - [ ] [MAJOR] Compact variant for constrained space
83
+ - [ ] [MINOR] Touch-friendly action targets (44px+)
84
+
85
+ ### Accessibility
86
+ - [ ] [CRITICAL] Card role and label for screen readers
87
+ - [ ] [CRITICAL] Interactive cards are focusable
88
+ - [ ] [MAJOR] Actions have accessible names
89
+ - [ ] [MAJOR] Color contrast meets WCAG AA
90
+ - [ ] [MINOR] Keyboard navigation between cards
91
+
92
+ ---
93
+
94
+ ## Implementation Patterns
95
+
96
+ ### Compact Card Component
97
+
98
+ ```tsx
99
+ interface CompactCardProps {
100
+ id: string;
101
+ name: string;
102
+ subtitle?: string;
103
+ stats: Array<{ label: string; value: string | number }>;
104
+ status?: { label: string; variant: BadgeVariant };
105
+ onClick?: () => void;
106
+ className?: string;
107
+ }
108
+
109
+ function CompactCard({
110
+ id,
111
+ name,
112
+ subtitle,
113
+ stats,
114
+ status,
115
+ onClick,
116
+ className = ''
117
+ }: CompactCardProps) {
118
+ return (
119
+ <div
120
+ onClick={onClick}
121
+ className={`
122
+ flex items-center justify-between gap-4 p-3
123
+ bg-surface-base rounded-lg border border-border-theme-subtle
124
+ hover:bg-surface-raised hover:border-border-theme
125
+ transition-colors cursor-pointer
126
+ ${className}
127
+ `}
128
+ role="article"
129
+ aria-labelledby={`card-${id}-title`}
130
+ tabIndex={0}
131
+ onKeyDown={e => e.key === 'Enter' && onClick?.()}
132
+ >
133
+ {/* Left: Identity */}
134
+ <div className="flex-1 min-w-0">
135
+ <div className="flex items-center gap-2">
136
+ <h3
137
+ id={`card-${id}-title`}
138
+ className="font-semibold text-text-theme-primary truncate"
139
+ >
140
+ {name}
141
+ </h3>
142
+ {status && (
143
+ <Badge variant={status.variant} size="sm">
144
+ {status.label}
145
+ </Badge>
146
+ )}
147
+ </div>
148
+ {subtitle && (
149
+ <p className="text-sm text-text-theme-muted truncate">
150
+ {subtitle}
151
+ </p>
152
+ )}
153
+ </div>
154
+
155
+ {/* Right: Key Stats */}
156
+ <div className="flex items-center gap-4 flex-shrink-0">
157
+ {stats.slice(0, 3).map((stat, idx) => (
158
+ <div key={idx} className="text-right">
159
+ <div className="text-xs text-text-theme-muted uppercase tracking-wide">
160
+ {stat.label}
161
+ </div>
162
+ <div className="font-mono font-medium text-text-theme-primary">
163
+ {stat.value}
164
+ </div>
165
+ </div>
166
+ ))}
167
+ </div>
168
+ </div>
169
+ );
170
+ }
171
+ ```
172
+
173
+ ### Standard Card Component
174
+
175
+ ```tsx
176
+ interface StandardCardProps {
177
+ id: string;
178
+ name: string;
179
+ image?: string;
180
+ description?: string;
181
+ stats: Array<{ label: string; value: string | number; highlight?: boolean }>;
182
+ badges?: Array<{ label: string; variant: BadgeVariant }>;
183
+ actions?: Array<{ label: string; icon?: React.ReactNode; onClick: () => void }>;
184
+ onClick?: () => void;
185
+ }
186
+
187
+ function StandardCard({
188
+ id,
189
+ name,
190
+ image,
191
+ description,
192
+ stats,
193
+ badges = [],
194
+ actions = [],
195
+ onClick,
196
+ }: StandardCardProps) {
197
+ return (
198
+ <div
199
+ className="bg-surface-base rounded-xl border border-border-theme-subtle overflow-hidden hover:shadow-lg transition-shadow"
200
+ role="article"
201
+ aria-labelledby={`card-${id}-title`}
202
+ >
203
+ {/* Header with Image */}
204
+ {image && (
205
+ <div className="aspect-video bg-surface-deep">
206
+ <img src={image} alt="" className="w-full h-full object-cover" />
207
+ </div>
208
+ )}
209
+
210
+ {/* Content */}
211
+ <div className="p-4">
212
+ {/* Title Row */}
213
+ <div className="flex items-start justify-between gap-2 mb-2">
214
+ <h3
215
+ id={`card-${id}-title`}
216
+ className="font-semibold text-lg text-text-theme-primary"
217
+ onClick={onClick}
218
+ style={{ cursor: onClick ? 'pointer' : 'default' }}
219
+ >
220
+ {name}
221
+ </h3>
222
+
223
+ {/* Badges */}
224
+ {badges.length > 0 && (
225
+ <div className="flex gap-1 flex-shrink-0">
226
+ {badges.map((badge, idx) => (
227
+ <Badge key={idx} variant={badge.variant} size="sm">
228
+ {badge.label}
229
+ </Badge>
230
+ ))}
231
+ </div>
232
+ )}
233
+ </div>
234
+
235
+ {/* Description */}
236
+ {description && (
237
+ <p className="text-sm text-text-theme-secondary mb-3 line-clamp-2">
238
+ {description}
239
+ </p>
240
+ )}
241
+
242
+ {/* Stats Grid */}
243
+ <div className="grid grid-cols-3 gap-3 mb-4">
244
+ {stats.slice(0, 6).map((stat, idx) => (
245
+ <div key={idx}>
246
+ <div className="text-xs text-text-theme-muted uppercase">
247
+ {stat.label}
248
+ </div>
249
+ <div className={`font-mono font-medium ${
250
+ stat.highlight ? 'text-accent' : 'text-text-theme-primary'
251
+ }`}>
252
+ {stat.value}
253
+ </div>
254
+ </div>
255
+ ))}
256
+ </div>
257
+
258
+ {/* Actions */}
259
+ {actions.length > 0 && (
260
+ <div className="flex gap-2 pt-3 border-t border-border-theme-subtle">
261
+ {actions.map((action, idx) => (
262
+ <Button
263
+ key={idx}
264
+ variant={idx === 0 ? 'primary' : 'ghost'}
265
+ size="sm"
266
+ onClick={action.onClick}
267
+ >
268
+ {action.icon}
269
+ {action.label}
270
+ </Button>
271
+ ))}
272
+ </div>
273
+ )}
274
+ </div>
275
+ </div>
276
+ );
277
+ }
278
+ ```
279
+
280
+ ### Stat Block Component
281
+
282
+ ```tsx
283
+ interface StatBlockProps {
284
+ stats: Array<{
285
+ label: string;
286
+ value: string | number;
287
+ max?: number;
288
+ color?: 'default' | 'success' | 'warning' | 'danger';
289
+ }>;
290
+ columns?: 2 | 3 | 4;
291
+ variant?: 'inline' | 'stacked';
292
+ }
293
+
294
+ function StatBlock({ stats, columns = 3, variant = 'stacked' }: StatBlockProps) {
295
+ const colorClasses = {
296
+ default: 'text-text-theme-primary',
297
+ success: 'text-emerald-400',
298
+ warning: 'text-amber-400',
299
+ danger: 'text-red-400',
300
+ };
301
+
302
+ return (
303
+ <dl className={`grid grid-cols-${columns} gap-4`}>
304
+ {stats.map((stat, idx) => (
305
+ <div
306
+ key={idx}
307
+ className={variant === 'inline' ? 'flex justify-between' : ''}
308
+ >
309
+ <dt className="text-xs text-text-theme-muted uppercase tracking-wide">
310
+ {stat.label}
311
+ </dt>
312
+ <dd className={`font-mono font-medium ${colorClasses[stat.color || 'default']}`}>
313
+ {stat.value}
314
+ {stat.max !== undefined && (
315
+ <span className="text-text-theme-muted">/{stat.max}</span>
316
+ )}
317
+ </dd>
318
+ </div>
319
+ ))}
320
+ </dl>
321
+ );
322
+ }
323
+ ```
324
+
325
+ ### Badge Component
326
+
327
+ ```tsx
328
+ type BadgeVariant = 'emerald' | 'amber' | 'rose' | 'cyan' | 'violet' | 'slate' | 'muted';
329
+ type BadgeSize = 'sm' | 'md' | 'lg';
330
+
331
+ interface BadgeProps {
332
+ children: React.ReactNode;
333
+ variant?: BadgeVariant;
334
+ size?: BadgeSize;
335
+ icon?: React.ReactNode;
336
+ }
337
+
338
+ function Badge({ children, variant = 'muted', size = 'md', icon }: BadgeProps) {
339
+ const variantClasses: Record<BadgeVariant, string> = {
340
+ emerald: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30',
341
+ amber: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
342
+ rose: 'bg-rose-500/20 text-rose-400 border-rose-500/30',
343
+ cyan: 'bg-cyan-500/20 text-cyan-400 border-cyan-500/30',
344
+ violet: 'bg-violet-500/20 text-violet-400 border-violet-500/30',
345
+ slate: 'bg-slate-500/20 text-slate-400 border-slate-500/30',
346
+ muted: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
347
+ };
348
+
349
+ const sizeClasses: Record<BadgeSize, string> = {
350
+ sm: 'px-1.5 py-0.5 text-xs',
351
+ md: 'px-2 py-1 text-sm',
352
+ lg: 'px-3 py-1.5 text-base',
353
+ };
354
+
355
+ return (
356
+ <span className={`
357
+ inline-flex items-center gap-1 rounded-full border font-medium
358
+ ${variantClasses[variant]}
359
+ ${sizeClasses[size]}
360
+ `}>
361
+ {icon}
362
+ {children}
363
+ </span>
364
+ );
365
+ }
366
+ ```
367
+
368
+ ### Card Grid Layout
369
+
370
+ ```tsx
371
+ interface CardGridProps {
372
+ children: React.ReactNode;
373
+ variant?: 'compact' | 'standard' | 'expanded';
374
+ }
375
+
376
+ function CardGrid({ children, variant = 'standard' }: CardGridProps) {
377
+ const gridClasses = {
378
+ compact: 'flex flex-col gap-2',
379
+ standard: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4',
380
+ expanded: 'flex flex-col gap-6',
381
+ };
382
+
383
+ return (
384
+ <div className={gridClasses[variant]}>
385
+ {children}
386
+ </div>
387
+ );
388
+ }
389
+ ```
390
+
391
+ ---
392
+
393
+ ## Visual Patterns
394
+
395
+ ### Status Badge Colors
396
+
397
+ ```typescript
398
+ const STATUS_BADGES = {
399
+ active: { label: 'Active', variant: 'emerald' as const },
400
+ inactive: { label: 'Inactive', variant: 'slate' as const },
401
+ pending: { label: 'Pending', variant: 'amber' as const },
402
+ error: { label: 'Error', variant: 'rose' as const },
403
+ premium: { label: 'Premium', variant: 'violet' as const },
404
+ new: { label: 'New', variant: 'cyan' as const },
405
+ };
406
+ ```
407
+
408
+ ### Value Coloring
409
+
410
+ ```typescript
411
+ function getValueColor(value: number, thresholds: { good: number; warning: number }) {
412
+ if (value >= thresholds.good) return 'text-emerald-400';
413
+ if (value >= thresholds.warning) return 'text-amber-400';
414
+ return 'text-red-400';
415
+ }
416
+
417
+ // Example usage for health percentage
418
+ const healthColor = getValueColor(healthPercent, { good: 75, warning: 25 });
419
+ ```
420
+
421
+ ---
422
+
423
+ ## Anti-Patterns
424
+
425
+ ### DON'T: Inconsistent Card Heights
426
+ ```tsx
427
+ // BAD - Cards of varying height in a row
428
+ <div className="flex gap-4">
429
+ <Card>Short content</Card>
430
+ <Card>Very long content that spans multiple lines...</Card>
431
+ </div>
432
+
433
+ // GOOD - Fixed or min-height for consistency
434
+ <div className="grid grid-cols-3 gap-4">
435
+ <Card className="min-h-[200px]">Short content</Card>
436
+ <Card className="min-h-[200px]">Long content...</Card>
437
+ </div>
438
+ ```
439
+
440
+ ### DON'T: Color-Only Status
441
+ ```tsx
442
+ // BAD - Status indicated only by color
443
+ <div className="w-3 h-3 rounded-full bg-green-500" />
444
+
445
+ // GOOD - Color + text
446
+ <Badge variant="emerald">Active</Badge>
447
+ ```
448
+
449
+ ### DON'T: Hidden Actions
450
+ ```tsx
451
+ // BAD - Actions only appear on hover (inaccessible on touch)
452
+ <Card onMouseEnter={() => setShowActions(true)}>
453
+ {showActions && <ActionButtons />}
454
+ </Card>
455
+
456
+ // GOOD - Actions always visible or in consistent location
457
+ <Card>
458
+ <ActionButtons />
459
+ </Card>
460
+ ```
461
+
462
+ ### DON'T: Unlabeled Stats
463
+ ```tsx
464
+ // BAD - Numbers without context
465
+ <div className="font-bold">42</div>
466
+
467
+ // GOOD - Label + value + unit
468
+ <div>
469
+ <span className="text-xs text-gray-500">Speed</span>
470
+ <span className="font-bold">42 mph</span>
471
+ </div>
472
+ ```
473
+
474
+ ---
475
+
476
+ ## Accessibility
477
+
478
+ ### Card Semantics
479
+
480
+ ```tsx
481
+ // Article for standalone content
482
+ <article aria-labelledby={titleId}>
483
+ <h3 id={titleId}>{title}</h3>
484
+ ...
485
+ </article>
486
+
487
+ // List item when in a list
488
+ <li role="article" aria-labelledby={titleId}>
489
+ ...
490
+ </li>
491
+ ```
492
+
493
+ ### Keyboard Navigation
494
+
495
+ ```tsx
496
+ // Make cards focusable and activatable
497
+ <div
498
+ role="article"
499
+ tabIndex={0}
500
+ onClick={handleClick}
501
+ onKeyDown={e => {
502
+ if (e.key === 'Enter' || e.key === ' ') {
503
+ e.preventDefault();
504
+ handleClick();
505
+ }
506
+ }}
507
+ aria-label={`${name}, ${status}. Press Enter for details.`}
508
+ >
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Testing Checklist
514
+
515
+ - [ ] Compact cards maintain consistent height
516
+ - [ ] Standard cards align in grid properly
517
+ - [ ] Long text truncates with ellipsis
518
+ - [ ] Badges display correct color for status
519
+ - [ ] Actions trigger correct callbacks
520
+ - [ ] Cards are keyboard navigable
521
+ - [ ] Screen reader announces card content
522
+ - [ ] Loading skeletons match card dimensions
523
+ - [ ] Responsive layout works at all breakpoints
524
+
525
+ ---
526
+
527
+ ## Related Skills
528
+
529
+ - `list-page-patterns` - For card list layouts
530
+ - `data-density-patterns` - For dense stat displays
531
+ - `status-visualization-patterns` - For health/progress indicators