omgkit 2.2.0 → 2.3.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/package.json +1 -1
- package/plugin/skills/databases/mongodb/SKILL.md +60 -776
- package/plugin/skills/databases/prisma/SKILL.md +53 -744
- package/plugin/skills/databases/redis/SKILL.md +53 -860
- package/plugin/skills/devops/aws/SKILL.md +68 -672
- package/plugin/skills/devops/github-actions/SKILL.md +54 -657
- package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
- package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
- package/plugin/skills/frameworks/django/SKILL.md +87 -853
- package/plugin/skills/frameworks/express/SKILL.md +95 -1301
- package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
- package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
- package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
- package/plugin/skills/frameworks/react/SKILL.md +94 -962
- package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
- package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
- package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
- package/plugin/skills/frontend/responsive/SKILL.md +76 -799
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
- package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
- package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
- package/plugin/skills/languages/javascript/SKILL.md +106 -849
- package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
- package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
- package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
- package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
- package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
- package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
- package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
- package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
- package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
- package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
- package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
- package/plugin/skills/security/better-auth/SKILL.md +46 -1034
- package/plugin/skills/security/oauth/SKILL.md +80 -934
- package/plugin/skills/security/owasp/SKILL.md +78 -862
- package/plugin/skills/testing/playwright/SKILL.md +77 -700
- package/plugin/skills/testing/pytest/SKILL.md +73 -811
- package/plugin/skills/testing/vitest/SKILL.md +60 -920
- package/plugin/skills/tools/document-processing/SKILL.md +111 -838
- package/plugin/skills/tools/image-processing/SKILL.md +126 -659
- package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
- package/plugin/skills/tools/media-processing/SKILL.md +118 -735
- package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
- package/plugin/skills/SKILL_STANDARDS.md +0 -743
|
@@ -1,442 +1,83 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: shadcn
|
|
3
|
-
description: shadcn/ui
|
|
4
|
-
category: frontend
|
|
5
|
-
triggers:
|
|
6
|
-
- shadcn
|
|
7
|
-
- shadcn/ui
|
|
8
|
-
- ui components
|
|
9
|
-
- radix ui
|
|
10
|
-
- react components
|
|
2
|
+
name: building-with-shadcn
|
|
3
|
+
description: Claude builds accessible React UIs using shadcn/ui components with Radix primitives and React Hook Form integration. Use when creating forms, dialogs, or composable UI systems.
|
|
11
4
|
---
|
|
12
5
|
|
|
13
|
-
# shadcn/ui
|
|
6
|
+
# Building with shadcn/ui
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Purpose
|
|
18
|
-
|
|
19
|
-
Build beautiful, accessible React applications:
|
|
20
|
-
|
|
21
|
-
- Install and configure components
|
|
22
|
-
- Customize themes and variants
|
|
23
|
-
- Integrate with React Hook Form
|
|
24
|
-
- Build complex UI patterns
|
|
25
|
-
- Extend component functionality
|
|
26
|
-
- Maintain accessibility standards
|
|
27
|
-
- Create consistent design systems
|
|
28
|
-
|
|
29
|
-
## Features
|
|
30
|
-
|
|
31
|
-
### 1. Installation and Setup
|
|
8
|
+
## Quick Start
|
|
32
9
|
|
|
33
10
|
```bash
|
|
34
|
-
# Initialize
|
|
11
|
+
# Initialize and add components
|
|
35
12
|
npx shadcn-ui@latest init
|
|
36
|
-
|
|
37
|
-
# Add individual components
|
|
38
|
-
npx shadcn-ui@latest add button
|
|
39
|
-
npx shadcn-ui@latest add card
|
|
40
|
-
npx shadcn-ui@latest add form
|
|
41
|
-
npx shadcn-ui@latest add input
|
|
42
|
-
npx shadcn-ui@latest add dialog
|
|
43
|
-
npx shadcn-ui@latest add dropdown-menu
|
|
44
|
-
npx shadcn-ui@latest add table
|
|
45
|
-
npx shadcn-ui@latest add toast
|
|
46
|
-
|
|
47
|
-
# Add multiple components at once
|
|
48
|
-
npx shadcn-ui@latest add button card input form
|
|
13
|
+
npx shadcn-ui@latest add button card form input dialog
|
|
49
14
|
```
|
|
50
15
|
|
|
51
|
-
```json
|
|
52
|
-
// components.json
|
|
53
|
-
{
|
|
54
|
-
"$schema": "https://ui.shadcn.com/schema.json",
|
|
55
|
-
"style": "default",
|
|
56
|
-
"rsc": true,
|
|
57
|
-
"tsx": true,
|
|
58
|
-
"tailwind": {
|
|
59
|
-
"config": "tailwind.config.ts",
|
|
60
|
-
"css": "app/globals.css",
|
|
61
|
-
"baseColor": "slate",
|
|
62
|
-
"cssVariables": true,
|
|
63
|
-
"prefix": ""
|
|
64
|
-
},
|
|
65
|
-
"aliases": {
|
|
66
|
-
"components": "@/components",
|
|
67
|
-
"utils": "@/lib/utils"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// lib/utils.ts
|
|
74
|
-
import { type ClassValue, clsx } from "clsx";
|
|
75
|
-
import { twMerge } from "tailwind-merge";
|
|
76
|
-
|
|
77
|
-
export function cn(...inputs: ClassValue[]) {
|
|
78
|
-
return twMerge(clsx(inputs));
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### 2. Core Components
|
|
83
|
-
|
|
84
16
|
```tsx
|
|
85
|
-
// Button usage with variants
|
|
86
17
|
import { Button } from "@/components/ui/button";
|
|
18
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
87
19
|
|
|
88
|
-
export function
|
|
89
|
-
return (
|
|
90
|
-
<div className="flex flex-wrap gap-4">
|
|
91
|
-
<Button>Default</Button>
|
|
92
|
-
<Button variant="secondary">Secondary</Button>
|
|
93
|
-
<Button variant="destructive">Destructive</Button>
|
|
94
|
-
<Button variant="outline">Outline</Button>
|
|
95
|
-
<Button variant="ghost">Ghost</Button>
|
|
96
|
-
<Button variant="link">Link</Button>
|
|
97
|
-
|
|
98
|
-
{/* Sizes */}
|
|
99
|
-
<Button size="sm">Small</Button>
|
|
100
|
-
<Button size="default">Default</Button>
|
|
101
|
-
<Button size="lg">Large</Button>
|
|
102
|
-
<Button size="icon">
|
|
103
|
-
<PlusIcon className="h-4 w-4" />
|
|
104
|
-
</Button>
|
|
105
|
-
|
|
106
|
-
{/* With loading state */}
|
|
107
|
-
<Button disabled>
|
|
108
|
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
109
|
-
Please wait
|
|
110
|
-
</Button>
|
|
111
|
-
|
|
112
|
-
{/* As child (for links) */}
|
|
113
|
-
<Button asChild>
|
|
114
|
-
<a href="/dashboard">Go to Dashboard</a>
|
|
115
|
-
</Button>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Card component
|
|
121
|
-
import {
|
|
122
|
-
Card,
|
|
123
|
-
CardContent,
|
|
124
|
-
CardDescription,
|
|
125
|
-
CardFooter,
|
|
126
|
-
CardHeader,
|
|
127
|
-
CardTitle,
|
|
128
|
-
} from "@/components/ui/card";
|
|
129
|
-
|
|
130
|
-
export function CardExample() {
|
|
20
|
+
export function Example() {
|
|
131
21
|
return (
|
|
132
|
-
<Card
|
|
22
|
+
<Card>
|
|
133
23
|
<CardHeader>
|
|
134
|
-
<CardTitle>
|
|
135
|
-
<CardDescription>Deploy your new project in one-click.</CardDescription>
|
|
24
|
+
<CardTitle>Welcome</CardTitle>
|
|
136
25
|
</CardHeader>
|
|
137
|
-
<CardContent>
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
<div className="flex flex-col space-y-1.5">
|
|
141
|
-
<Label htmlFor="name">Name</Label>
|
|
142
|
-
<Input id="name" placeholder="Name of your project" />
|
|
143
|
-
</div>
|
|
144
|
-
<div className="flex flex-col space-y-1.5">
|
|
145
|
-
<Label htmlFor="framework">Framework</Label>
|
|
146
|
-
<Select>
|
|
147
|
-
<SelectTrigger id="framework">
|
|
148
|
-
<SelectValue placeholder="Select" />
|
|
149
|
-
</SelectTrigger>
|
|
150
|
-
<SelectContent position="popper">
|
|
151
|
-
<SelectItem value="next">Next.js</SelectItem>
|
|
152
|
-
<SelectItem value="sveltekit">SvelteKit</SelectItem>
|
|
153
|
-
<SelectItem value="astro">Astro</SelectItem>
|
|
154
|
-
<SelectItem value="nuxt">Nuxt.js</SelectItem>
|
|
155
|
-
</SelectContent>
|
|
156
|
-
</Select>
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
</form>
|
|
26
|
+
<CardContent className="flex gap-4">
|
|
27
|
+
<Button>Primary</Button>
|
|
28
|
+
<Button variant="outline">Outline</Button>
|
|
160
29
|
</CardContent>
|
|
161
|
-
<CardFooter className="flex justify-between">
|
|
162
|
-
<Button variant="outline">Cancel</Button>
|
|
163
|
-
<Button>Deploy</Button>
|
|
164
|
-
</CardFooter>
|
|
165
30
|
</Card>
|
|
166
31
|
);
|
|
167
32
|
}
|
|
168
|
-
|
|
169
|
-
// Input component
|
|
170
|
-
import { Input } from "@/components/ui/input";
|
|
171
|
-
import { Label } from "@/components/ui/label";
|
|
172
|
-
|
|
173
|
-
export function InputWithLabel() {
|
|
174
|
-
return (
|
|
175
|
-
<div className="grid w-full max-w-sm items-center gap-1.5">
|
|
176
|
-
<Label htmlFor="email">Email</Label>
|
|
177
|
-
<Input type="email" id="email" placeholder="Email" />
|
|
178
|
-
</div>
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function InputWithButton() {
|
|
183
|
-
return (
|
|
184
|
-
<div className="flex w-full max-w-sm items-center space-x-2">
|
|
185
|
-
<Input type="email" placeholder="Email" />
|
|
186
|
-
<Button type="submit">Subscribe</Button>
|
|
187
|
-
</div>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
33
|
```
|
|
191
34
|
|
|
192
|
-
|
|
35
|
+
## Features
|
|
193
36
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
37
|
+
| Feature | Description | Guide |
|
|
38
|
+
|---------|-------------|-------|
|
|
39
|
+
| Button Variants | default, secondary, destructive, outline, ghost, link | `ref/button.md` |
|
|
40
|
+
| Form Integration | React Hook Form + Zod validation pattern | `ref/forms.md` |
|
|
41
|
+
| Dialog/Sheet | Modal dialogs and slide-out panels | `ref/dialogs.md` |
|
|
42
|
+
| Data Display | Table, Tabs, Accordion components | `ref/data-display.md` |
|
|
43
|
+
| Navigation | DropdownMenu, Command palette, NavigationMenu | `ref/navigation.md` |
|
|
44
|
+
| Feedback | Toast notifications with useToast hook | `ref/toast.md` |
|
|
197
45
|
|
|
198
|
-
|
|
199
|
-
import { useForm } from "react-hook-form";
|
|
200
|
-
import * as z from "zod";
|
|
46
|
+
## Common Patterns
|
|
201
47
|
|
|
202
|
-
|
|
203
|
-
import {
|
|
204
|
-
Form,
|
|
205
|
-
FormControl,
|
|
206
|
-
FormDescription,
|
|
207
|
-
FormField,
|
|
208
|
-
FormItem,
|
|
209
|
-
FormLabel,
|
|
210
|
-
FormMessage,
|
|
211
|
-
} from "@/components/ui/form";
|
|
212
|
-
import { Input } from "@/components/ui/input";
|
|
213
|
-
import {
|
|
214
|
-
Select,
|
|
215
|
-
SelectContent,
|
|
216
|
-
SelectItem,
|
|
217
|
-
SelectTrigger,
|
|
218
|
-
SelectValue,
|
|
219
|
-
} from "@/components/ui/select";
|
|
220
|
-
import { Textarea } from "@/components/ui/textarea";
|
|
221
|
-
import { Checkbox } from "@/components/ui/checkbox";
|
|
222
|
-
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
223
|
-
import { Switch } from "@/components/ui/switch";
|
|
224
|
-
import { toast } from "@/components/ui/use-toast";
|
|
48
|
+
### Form with Validation
|
|
225
49
|
|
|
50
|
+
```tsx
|
|
226
51
|
const formSchema = z.object({
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
.min(2, "Username must be at least 2 characters.")
|
|
230
|
-
.max(30, "Username must not exceed 30 characters."),
|
|
231
|
-
email: z.string().email("Please enter a valid email address."),
|
|
232
|
-
bio: z
|
|
233
|
-
.string()
|
|
234
|
-
.max(160, "Bio must not exceed 160 characters.")
|
|
235
|
-
.optional(),
|
|
236
|
-
role: z.enum(["admin", "user", "guest"], {
|
|
237
|
-
required_error: "Please select a role.",
|
|
238
|
-
}),
|
|
239
|
-
notifications: z.boolean().default(false),
|
|
240
|
-
marketingEmails: z.boolean().default(false),
|
|
241
|
-
securityEmails: z.boolean().default(true),
|
|
242
|
-
communicationMethod: z.enum(["email", "sms", "push"], {
|
|
243
|
-
required_error: "Please select a communication method.",
|
|
244
|
-
}),
|
|
52
|
+
email: z.string().email("Invalid email"),
|
|
53
|
+
name: z.string().min(2, "Name must be at least 2 characters"),
|
|
245
54
|
});
|
|
246
55
|
|
|
247
|
-
type FormData = z.infer<typeof formSchema>;
|
|
248
|
-
|
|
249
56
|
export function ProfileForm() {
|
|
250
|
-
const form = useForm<
|
|
57
|
+
const form = useForm<z.infer<typeof formSchema>>({
|
|
251
58
|
resolver: zodResolver(formSchema),
|
|
252
|
-
defaultValues: {
|
|
253
|
-
username: "",
|
|
254
|
-
email: "",
|
|
255
|
-
bio: "",
|
|
256
|
-
notifications: false,
|
|
257
|
-
marketingEmails: false,
|
|
258
|
-
securityEmails: true,
|
|
259
|
-
},
|
|
59
|
+
defaultValues: { email: "", name: "" },
|
|
260
60
|
});
|
|
261
61
|
|
|
262
|
-
async function onSubmit(data: FormData) {
|
|
263
|
-
try {
|
|
264
|
-
// API call here
|
|
265
|
-
console.log(data);
|
|
266
|
-
toast({
|
|
267
|
-
title: "Profile updated",
|
|
268
|
-
description: "Your profile has been updated successfully.",
|
|
269
|
-
});
|
|
270
|
-
} catch (error) {
|
|
271
|
-
toast({
|
|
272
|
-
title: "Error",
|
|
273
|
-
description: "Something went wrong. Please try again.",
|
|
274
|
-
variant: "destructive",
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
62
|
return (
|
|
280
63
|
<Form {...form}>
|
|
281
|
-
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-
|
|
282
|
-
<FormField
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
)}
|
|
297
|
-
/>
|
|
298
|
-
|
|
299
|
-
<FormField
|
|
300
|
-
control={form.control}
|
|
301
|
-
name="email"
|
|
302
|
-
render={({ field }) => (
|
|
303
|
-
<FormItem>
|
|
304
|
-
<FormLabel>Email</FormLabel>
|
|
305
|
-
<FormControl>
|
|
306
|
-
<Input type="email" placeholder="john@example.com" {...field} />
|
|
307
|
-
</FormControl>
|
|
308
|
-
<FormMessage />
|
|
309
|
-
</FormItem>
|
|
310
|
-
)}
|
|
311
|
-
/>
|
|
312
|
-
|
|
313
|
-
<FormField
|
|
314
|
-
control={form.control}
|
|
315
|
-
name="bio"
|
|
316
|
-
render={({ field }) => (
|
|
317
|
-
<FormItem>
|
|
318
|
-
<FormLabel>Bio</FormLabel>
|
|
319
|
-
<FormControl>
|
|
320
|
-
<Textarea
|
|
321
|
-
placeholder="Tell us a little about yourself"
|
|
322
|
-
className="resize-none"
|
|
323
|
-
{...field}
|
|
324
|
-
/>
|
|
325
|
-
</FormControl>
|
|
326
|
-
<FormDescription>
|
|
327
|
-
You can @mention other users and organizations.
|
|
328
|
-
</FormDescription>
|
|
329
|
-
<FormMessage />
|
|
330
|
-
</FormItem>
|
|
331
|
-
)}
|
|
332
|
-
/>
|
|
333
|
-
|
|
334
|
-
<FormField
|
|
335
|
-
control={form.control}
|
|
336
|
-
name="role"
|
|
337
|
-
render={({ field }) => (
|
|
338
|
-
<FormItem>
|
|
339
|
-
<FormLabel>Role</FormLabel>
|
|
340
|
-
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
341
|
-
<FormControl>
|
|
342
|
-
<SelectTrigger>
|
|
343
|
-
<SelectValue placeholder="Select a role" />
|
|
344
|
-
</SelectTrigger>
|
|
345
|
-
</FormControl>
|
|
346
|
-
<SelectContent>
|
|
347
|
-
<SelectItem value="admin">Admin</SelectItem>
|
|
348
|
-
<SelectItem value="user">User</SelectItem>
|
|
349
|
-
<SelectItem value="guest">Guest</SelectItem>
|
|
350
|
-
</SelectContent>
|
|
351
|
-
</Select>
|
|
352
|
-
<FormMessage />
|
|
353
|
-
</FormItem>
|
|
354
|
-
)}
|
|
355
|
-
/>
|
|
356
|
-
|
|
357
|
-
<FormField
|
|
358
|
-
control={form.control}
|
|
359
|
-
name="communicationMethod"
|
|
360
|
-
render={({ field }) => (
|
|
361
|
-
<FormItem className="space-y-3">
|
|
362
|
-
<FormLabel>Notify me via</FormLabel>
|
|
363
|
-
<FormControl>
|
|
364
|
-
<RadioGroup
|
|
365
|
-
onValueChange={field.onChange}
|
|
366
|
-
defaultValue={field.value}
|
|
367
|
-
className="flex flex-col space-y-1"
|
|
368
|
-
>
|
|
369
|
-
<FormItem className="flex items-center space-x-3 space-y-0">
|
|
370
|
-
<FormControl>
|
|
371
|
-
<RadioGroupItem value="email" />
|
|
372
|
-
</FormControl>
|
|
373
|
-
<FormLabel className="font-normal">Email</FormLabel>
|
|
374
|
-
</FormItem>
|
|
375
|
-
<FormItem className="flex items-center space-x-3 space-y-0">
|
|
376
|
-
<FormControl>
|
|
377
|
-
<RadioGroupItem value="sms" />
|
|
378
|
-
</FormControl>
|
|
379
|
-
<FormLabel className="font-normal">SMS</FormLabel>
|
|
380
|
-
</FormItem>
|
|
381
|
-
<FormItem className="flex items-center space-x-3 space-y-0">
|
|
382
|
-
<FormControl>
|
|
383
|
-
<RadioGroupItem value="push" />
|
|
384
|
-
</FormControl>
|
|
385
|
-
<FormLabel className="font-normal">Push notification</FormLabel>
|
|
386
|
-
</FormItem>
|
|
387
|
-
</RadioGroup>
|
|
388
|
-
</FormControl>
|
|
389
|
-
<FormMessage />
|
|
390
|
-
</FormItem>
|
|
391
|
-
)}
|
|
392
|
-
/>
|
|
393
|
-
|
|
394
|
-
<div className="space-y-4">
|
|
395
|
-
<FormField
|
|
396
|
-
control={form.control}
|
|
397
|
-
name="notifications"
|
|
398
|
-
render={({ field }) => (
|
|
399
|
-
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
400
|
-
<div className="space-y-0.5">
|
|
401
|
-
<FormLabel className="text-base">Push Notifications</FormLabel>
|
|
402
|
-
<FormDescription>
|
|
403
|
-
Receive push notifications on your device.
|
|
404
|
-
</FormDescription>
|
|
405
|
-
</div>
|
|
406
|
-
<FormControl>
|
|
407
|
-
<Switch
|
|
408
|
-
checked={field.value}
|
|
409
|
-
onCheckedChange={field.onChange}
|
|
410
|
-
/>
|
|
411
|
-
</FormControl>
|
|
412
|
-
</FormItem>
|
|
413
|
-
)}
|
|
414
|
-
/>
|
|
415
|
-
|
|
416
|
-
<FormField
|
|
417
|
-
control={form.control}
|
|
418
|
-
name="marketingEmails"
|
|
419
|
-
render={({ field }) => (
|
|
420
|
-
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
|
421
|
-
<FormControl>
|
|
422
|
-
<Checkbox
|
|
423
|
-
checked={field.value}
|
|
424
|
-
onCheckedChange={field.onChange}
|
|
425
|
-
/>
|
|
426
|
-
</FormControl>
|
|
427
|
-
<div className="space-y-1 leading-none">
|
|
428
|
-
<FormLabel>Marketing emails</FormLabel>
|
|
429
|
-
<FormDescription>
|
|
430
|
-
Receive emails about new products and features.
|
|
431
|
-
</FormDescription>
|
|
432
|
-
</div>
|
|
433
|
-
</FormItem>
|
|
434
|
-
)}
|
|
435
|
-
/>
|
|
436
|
-
</div>
|
|
437
|
-
|
|
64
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
65
|
+
<FormField control={form.control} name="email" render={({ field }) => (
|
|
66
|
+
<FormItem>
|
|
67
|
+
<FormLabel>Email</FormLabel>
|
|
68
|
+
<FormControl><Input {...field} /></FormControl>
|
|
69
|
+
<FormMessage />
|
|
70
|
+
</FormItem>
|
|
71
|
+
)} />
|
|
72
|
+
<FormField control={form.control} name="name" render={({ field }) => (
|
|
73
|
+
<FormItem>
|
|
74
|
+
<FormLabel>Name</FormLabel>
|
|
75
|
+
<FormControl><Input {...field} /></FormControl>
|
|
76
|
+
<FormMessage />
|
|
77
|
+
</FormItem>
|
|
78
|
+
)} />
|
|
438
79
|
<Button type="submit" disabled={form.formState.isSubmitting}>
|
|
439
|
-
{form.formState.isSubmitting ? "Saving..." : "Save
|
|
80
|
+
{form.formState.isSubmitting ? "Saving..." : "Save"}
|
|
440
81
|
</Button>
|
|
441
82
|
</form>
|
|
442
83
|
</Form>
|
|
@@ -444,553 +85,64 @@ export function ProfileForm() {
|
|
|
444
85
|
}
|
|
445
86
|
```
|
|
446
87
|
|
|
447
|
-
###
|
|
88
|
+
### Dialog with Form
|
|
448
89
|
|
|
449
90
|
```tsx
|
|
450
|
-
|
|
451
|
-
import {
|
|
452
|
-
Dialog,
|
|
453
|
-
DialogContent,
|
|
454
|
-
DialogDescription,
|
|
455
|
-
DialogFooter,
|
|
456
|
-
DialogHeader,
|
|
457
|
-
DialogTitle,
|
|
458
|
-
DialogTrigger,
|
|
459
|
-
DialogClose,
|
|
460
|
-
} from "@/components/ui/dialog";
|
|
461
|
-
|
|
462
|
-
export function DialogExample() {
|
|
91
|
+
export function EditDialog({ onSave }: { onSave: (data: Data) => void }) {
|
|
463
92
|
return (
|
|
464
93
|
<Dialog>
|
|
465
94
|
<DialogTrigger asChild>
|
|
466
95
|
<Button variant="outline">Edit Profile</Button>
|
|
467
96
|
</DialogTrigger>
|
|
468
|
-
<DialogContent
|
|
97
|
+
<DialogContent>
|
|
469
98
|
<DialogHeader>
|
|
470
|
-
<DialogTitle>Edit
|
|
471
|
-
<DialogDescription>
|
|
472
|
-
Make changes to your profile here. Click save when you're done.
|
|
473
|
-
</DialogDescription>
|
|
99
|
+
<DialogTitle>Edit Profile</DialogTitle>
|
|
100
|
+
<DialogDescription>Update your profile information.</DialogDescription>
|
|
474
101
|
</DialogHeader>
|
|
475
102
|
<div className="grid gap-4 py-4">
|
|
476
103
|
<div className="grid grid-cols-4 items-center gap-4">
|
|
477
|
-
<Label htmlFor="name" className="text-right">
|
|
478
|
-
|
|
479
|
-
</Label>
|
|
480
|
-
<Input
|
|
481
|
-
id="name"
|
|
482
|
-
defaultValue="Pedro Duarte"
|
|
483
|
-
className="col-span-3"
|
|
484
|
-
/>
|
|
485
|
-
</div>
|
|
486
|
-
<div className="grid grid-cols-4 items-center gap-4">
|
|
487
|
-
<Label htmlFor="username" className="text-right">
|
|
488
|
-
Username
|
|
489
|
-
</Label>
|
|
490
|
-
<Input
|
|
491
|
-
id="username"
|
|
492
|
-
defaultValue="@peduarte"
|
|
493
|
-
className="col-span-3"
|
|
494
|
-
/>
|
|
104
|
+
<Label htmlFor="name" className="text-right">Name</Label>
|
|
105
|
+
<Input id="name" className="col-span-3" />
|
|
495
106
|
</div>
|
|
496
107
|
</div>
|
|
497
108
|
<DialogFooter>
|
|
498
|
-
<DialogClose asChild>
|
|
499
|
-
|
|
500
|
-
</DialogClose>
|
|
501
|
-
<Button type="submit">Save changes</Button>
|
|
109
|
+
<DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose>
|
|
110
|
+
<Button onClick={() => onSave(data)}>Save</Button>
|
|
502
111
|
</DialogFooter>
|
|
503
112
|
</DialogContent>
|
|
504
113
|
</Dialog>
|
|
505
114
|
);
|
|
506
115
|
}
|
|
507
|
-
|
|
508
|
-
// Alert Dialog for confirmations
|
|
509
|
-
import {
|
|
510
|
-
AlertDialog,
|
|
511
|
-
AlertDialogAction,
|
|
512
|
-
AlertDialogCancel,
|
|
513
|
-
AlertDialogContent,
|
|
514
|
-
AlertDialogDescription,
|
|
515
|
-
AlertDialogFooter,
|
|
516
|
-
AlertDialogHeader,
|
|
517
|
-
AlertDialogTitle,
|
|
518
|
-
AlertDialogTrigger,
|
|
519
|
-
} from "@/components/ui/alert-dialog";
|
|
520
|
-
|
|
521
|
-
export function DeleteConfirmation({ onDelete }: { onDelete: () => void }) {
|
|
522
|
-
return (
|
|
523
|
-
<AlertDialog>
|
|
524
|
-
<AlertDialogTrigger asChild>
|
|
525
|
-
<Button variant="destructive">Delete</Button>
|
|
526
|
-
</AlertDialogTrigger>
|
|
527
|
-
<AlertDialogContent>
|
|
528
|
-
<AlertDialogHeader>
|
|
529
|
-
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
|
530
|
-
<AlertDialogDescription>
|
|
531
|
-
This action cannot be undone. This will permanently delete your
|
|
532
|
-
account and remove your data from our servers.
|
|
533
|
-
</AlertDialogDescription>
|
|
534
|
-
</AlertDialogHeader>
|
|
535
|
-
<AlertDialogFooter>
|
|
536
|
-
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
537
|
-
<AlertDialogAction onClick={onDelete}>
|
|
538
|
-
Yes, delete account
|
|
539
|
-
</AlertDialogAction>
|
|
540
|
-
</AlertDialogFooter>
|
|
541
|
-
</AlertDialogContent>
|
|
542
|
-
</AlertDialog>
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Sheet (slide-out panel)
|
|
547
|
-
import {
|
|
548
|
-
Sheet,
|
|
549
|
-
SheetClose,
|
|
550
|
-
SheetContent,
|
|
551
|
-
SheetDescription,
|
|
552
|
-
SheetFooter,
|
|
553
|
-
SheetHeader,
|
|
554
|
-
SheetTitle,
|
|
555
|
-
SheetTrigger,
|
|
556
|
-
} from "@/components/ui/sheet";
|
|
557
|
-
|
|
558
|
-
export function SheetExample() {
|
|
559
|
-
return (
|
|
560
|
-
<Sheet>
|
|
561
|
-
<SheetTrigger asChild>
|
|
562
|
-
<Button variant="outline">Open Settings</Button>
|
|
563
|
-
</SheetTrigger>
|
|
564
|
-
<SheetContent>
|
|
565
|
-
<SheetHeader>
|
|
566
|
-
<SheetTitle>Edit settings</SheetTitle>
|
|
567
|
-
<SheetDescription>
|
|
568
|
-
Make changes to your settings here.
|
|
569
|
-
</SheetDescription>
|
|
570
|
-
</SheetHeader>
|
|
571
|
-
<div className="grid gap-4 py-4">
|
|
572
|
-
{/* Form fields */}
|
|
573
|
-
</div>
|
|
574
|
-
<SheetFooter>
|
|
575
|
-
<SheetClose asChild>
|
|
576
|
-
<Button type="submit">Save changes</Button>
|
|
577
|
-
</SheetClose>
|
|
578
|
-
</SheetFooter>
|
|
579
|
-
</SheetContent>
|
|
580
|
-
</Sheet>
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
### 5. Data Display Components
|
|
586
|
-
|
|
587
|
-
```tsx
|
|
588
|
-
// Table component
|
|
589
|
-
import {
|
|
590
|
-
Table,
|
|
591
|
-
TableBody,
|
|
592
|
-
TableCaption,
|
|
593
|
-
TableCell,
|
|
594
|
-
TableHead,
|
|
595
|
-
TableHeader,
|
|
596
|
-
TableRow,
|
|
597
|
-
} from "@/components/ui/table";
|
|
598
|
-
|
|
599
|
-
interface Invoice {
|
|
600
|
-
id: string;
|
|
601
|
-
paymentStatus: string;
|
|
602
|
-
totalAmount: number;
|
|
603
|
-
paymentMethod: string;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
export function DataTable({ invoices }: { invoices: Invoice[] }) {
|
|
607
|
-
return (
|
|
608
|
-
<Table>
|
|
609
|
-
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
610
|
-
<TableHeader>
|
|
611
|
-
<TableRow>
|
|
612
|
-
<TableHead className="w-[100px]">Invoice</TableHead>
|
|
613
|
-
<TableHead>Status</TableHead>
|
|
614
|
-
<TableHead>Method</TableHead>
|
|
615
|
-
<TableHead className="text-right">Amount</TableHead>
|
|
616
|
-
</TableRow>
|
|
617
|
-
</TableHeader>
|
|
618
|
-
<TableBody>
|
|
619
|
-
{invoices.map((invoice) => (
|
|
620
|
-
<TableRow key={invoice.id}>
|
|
621
|
-
<TableCell className="font-medium">{invoice.id}</TableCell>
|
|
622
|
-
<TableCell>
|
|
623
|
-
<Badge
|
|
624
|
-
variant={
|
|
625
|
-
invoice.paymentStatus === "paid" ? "default" : "secondary"
|
|
626
|
-
}
|
|
627
|
-
>
|
|
628
|
-
{invoice.paymentStatus}
|
|
629
|
-
</Badge>
|
|
630
|
-
</TableCell>
|
|
631
|
-
<TableCell>{invoice.paymentMethod}</TableCell>
|
|
632
|
-
<TableCell className="text-right">
|
|
633
|
-
${invoice.totalAmount.toFixed(2)}
|
|
634
|
-
</TableCell>
|
|
635
|
-
</TableRow>
|
|
636
|
-
))}
|
|
637
|
-
</TableBody>
|
|
638
|
-
</Table>
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Tabs component
|
|
643
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
644
|
-
|
|
645
|
-
export function TabsExample() {
|
|
646
|
-
return (
|
|
647
|
-
<Tabs defaultValue="account" className="w-[400px]">
|
|
648
|
-
<TabsList className="grid w-full grid-cols-2">
|
|
649
|
-
<TabsTrigger value="account">Account</TabsTrigger>
|
|
650
|
-
<TabsTrigger value="password">Password</TabsTrigger>
|
|
651
|
-
</TabsList>
|
|
652
|
-
<TabsContent value="account">
|
|
653
|
-
<Card>
|
|
654
|
-
<CardHeader>
|
|
655
|
-
<CardTitle>Account</CardTitle>
|
|
656
|
-
<CardDescription>
|
|
657
|
-
Make changes to your account here.
|
|
658
|
-
</CardDescription>
|
|
659
|
-
</CardHeader>
|
|
660
|
-
<CardContent className="space-y-2">
|
|
661
|
-
<div className="space-y-1">
|
|
662
|
-
<Label htmlFor="name">Name</Label>
|
|
663
|
-
<Input id="name" defaultValue="Pedro Duarte" />
|
|
664
|
-
</div>
|
|
665
|
-
<div className="space-y-1">
|
|
666
|
-
<Label htmlFor="username">Username</Label>
|
|
667
|
-
<Input id="username" defaultValue="@peduarte" />
|
|
668
|
-
</div>
|
|
669
|
-
</CardContent>
|
|
670
|
-
<CardFooter>
|
|
671
|
-
<Button>Save changes</Button>
|
|
672
|
-
</CardFooter>
|
|
673
|
-
</Card>
|
|
674
|
-
</TabsContent>
|
|
675
|
-
<TabsContent value="password">
|
|
676
|
-
<Card>
|
|
677
|
-
<CardHeader>
|
|
678
|
-
<CardTitle>Password</CardTitle>
|
|
679
|
-
<CardDescription>Change your password here.</CardDescription>
|
|
680
|
-
</CardHeader>
|
|
681
|
-
<CardContent className="space-y-2">
|
|
682
|
-
<div className="space-y-1">
|
|
683
|
-
<Label htmlFor="current">Current password</Label>
|
|
684
|
-
<Input id="current" type="password" />
|
|
685
|
-
</div>
|
|
686
|
-
<div className="space-y-1">
|
|
687
|
-
<Label htmlFor="new">New password</Label>
|
|
688
|
-
<Input id="new" type="password" />
|
|
689
|
-
</div>
|
|
690
|
-
</CardContent>
|
|
691
|
-
<CardFooter>
|
|
692
|
-
<Button>Save password</Button>
|
|
693
|
-
</CardFooter>
|
|
694
|
-
</Card>
|
|
695
|
-
</TabsContent>
|
|
696
|
-
</Tabs>
|
|
697
|
-
);
|
|
698
|
-
}
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
### 6. Navigation Components
|
|
702
|
-
|
|
703
|
-
```tsx
|
|
704
|
-
// Dropdown Menu
|
|
705
|
-
import {
|
|
706
|
-
DropdownMenu,
|
|
707
|
-
DropdownMenuContent,
|
|
708
|
-
DropdownMenuItem,
|
|
709
|
-
DropdownMenuLabel,
|
|
710
|
-
DropdownMenuSeparator,
|
|
711
|
-
DropdownMenuShortcut,
|
|
712
|
-
DropdownMenuTrigger,
|
|
713
|
-
DropdownMenuSub,
|
|
714
|
-
DropdownMenuSubContent,
|
|
715
|
-
DropdownMenuSubTrigger,
|
|
716
|
-
} from "@/components/ui/dropdown-menu";
|
|
717
|
-
|
|
718
|
-
export function UserMenu() {
|
|
719
|
-
return (
|
|
720
|
-
<DropdownMenu>
|
|
721
|
-
<DropdownMenuTrigger asChild>
|
|
722
|
-
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
|
723
|
-
<Avatar className="h-8 w-8">
|
|
724
|
-
<AvatarImage src="/avatars/01.png" alt="@username" />
|
|
725
|
-
<AvatarFallback>JD</AvatarFallback>
|
|
726
|
-
</Avatar>
|
|
727
|
-
</Button>
|
|
728
|
-
</DropdownMenuTrigger>
|
|
729
|
-
<DropdownMenuContent className="w-56" align="end" forceMount>
|
|
730
|
-
<DropdownMenuLabel className="font-normal">
|
|
731
|
-
<div className="flex flex-col space-y-1">
|
|
732
|
-
<p className="text-sm font-medium leading-none">John Doe</p>
|
|
733
|
-
<p className="text-xs leading-none text-muted-foreground">
|
|
734
|
-
john@example.com
|
|
735
|
-
</p>
|
|
736
|
-
</div>
|
|
737
|
-
</DropdownMenuLabel>
|
|
738
|
-
<DropdownMenuSeparator />
|
|
739
|
-
<DropdownMenuItem>
|
|
740
|
-
<User className="mr-2 h-4 w-4" />
|
|
741
|
-
Profile
|
|
742
|
-
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
|
743
|
-
</DropdownMenuItem>
|
|
744
|
-
<DropdownMenuItem>
|
|
745
|
-
<Settings className="mr-2 h-4 w-4" />
|
|
746
|
-
Settings
|
|
747
|
-
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
|
748
|
-
</DropdownMenuItem>
|
|
749
|
-
<DropdownMenuSeparator />
|
|
750
|
-
<DropdownMenuItem className="text-red-600">
|
|
751
|
-
<LogOut className="mr-2 h-4 w-4" />
|
|
752
|
-
Log out
|
|
753
|
-
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
|
754
|
-
</DropdownMenuItem>
|
|
755
|
-
</DropdownMenuContent>
|
|
756
|
-
</DropdownMenu>
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// Command Menu (CMD+K)
|
|
761
|
-
import {
|
|
762
|
-
CommandDialog,
|
|
763
|
-
CommandEmpty,
|
|
764
|
-
CommandGroup,
|
|
765
|
-
CommandInput,
|
|
766
|
-
CommandItem,
|
|
767
|
-
CommandList,
|
|
768
|
-
CommandSeparator,
|
|
769
|
-
CommandShortcut,
|
|
770
|
-
} from "@/components/ui/command";
|
|
771
|
-
|
|
772
|
-
export function CommandMenu() {
|
|
773
|
-
const [open, setOpen] = useState(false);
|
|
774
|
-
|
|
775
|
-
useEffect(() => {
|
|
776
|
-
const down = (e: KeyboardEvent) => {
|
|
777
|
-
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
778
|
-
e.preventDefault();
|
|
779
|
-
setOpen((open) => !open);
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
document.addEventListener("keydown", down);
|
|
784
|
-
return () => document.removeEventListener("keydown", down);
|
|
785
|
-
}, []);
|
|
786
|
-
|
|
787
|
-
return (
|
|
788
|
-
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
789
|
-
<CommandInput placeholder="Type a command or search..." />
|
|
790
|
-
<CommandList>
|
|
791
|
-
<CommandEmpty>No results found.</CommandEmpty>
|
|
792
|
-
<CommandGroup heading="Suggestions">
|
|
793
|
-
<CommandItem>
|
|
794
|
-
<Calendar className="mr-2 h-4 w-4" />
|
|
795
|
-
Calendar
|
|
796
|
-
</CommandItem>
|
|
797
|
-
<CommandItem>
|
|
798
|
-
<Search className="mr-2 h-4 w-4" />
|
|
799
|
-
Search
|
|
800
|
-
</CommandItem>
|
|
801
|
-
</CommandGroup>
|
|
802
|
-
<CommandSeparator />
|
|
803
|
-
<CommandGroup heading="Settings">
|
|
804
|
-
<CommandItem>
|
|
805
|
-
<User className="mr-2 h-4 w-4" />
|
|
806
|
-
Profile
|
|
807
|
-
<CommandShortcut>⌘P</CommandShortcut>
|
|
808
|
-
</CommandItem>
|
|
809
|
-
<CommandItem>
|
|
810
|
-
<Settings className="mr-2 h-4 w-4" />
|
|
811
|
-
Settings
|
|
812
|
-
<CommandShortcut>⌘S</CommandShortcut>
|
|
813
|
-
</CommandItem>
|
|
814
|
-
</CommandGroup>
|
|
815
|
-
</CommandList>
|
|
816
|
-
</CommandDialog>
|
|
817
|
-
);
|
|
818
|
-
}
|
|
819
116
|
```
|
|
820
117
|
|
|
821
|
-
###
|
|
118
|
+
### Toast Notifications
|
|
822
119
|
|
|
823
120
|
```tsx
|
|
824
|
-
// Toast setup and usage
|
|
825
|
-
// components/ui/toaster.tsx
|
|
826
|
-
"use client";
|
|
827
|
-
|
|
828
|
-
import {
|
|
829
|
-
Toast,
|
|
830
|
-
ToastClose,
|
|
831
|
-
ToastDescription,
|
|
832
|
-
ToastProvider,
|
|
833
|
-
ToastTitle,
|
|
834
|
-
ToastViewport,
|
|
835
|
-
} from "@/components/ui/toast";
|
|
836
121
|
import { useToast } from "@/components/ui/use-toast";
|
|
837
122
|
|
|
838
|
-
export function
|
|
839
|
-
const { toasts } = useToast();
|
|
840
|
-
|
|
841
|
-
return (
|
|
842
|
-
<ToastProvider>
|
|
843
|
-
{toasts.map(function ({ id, title, description, action, ...props }) {
|
|
844
|
-
return (
|
|
845
|
-
<Toast key={id} {...props}>
|
|
846
|
-
<div className="grid gap-1">
|
|
847
|
-
{title && <ToastTitle>{title}</ToastTitle>}
|
|
848
|
-
{description && (
|
|
849
|
-
<ToastDescription>{description}</ToastDescription>
|
|
850
|
-
)}
|
|
851
|
-
</div>
|
|
852
|
-
{action}
|
|
853
|
-
<ToastClose />
|
|
854
|
-
</Toast>
|
|
855
|
-
);
|
|
856
|
-
})}
|
|
857
|
-
<ToastViewport />
|
|
858
|
-
</ToastProvider>
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
// Usage in components
|
|
863
|
-
import { useToast } from "@/components/ui/use-toast";
|
|
864
|
-
import { ToastAction } from "@/components/ui/toast";
|
|
865
|
-
|
|
866
|
-
export function ToastDemo() {
|
|
123
|
+
export function SaveButton() {
|
|
867
124
|
const { toast } = useToast();
|
|
868
125
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}}
|
|
878
|
-
>
|
|
879
|
-
Show Toast
|
|
880
|
-
</Button>
|
|
881
|
-
|
|
882
|
-
<Button
|
|
883
|
-
variant="destructive"
|
|
884
|
-
onClick={() => {
|
|
885
|
-
toast({
|
|
886
|
-
variant: "destructive",
|
|
887
|
-
title: "Uh oh! Something went wrong.",
|
|
888
|
-
description: "There was a problem with your request.",
|
|
889
|
-
action: <ToastAction altText="Try again">Try again</ToastAction>,
|
|
890
|
-
});
|
|
891
|
-
}}
|
|
892
|
-
>
|
|
893
|
-
Show Error Toast
|
|
894
|
-
</Button>
|
|
895
|
-
</div>
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
## Use Cases
|
|
901
|
-
|
|
902
|
-
### Complete Dashboard Page
|
|
903
|
-
|
|
904
|
-
```tsx
|
|
905
|
-
// app/dashboard/page.tsx
|
|
906
|
-
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
907
|
-
import { DataTable } from "@/components/data-table";
|
|
908
|
-
import { UserMenu } from "@/components/user-menu";
|
|
909
|
-
|
|
910
|
-
export default function DashboardPage() {
|
|
911
|
-
return (
|
|
912
|
-
<div className="flex min-h-screen flex-col">
|
|
913
|
-
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
914
|
-
<div className="container flex h-14 items-center">
|
|
915
|
-
<div className="mr-4 flex">
|
|
916
|
-
<a className="mr-6 flex items-center space-x-2" href="/">
|
|
917
|
-
<span className="font-bold">Dashboard</span>
|
|
918
|
-
</a>
|
|
919
|
-
</div>
|
|
920
|
-
<div className="flex flex-1 items-center justify-end space-x-2">
|
|
921
|
-
<UserMenu />
|
|
922
|
-
</div>
|
|
923
|
-
</div>
|
|
924
|
-
</header>
|
|
925
|
-
|
|
926
|
-
<main className="flex-1 space-y-4 p-8 pt-6">
|
|
927
|
-
<div className="flex items-center justify-between space-y-2">
|
|
928
|
-
<h2 className="text-3xl font-bold tracking-tight">Dashboard</h2>
|
|
929
|
-
<Button>Download Report</Button>
|
|
930
|
-
</div>
|
|
931
|
-
|
|
932
|
-
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
933
|
-
<Card>
|
|
934
|
-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
935
|
-
<CardTitle className="text-sm font-medium">Total Revenue</CardTitle>
|
|
936
|
-
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
|
937
|
-
</CardHeader>
|
|
938
|
-
<CardContent>
|
|
939
|
-
<div className="text-2xl font-bold">$45,231.89</div>
|
|
940
|
-
<p className="text-xs text-muted-foreground">
|
|
941
|
-
+20.1% from last month
|
|
942
|
-
</p>
|
|
943
|
-
</CardContent>
|
|
944
|
-
</Card>
|
|
945
|
-
{/* More cards... */}
|
|
946
|
-
</div>
|
|
126
|
+
const handleSave = async () => {
|
|
127
|
+
try {
|
|
128
|
+
await saveData();
|
|
129
|
+
toast({ title: "Success", description: "Changes saved." });
|
|
130
|
+
} catch {
|
|
131
|
+
toast({ variant: "destructive", title: "Error", description: "Failed to save." });
|
|
132
|
+
}
|
|
133
|
+
};
|
|
947
134
|
|
|
948
|
-
|
|
949
|
-
<CardHeader>
|
|
950
|
-
<CardTitle>Recent Orders</CardTitle>
|
|
951
|
-
</CardHeader>
|
|
952
|
-
<CardContent>
|
|
953
|
-
<DataTable />
|
|
954
|
-
</CardContent>
|
|
955
|
-
</Card>
|
|
956
|
-
</main>
|
|
957
|
-
</div>
|
|
958
|
-
);
|
|
135
|
+
return <Button onClick={handleSave}>Save</Button>;
|
|
959
136
|
}
|
|
960
137
|
```
|
|
961
138
|
|
|
962
139
|
## Best Practices
|
|
963
140
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
- Keep components accessible
|
|
973
|
-
- Use TypeScript for type safety
|
|
974
|
-
- Follow React Hook Form patterns
|
|
975
|
-
- Test component accessibility
|
|
976
|
-
|
|
977
|
-
### Don'ts
|
|
978
|
-
|
|
979
|
-
- Don't modify generated component files directly
|
|
980
|
-
- Don't skip form validation
|
|
981
|
-
- Don't ignore accessibility features
|
|
982
|
-
- Don't override styles without reason
|
|
983
|
-
- Don't use inline styles
|
|
984
|
-
- Don't skip loading states
|
|
985
|
-
- Don't ignore error handling
|
|
986
|
-
- Don't hardcode text strings
|
|
987
|
-
- Don't skip responsive testing
|
|
988
|
-
- Don't ignore keyboard navigation
|
|
989
|
-
|
|
990
|
-
## References
|
|
991
|
-
|
|
992
|
-
- [shadcn/ui Documentation](https://ui.shadcn.com/)
|
|
993
|
-
- [Radix UI Primitives](https://www.radix-ui.com/)
|
|
994
|
-
- [React Hook Form](https://react-hook-form.com/)
|
|
995
|
-
- [Zod Validation](https://zod.dev/)
|
|
996
|
-
- [Tailwind CSS](https://tailwindcss.com/)
|
|
141
|
+
| Do | Avoid |
|
|
142
|
+
|----|-------|
|
|
143
|
+
| Install only components you need | Modifying generated component files directly |
|
|
144
|
+
| Use `cn()` utility for class merging | Skipping form validation |
|
|
145
|
+
| Extend components with composition | Overriding styles without good reason |
|
|
146
|
+
| Follow React Hook Form patterns | Using inline styles |
|
|
147
|
+
| Use TypeScript for type safety | Skipping loading and error states |
|
|
148
|
+
| Test component accessibility | Ignoring keyboard navigation |
|