omgkit 2.1.1 → 2.2.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/SKILL_STANDARDS.md +743 -0
- package/plugin/skills/databases/mongodb/SKILL.md +797 -28
- package/plugin/skills/databases/prisma/SKILL.md +776 -30
- package/plugin/skills/databases/redis/SKILL.md +885 -25
- package/plugin/skills/devops/aws/SKILL.md +686 -28
- package/plugin/skills/devops/github-actions/SKILL.md +684 -29
- package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
- package/plugin/skills/frameworks/django/SKILL.md +920 -20
- package/plugin/skills/frameworks/express/SKILL.md +1361 -35
- package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
- package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
- package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
- package/plugin/skills/frameworks/rails/SKILL.md +594 -28
- package/plugin/skills/frameworks/spring/SKILL.md +528 -35
- package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
- package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
- package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
- package/plugin/skills/frontend/responsive/SKILL.md +847 -21
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
- package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
- package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
- package/plugin/skills/languages/javascript/SKILL.md +935 -31
- package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
- package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
- package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
- package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
- package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
- package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
- package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
- package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
- package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
- package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
- package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
- package/plugin/skills/security/better-auth/SKILL.md +1065 -28
- package/plugin/skills/security/oauth/SKILL.md +968 -31
- package/plugin/skills/security/owasp/SKILL.md +894 -33
- package/plugin/skills/testing/playwright/SKILL.md +764 -38
- package/plugin/skills/testing/pytest/SKILL.md +873 -36
- package/plugin/skills/testing/vitest/SKILL.md +980 -35
|
@@ -1,58 +1,996 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: shadcn-ui
|
|
3
|
-
description: shadcn/ui
|
|
3
|
+
description: shadcn/ui component library with accessible React components, theming, and form integration
|
|
4
|
+
category: frontend
|
|
5
|
+
triggers:
|
|
6
|
+
- shadcn
|
|
7
|
+
- shadcn/ui
|
|
8
|
+
- ui components
|
|
9
|
+
- radix ui
|
|
10
|
+
- react components
|
|
4
11
|
---
|
|
5
12
|
|
|
6
|
-
# shadcn/ui
|
|
13
|
+
# shadcn/ui
|
|
14
|
+
|
|
15
|
+
Enterprise-grade **React component library** built on Radix UI primitives following industry best practices. This skill covers component installation, theming, form integration, customization, and accessibility patterns used by top engineering teams.
|
|
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
|
|
7
32
|
|
|
8
|
-
## Installation
|
|
9
33
|
```bash
|
|
34
|
+
# Initialize shadcn/ui in your project
|
|
10
35
|
npx shadcn-ui@latest init
|
|
36
|
+
|
|
37
|
+
# Add individual components
|
|
11
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
|
|
49
|
+
```
|
|
50
|
+
|
|
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
|
+
}
|
|
12
70
|
```
|
|
13
71
|
|
|
14
|
-
|
|
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
|
+
|
|
15
84
|
```tsx
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
|
|
85
|
+
// Button usage with variants
|
|
86
|
+
import { Button } from "@/components/ui/button";
|
|
87
|
+
|
|
88
|
+
export function ButtonExamples() {
|
|
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
|
+
}
|
|
19
119
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
120
|
+
// Card component
|
|
121
|
+
import {
|
|
122
|
+
Card,
|
|
123
|
+
CardContent,
|
|
124
|
+
CardDescription,
|
|
125
|
+
CardFooter,
|
|
126
|
+
CardHeader,
|
|
127
|
+
CardTitle,
|
|
128
|
+
} from "@/components/ui/card";
|
|
23
129
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
130
|
+
export function CardExample() {
|
|
131
|
+
return (
|
|
132
|
+
<Card className="w-[350px]">
|
|
133
|
+
<CardHeader>
|
|
134
|
+
<CardTitle>Create project</CardTitle>
|
|
135
|
+
<CardDescription>Deploy your new project in one-click.</CardDescription>
|
|
136
|
+
</CardHeader>
|
|
137
|
+
<CardContent>
|
|
138
|
+
<form>
|
|
139
|
+
<div className="grid w-full items-center gap-4">
|
|
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>
|
|
160
|
+
</CardContent>
|
|
161
|
+
<CardFooter className="flex justify-between">
|
|
162
|
+
<Button variant="outline">Cancel</Button>
|
|
163
|
+
<Button>Deploy</Button>
|
|
164
|
+
</CardFooter>
|
|
165
|
+
</Card>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
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
|
+
}
|
|
32
190
|
```
|
|
33
191
|
|
|
34
|
-
|
|
192
|
+
### 3. Form Integration with React Hook Form
|
|
193
|
+
|
|
35
194
|
```tsx
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
195
|
+
// Complete form example with validation
|
|
196
|
+
"use client";
|
|
197
|
+
|
|
198
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
199
|
+
import { useForm } from "react-hook-form";
|
|
200
|
+
import * as z from "zod";
|
|
201
|
+
|
|
202
|
+
import { Button } from "@/components/ui/button";
|
|
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";
|
|
225
|
+
|
|
226
|
+
const formSchema = z.object({
|
|
227
|
+
username: z
|
|
228
|
+
.string()
|
|
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
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
type FormData = z.infer<typeof formSchema>;
|
|
248
|
+
|
|
249
|
+
export function ProfileForm() {
|
|
250
|
+
const form = useForm<FormData>({
|
|
251
|
+
resolver: zodResolver(formSchema),
|
|
252
|
+
defaultValues: {
|
|
253
|
+
username: "",
|
|
254
|
+
email: "",
|
|
255
|
+
bio: "",
|
|
256
|
+
notifications: false,
|
|
257
|
+
marketingEmails: false,
|
|
258
|
+
securityEmails: true,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
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
|
+
return (
|
|
280
|
+
<Form {...form}>
|
|
281
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
|
282
|
+
<FormField
|
|
283
|
+
control={form.control}
|
|
284
|
+
name="username"
|
|
285
|
+
render={({ field }) => (
|
|
286
|
+
<FormItem>
|
|
287
|
+
<FormLabel>Username</FormLabel>
|
|
288
|
+
<FormControl>
|
|
289
|
+
<Input placeholder="johndoe" {...field} />
|
|
290
|
+
</FormControl>
|
|
291
|
+
<FormDescription>
|
|
292
|
+
This is your public display name.
|
|
293
|
+
</FormDescription>
|
|
294
|
+
<FormMessage />
|
|
295
|
+
</FormItem>
|
|
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
|
+
|
|
438
|
+
<Button type="submit" disabled={form.formState.isSubmitting}>
|
|
439
|
+
{form.formState.isSubmitting ? "Saving..." : "Save changes"}
|
|
440
|
+
</Button>
|
|
441
|
+
</form>
|
|
442
|
+
</Form>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### 4. Dialog and Modals
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
// Dialog component
|
|
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() {
|
|
463
|
+
return (
|
|
464
|
+
<Dialog>
|
|
465
|
+
<DialogTrigger asChild>
|
|
466
|
+
<Button variant="outline">Edit Profile</Button>
|
|
467
|
+
</DialogTrigger>
|
|
468
|
+
<DialogContent className="sm:max-w-[425px]">
|
|
469
|
+
<DialogHeader>
|
|
470
|
+
<DialogTitle>Edit profile</DialogTitle>
|
|
471
|
+
<DialogDescription>
|
|
472
|
+
Make changes to your profile here. Click save when you're done.
|
|
473
|
+
</DialogDescription>
|
|
474
|
+
</DialogHeader>
|
|
475
|
+
<div className="grid gap-4 py-4">
|
|
476
|
+
<div className="grid grid-cols-4 items-center gap-4">
|
|
477
|
+
<Label htmlFor="name" className="text-right">
|
|
478
|
+
Name
|
|
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
|
+
/>
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
<DialogFooter>
|
|
498
|
+
<DialogClose asChild>
|
|
499
|
+
<Button variant="outline">Cancel</Button>
|
|
500
|
+
</DialogClose>
|
|
501
|
+
<Button type="submit">Save changes</Button>
|
|
502
|
+
</DialogFooter>
|
|
503
|
+
</DialogContent>
|
|
504
|
+
</Dialog>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
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
|
+
```
|
|
820
|
+
|
|
821
|
+
### 7. Toast Notifications
|
|
822
|
+
|
|
823
|
+
```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
|
+
import { useToast } from "@/components/ui/use-toast";
|
|
837
|
+
|
|
838
|
+
export function Toaster() {
|
|
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() {
|
|
867
|
+
const { toast } = useToast();
|
|
868
|
+
|
|
869
|
+
return (
|
|
870
|
+
<div className="space-x-4">
|
|
871
|
+
<Button
|
|
872
|
+
onClick={() => {
|
|
873
|
+
toast({
|
|
874
|
+
title: "Success!",
|
|
875
|
+
description: "Your changes have been saved.",
|
|
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>
|
|
947
|
+
|
|
948
|
+
<Card>
|
|
949
|
+
<CardHeader>
|
|
950
|
+
<CardTitle>Recent Orders</CardTitle>
|
|
951
|
+
</CardHeader>
|
|
952
|
+
<CardContent>
|
|
953
|
+
<DataTable />
|
|
954
|
+
</CardContent>
|
|
955
|
+
</Card>
|
|
956
|
+
</main>
|
|
957
|
+
</div>
|
|
958
|
+
);
|
|
959
|
+
}
|
|
53
960
|
```
|
|
54
961
|
|
|
55
962
|
## Best Practices
|
|
963
|
+
|
|
964
|
+
### Do's
|
|
965
|
+
|
|
966
|
+
- Install only components you need
|
|
967
|
+
- Use the cn() utility for class merging
|
|
968
|
+
- Extend components with composition
|
|
969
|
+
- Follow form validation patterns
|
|
970
|
+
- Use proper ARIA attributes
|
|
56
971
|
- Customize via CSS variables
|
|
57
|
-
-
|
|
58
|
-
-
|
|
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/)
|