design-protocol 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.
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/agents/dp-researcher.md +239 -0
- package/agents/dp-verifier.md +207 -0
- package/bin/install.js +464 -0
- package/commands/dp-back.md +221 -0
- package/commands/dp-discuss.md +257 -0
- package/commands/dp-execute.md +513 -0
- package/commands/dp-journey.md +85 -0
- package/commands/dp-progress.md +178 -0
- package/commands/dp-roadmap.md +83 -0
- package/commands/dp-skip.md +186 -0
- package/commands/dp-start.md +510 -0
- package/commands/dp-storytell.md +94 -0
- package/commands/dp-verify.md +207 -0
- package/package.json +59 -0
- package/skills/dp-color/SKILL.md +214 -0
- package/skills/dp-color/export_tokens.py +297 -0
- package/skills/dp-color/references/apca-contrast.md +87 -0
- package/skills/dp-color/references/hue-emotions.md +109 -0
- package/skills/dp-color/references/oklch-gamut.md +79 -0
- package/skills/dp-color/references/pitfalls.md +171 -0
- package/skills/dp-color/references/scale-patterns.md +206 -0
- package/skills/dp-color/references/tool-workflows.md +200 -0
- package/skills/dp-discovery/SKILL.md +480 -0
- package/skills/dp-eng_review/SKILL.md +471 -0
- package/skills/dp-eng_review/references/code-review-checklist.md +385 -0
- package/skills/dp-eng_review/references/react-patterns.md +512 -0
- package/skills/dp-eng_review/references/shadcn-patterns.md +510 -0
- package/skills/dp-eng_review/references/tailwind-conventions.md +351 -0
- package/skills/dp-journey/SKILL.md +682 -0
- package/skills/dp-journey/references/journey-types.md +97 -0
- package/skills/dp-journey/references/map-structures.md +177 -0
- package/skills/dp-journey/references/omnichannel-patterns.md +208 -0
- package/skills/dp-journey/references/research-methods.md +125 -0
- package/skills/dp-prd/SKILL.md +201 -0
- package/skills/dp-prd/references/claude-code-spec.md +107 -0
- package/skills/dp-prd/references/interview-questions.md +158 -0
- package/skills/dp-prd/references/section-templates.md +231 -0
- package/skills/dp-research/SKILL.md +540 -0
- package/skills/dp-research/references/facilitation-guide.md +291 -0
- package/skills/dp-research/references/interview-guide-template.md +190 -0
- package/skills/dp-research/references/method-selection.md +195 -0
- package/skills/dp-research/references/question-writing.md +244 -0
- package/skills/dp-research/references/research-report-template.md +363 -0
- package/skills/dp-research/references/synthesis-methods.md +289 -0
- package/skills/dp-research/references/usability-test-template.md +260 -0
- package/skills/dp-roadmap/SKILL.md +648 -0
- package/skills/dp-roadmap/references/prioritization-frameworks.md +312 -0
- package/skills/dp-roadmap/references/roadmap-structures.md +179 -0
- package/skills/dp-roadmap/references/roadmap-workshops.md +264 -0
- package/skills/dp-roadmap/references/theme-development.md +168 -0
- package/skills/dp-storytell/SKILL.md +645 -0
- package/skills/dp-storytell/references/audience-playbooks.md +260 -0
- package/skills/dp-storytell/references/content-type-templates.md +310 -0
- package/skills/dp-storytell/references/delivery-tactics.md +228 -0
- package/skills/dp-storytell/references/narrative-frameworks.md +259 -0
- package/skills/dp-ui/SKILL.md +503 -0
- package/skills/dp-ui/references/b2b-enterprise-patterns.md +319 -0
- package/skills/dp-ui/references/data-visualization.md +304 -0
- package/skills/dp-ui/references/visual-design-principles.md +237 -0
- package/skills/dp-ux/SKILL.md +414 -0
- package/skills/dp-ux/references/accessibility-checklist.md +128 -0
- package/skills/dp-ux/references/product-excellence.md +149 -0
- package/skills/dp-ux/references/usability-principles.md +140 -0
- package/skills/dp-ux/references/ux-patterns.md +221 -0
- package/templates/config.json +55 -0
- package/templates/context.md +96 -0
- package/templates/project.md +83 -0
- package/templates/requirements.md +137 -0
- package/templates/roadmap.md +168 -0
- package/templates/state.md +107 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# shadcn/ui Patterns
|
|
2
|
+
|
|
3
|
+
Correct usage patterns for shadcn/ui components in this project. Reference this when reviewing component implementations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Component Usage Philosophy
|
|
8
|
+
|
|
9
|
+
1. **Use shadcn components over primitives** — Don't write `<button>` when `<Button>` exists
|
|
10
|
+
2. **Leverage composition** — Most components are built to be composed
|
|
11
|
+
3. **Don't override internals** — Style via className, don't recreate
|
|
12
|
+
4. **Check the docs** — Each component has specific patterns
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Button Patterns
|
|
17
|
+
|
|
18
|
+
### Basic Usage
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { Button } from "@/components/dp:ui/button"
|
|
22
|
+
|
|
23
|
+
// Variants
|
|
24
|
+
<Button>Default</Button>
|
|
25
|
+
<Button variant="secondary">Secondary</Button>
|
|
26
|
+
<Button variant="destructive">Delete</Button>
|
|
27
|
+
<Button variant="outline">Outline</Button>
|
|
28
|
+
<Button variant="ghost">Ghost</Button>
|
|
29
|
+
<Button variant="link">Link</Button>
|
|
30
|
+
|
|
31
|
+
// Sizes
|
|
32
|
+
<Button size="sm">Small</Button>
|
|
33
|
+
<Button size="default">Default</Button>
|
|
34
|
+
<Button size="lg">Large</Button>
|
|
35
|
+
<Button size="icon">
|
|
36
|
+
<Plus className="h-4 w-4" />
|
|
37
|
+
</Button>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Button Loading State {#button-loading}
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// REQUIRED PATTERN for async buttons
|
|
44
|
+
interface SubmitButtonProps {
|
|
45
|
+
isLoading: boolean
|
|
46
|
+
children: React.ReactNode
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function SubmitButton({ isLoading, children }: SubmitButtonProps) {
|
|
50
|
+
return (
|
|
51
|
+
<Button disabled={isLoading}>
|
|
52
|
+
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
53
|
+
{children}
|
|
54
|
+
</Button>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Usage
|
|
59
|
+
<Button disabled={isSubmitting} onClick={handleSubmit}>
|
|
60
|
+
{isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
61
|
+
{isSubmitting ? 'Saving...' : 'Save'}
|
|
62
|
+
</Button>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Icon Buttons (Accessibility Required)
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// ALWAYS add aria-label for icon-only buttons
|
|
69
|
+
<Button size="icon" aria-label="Close dialog">
|
|
70
|
+
<X className="h-4 w-4" />
|
|
71
|
+
</Button>
|
|
72
|
+
|
|
73
|
+
// Or use sr-only text
|
|
74
|
+
<Button size="icon">
|
|
75
|
+
<X className="h-4 w-4" />
|
|
76
|
+
<span className="sr-only">Close dialog</span>
|
|
77
|
+
</Button>
|
|
78
|
+
|
|
79
|
+
// With tooltip (preferred for discoverability)
|
|
80
|
+
<TooltipProvider>
|
|
81
|
+
<Tooltip>
|
|
82
|
+
<TooltipTrigger asChild>
|
|
83
|
+
<Button size="icon" aria-label="Delete item">
|
|
84
|
+
<Trash2 className="h-4 w-4" />
|
|
85
|
+
</Button>
|
|
86
|
+
</TooltipTrigger>
|
|
87
|
+
<TooltipContent>Delete item</TooltipContent>
|
|
88
|
+
</Tooltip>
|
|
89
|
+
</TooltipProvider>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Button as Link
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// Use asChild to render as different element
|
|
96
|
+
import Link from "next/link"
|
|
97
|
+
|
|
98
|
+
<Button asChild>
|
|
99
|
+
<Link href="/dashboard">Go to Dashboard</Link>
|
|
100
|
+
</Button>
|
|
101
|
+
|
|
102
|
+
// NOT this (wrong semantics)
|
|
103
|
+
<Button onClick={() => router.push('/dashboard')}>Go to Dashboard</Button>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Form Patterns
|
|
109
|
+
|
|
110
|
+
### Input with Label (Required)
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { Input } from "@/components/dp:ui/input"
|
|
114
|
+
import { Label } from "@/components/dp:ui/label"
|
|
115
|
+
|
|
116
|
+
// ALWAYS pair inputs with labels
|
|
117
|
+
<div className="space-y-2">
|
|
118
|
+
<Label htmlFor="email">Email</Label>
|
|
119
|
+
<Input id="email" type="email" placeholder="name@example.com" />
|
|
120
|
+
</div>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Input with Error State
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<div className="space-y-2">
|
|
127
|
+
<Label htmlFor="email" className={error ? 'text-destructive' : ''}>
|
|
128
|
+
Email
|
|
129
|
+
</Label>
|
|
130
|
+
<Input
|
|
131
|
+
id="email"
|
|
132
|
+
type="email"
|
|
133
|
+
aria-invalid={!!error}
|
|
134
|
+
aria-describedby={error ? 'email-error' : undefined}
|
|
135
|
+
className={error ? 'border-destructive' : ''}
|
|
136
|
+
/>
|
|
137
|
+
{error && (
|
|
138
|
+
<p id="email-error" className="text-sm text-destructive">
|
|
139
|
+
{error}
|
|
140
|
+
</p>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Complete Form Field Pattern
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
interface FormFieldProps {
|
|
149
|
+
id: string
|
|
150
|
+
label: string
|
|
151
|
+
error?: string
|
|
152
|
+
hint?: string
|
|
153
|
+
required?: boolean
|
|
154
|
+
children: React.ReactNode
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function FormField({ id, label, error, hint, required, children }: FormFieldProps) {
|
|
158
|
+
return (
|
|
159
|
+
<div className="space-y-2">
|
|
160
|
+
<Label htmlFor={id} className={error ? 'text-destructive' : ''}>
|
|
161
|
+
{label}
|
|
162
|
+
{required && <span className="text-destructive ml-1">*</span>}
|
|
163
|
+
</Label>
|
|
164
|
+
{children}
|
|
165
|
+
{error ? (
|
|
166
|
+
<p id={`${id}-error`} className="text-sm text-destructive">
|
|
167
|
+
{error}
|
|
168
|
+
</p>
|
|
169
|
+
) : hint ? (
|
|
170
|
+
<p id={`${id}-hint`} className="text-sm text-muted-foreground">
|
|
171
|
+
{hint}
|
|
172
|
+
</p>
|
|
173
|
+
) : null}
|
|
174
|
+
</div>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Select Component
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import {
|
|
183
|
+
Select,
|
|
184
|
+
SelectContent,
|
|
185
|
+
SelectItem,
|
|
186
|
+
SelectTrigger,
|
|
187
|
+
SelectValue,
|
|
188
|
+
} from "@/components/dp:ui/select"
|
|
189
|
+
|
|
190
|
+
<div className="space-y-2">
|
|
191
|
+
<Label htmlFor="status">Status</Label>
|
|
192
|
+
<Select value={status} onValueChange={setStatus}>
|
|
193
|
+
<SelectTrigger id="status">
|
|
194
|
+
<SelectValue placeholder="Select status" />
|
|
195
|
+
</SelectTrigger>
|
|
196
|
+
<SelectContent>
|
|
197
|
+
<SelectItem value="active">Active</SelectItem>
|
|
198
|
+
<SelectItem value="inactive">Inactive</SelectItem>
|
|
199
|
+
<SelectItem value="pending">Pending</SelectItem>
|
|
200
|
+
</SelectContent>
|
|
201
|
+
</Select>
|
|
202
|
+
</div>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Dialog Patterns
|
|
208
|
+
|
|
209
|
+
### Basic Dialog
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import {
|
|
213
|
+
Dialog,
|
|
214
|
+
DialogContent,
|
|
215
|
+
DialogDescription,
|
|
216
|
+
DialogFooter,
|
|
217
|
+
DialogHeader,
|
|
218
|
+
DialogTitle,
|
|
219
|
+
DialogTrigger,
|
|
220
|
+
} from "@/components/dp:ui/dialog"
|
|
221
|
+
|
|
222
|
+
<Dialog>
|
|
223
|
+
<DialogTrigger asChild>
|
|
224
|
+
<Button>Open Dialog</Button>
|
|
225
|
+
</DialogTrigger>
|
|
226
|
+
<DialogContent>
|
|
227
|
+
<DialogHeader>
|
|
228
|
+
{/* DialogTitle is REQUIRED for accessibility */}
|
|
229
|
+
<DialogTitle>Edit Profile</DialogTitle>
|
|
230
|
+
{/* DialogDescription is REQUIRED for accessibility */}
|
|
231
|
+
<DialogDescription>
|
|
232
|
+
Make changes to your profile here. Click save when done.
|
|
233
|
+
</DialogDescription>
|
|
234
|
+
</DialogHeader>
|
|
235
|
+
<div className="py-4">
|
|
236
|
+
{/* Content */}
|
|
237
|
+
</div>
|
|
238
|
+
<DialogFooter>
|
|
239
|
+
<Button type="submit">Save changes</Button>
|
|
240
|
+
</DialogFooter>
|
|
241
|
+
</DialogContent>
|
|
242
|
+
</Dialog>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Controlled Dialog
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
function EditDialog({ open, onOpenChange }: DialogProps) {
|
|
249
|
+
return (
|
|
250
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
251
|
+
<DialogContent>
|
|
252
|
+
<DialogHeader>
|
|
253
|
+
<DialogTitle>Edit Item</DialogTitle>
|
|
254
|
+
<DialogDescription>Make changes to the item.</DialogDescription>
|
|
255
|
+
</DialogHeader>
|
|
256
|
+
{/* Content */}
|
|
257
|
+
</DialogContent>
|
|
258
|
+
</Dialog>
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Alert Dialog (Destructive Actions)
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import {
|
|
267
|
+
AlertDialog,
|
|
268
|
+
AlertDialogAction,
|
|
269
|
+
AlertDialogCancel,
|
|
270
|
+
AlertDialogContent,
|
|
271
|
+
AlertDialogDescription,
|
|
272
|
+
AlertDialogFooter,
|
|
273
|
+
AlertDialogHeader,
|
|
274
|
+
AlertDialogTitle,
|
|
275
|
+
AlertDialogTrigger,
|
|
276
|
+
} from "@/components/dp:ui/alert-dialog"
|
|
277
|
+
|
|
278
|
+
<AlertDialog>
|
|
279
|
+
<AlertDialogTrigger asChild>
|
|
280
|
+
<Button variant="destructive">Delete</Button>
|
|
281
|
+
</AlertDialogTrigger>
|
|
282
|
+
<AlertDialogContent>
|
|
283
|
+
<AlertDialogHeader>
|
|
284
|
+
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
|
285
|
+
<AlertDialogDescription>
|
|
286
|
+
This action cannot be undone. This will permanently delete the item.
|
|
287
|
+
</AlertDialogDescription>
|
|
288
|
+
</AlertDialogHeader>
|
|
289
|
+
<AlertDialogFooter>
|
|
290
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
291
|
+
<AlertDialogAction className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
|
|
292
|
+
Delete
|
|
293
|
+
</AlertDialogAction>
|
|
294
|
+
</AlertDialogFooter>
|
|
295
|
+
</AlertDialogContent>
|
|
296
|
+
</AlertDialog>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Table Patterns
|
|
302
|
+
|
|
303
|
+
### Basic Table
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
import {
|
|
307
|
+
Table,
|
|
308
|
+
TableBody,
|
|
309
|
+
TableCell,
|
|
310
|
+
TableHead,
|
|
311
|
+
TableHeader,
|
|
312
|
+
TableRow,
|
|
313
|
+
} from "@/components/dp:ui/table"
|
|
314
|
+
|
|
315
|
+
<Table>
|
|
316
|
+
<TableHeader>
|
|
317
|
+
<TableRow>
|
|
318
|
+
<TableHead>Name</TableHead>
|
|
319
|
+
<TableHead>Status</TableHead>
|
|
320
|
+
<TableHead className="text-right">Actions</TableHead>
|
|
321
|
+
</TableRow>
|
|
322
|
+
</TableHeader>
|
|
323
|
+
<TableBody>
|
|
324
|
+
{items.map((item) => (
|
|
325
|
+
<TableRow key={item.id}>
|
|
326
|
+
<TableCell className="font-medium">{item.name}</TableCell>
|
|
327
|
+
<TableCell>
|
|
328
|
+
<Badge variant={item.active ? 'default' : 'secondary'}>
|
|
329
|
+
{item.active ? 'Active' : 'Inactive'}
|
|
330
|
+
</Badge>
|
|
331
|
+
</TableCell>
|
|
332
|
+
<TableCell className="text-right">
|
|
333
|
+
<Button variant="ghost" size="icon" aria-label={`Edit ${item.name}`}>
|
|
334
|
+
<Pencil className="h-4 w-4" />
|
|
335
|
+
</Button>
|
|
336
|
+
</TableCell>
|
|
337
|
+
</TableRow>
|
|
338
|
+
))}
|
|
339
|
+
</TableBody>
|
|
340
|
+
</Table>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Empty Table State
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
<Table>
|
|
347
|
+
<TableHeader>
|
|
348
|
+
<TableRow>
|
|
349
|
+
<TableHead>Name</TableHead>
|
|
350
|
+
<TableHead>Status</TableHead>
|
|
351
|
+
</TableRow>
|
|
352
|
+
</TableHeader>
|
|
353
|
+
<TableBody>
|
|
354
|
+
{items.length === 0 ? (
|
|
355
|
+
<TableRow>
|
|
356
|
+
<TableCell colSpan={2} className="h-24 text-center">
|
|
357
|
+
<div className="flex flex-col items-center justify-center">
|
|
358
|
+
<Package className="h-8 w-8 text-muted-foreground mb-2" />
|
|
359
|
+
<p className="text-muted-foreground">No items found</p>
|
|
360
|
+
</div>
|
|
361
|
+
</TableCell>
|
|
362
|
+
</TableRow>
|
|
363
|
+
) : (
|
|
364
|
+
items.map((item) => (
|
|
365
|
+
<TableRow key={item.id}>
|
|
366
|
+
{/* ... */}
|
|
367
|
+
</TableRow>
|
|
368
|
+
))
|
|
369
|
+
)}
|
|
370
|
+
</TableBody>
|
|
371
|
+
</Table>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Card Patterns
|
|
377
|
+
|
|
378
|
+
### Basic Card
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
import {
|
|
382
|
+
Card,
|
|
383
|
+
CardContent,
|
|
384
|
+
CardDescription,
|
|
385
|
+
CardFooter,
|
|
386
|
+
CardHeader,
|
|
387
|
+
CardTitle,
|
|
388
|
+
} from "@/components/dp:ui/card"
|
|
389
|
+
|
|
390
|
+
<Card>
|
|
391
|
+
<CardHeader>
|
|
392
|
+
<CardTitle>Card Title</CardTitle>
|
|
393
|
+
<CardDescription>Card description goes here.</CardDescription>
|
|
394
|
+
</CardHeader>
|
|
395
|
+
<CardContent>
|
|
396
|
+
<p>Card content</p>
|
|
397
|
+
</CardContent>
|
|
398
|
+
<CardFooter>
|
|
399
|
+
<Button>Action</Button>
|
|
400
|
+
</CardFooter>
|
|
401
|
+
</Card>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Clickable Card
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
// Use proper semantic when card is clickable
|
|
408
|
+
<Card
|
|
409
|
+
className="cursor-pointer hover:bg-accent transition-colors"
|
|
410
|
+
role="button"
|
|
411
|
+
tabIndex={0}
|
|
412
|
+
onClick={handleClick}
|
|
413
|
+
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
|
|
414
|
+
>
|
|
415
|
+
{/* Content */}
|
|
416
|
+
</Card>
|
|
417
|
+
|
|
418
|
+
// Better: Use Link for navigation
|
|
419
|
+
<Link href={`/items/${item.id}`} className="block">
|
|
420
|
+
<Card className="hover:bg-accent transition-colors">
|
|
421
|
+
{/* Content */}
|
|
422
|
+
</Card>
|
|
423
|
+
</Link>
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Toast Patterns
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
import { toast } from "sonner"
|
|
432
|
+
|
|
433
|
+
// Success
|
|
434
|
+
toast.success("Item created successfully")
|
|
435
|
+
|
|
436
|
+
// Error
|
|
437
|
+
toast.error("Failed to create item")
|
|
438
|
+
|
|
439
|
+
// With description
|
|
440
|
+
toast.success("Item created", {
|
|
441
|
+
description: "Your item has been added to the list."
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
// With action
|
|
445
|
+
toast.error("Failed to save", {
|
|
446
|
+
action: {
|
|
447
|
+
label: "Retry",
|
|
448
|
+
onClick: () => handleRetry(),
|
|
449
|
+
},
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// Promise-based
|
|
453
|
+
toast.promise(saveItem(), {
|
|
454
|
+
loading: 'Saving...',
|
|
455
|
+
success: 'Item saved!',
|
|
456
|
+
error: 'Failed to save item',
|
|
457
|
+
})
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## Common Anti-Patterns
|
|
463
|
+
|
|
464
|
+
### Don't Recreate Components
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
// BAD: Custom button
|
|
468
|
+
<button className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-4 py-2">
|
|
469
|
+
Click me
|
|
470
|
+
</button>
|
|
471
|
+
|
|
472
|
+
// GOOD: Use the component
|
|
473
|
+
<Button>Click me</Button>
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Don't Skip Accessibility
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
// BAD: Missing required parts
|
|
480
|
+
<Dialog>
|
|
481
|
+
<DialogContent>
|
|
482
|
+
<h2>Title</h2> {/* Wrong! Use DialogTitle */}
|
|
483
|
+
Content here
|
|
484
|
+
</DialogContent>
|
|
485
|
+
</Dialog>
|
|
486
|
+
|
|
487
|
+
// GOOD: Proper structure
|
|
488
|
+
<Dialog>
|
|
489
|
+
<DialogContent>
|
|
490
|
+
<DialogHeader>
|
|
491
|
+
<DialogTitle>Title</DialogTitle>
|
|
492
|
+
<DialogDescription>Description</DialogDescription>
|
|
493
|
+
</DialogHeader>
|
|
494
|
+
Content here
|
|
495
|
+
</DialogContent>
|
|
496
|
+
</Dialog>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Don't Fight the Styling System
|
|
500
|
+
|
|
501
|
+
```tsx
|
|
502
|
+
// BAD: Inline styles overriding design system
|
|
503
|
+
<Button style={{ backgroundColor: '#ff0000' }}>Delete</Button>
|
|
504
|
+
|
|
505
|
+
// GOOD: Use variants
|
|
506
|
+
<Button variant="destructive">Delete</Button>
|
|
507
|
+
|
|
508
|
+
// GOOD: If custom color needed, use className
|
|
509
|
+
<Button className="bg-orange-500 hover:bg-orange-600">Custom</Button>
|
|
510
|
+
```
|