@adlas/create-app 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/README.md +476 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/figma.d.ts +16 -0
- package/dist/commands/figma.d.ts.map +1 -0
- package/dist/commands/figma.js +172 -0
- package/dist/commands/figma.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +5 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +1471 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/swagger.d.ts +16 -0
- package/dist/commands/swagger.d.ts.map +1 -0
- package/dist/commands/swagger.js +404 -0
- package/dist/commands/swagger.js.map +1 -0
- package/dist/commands/update.d.ts +15 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/package.json +63 -0
- package/templates/.vscode/extensions.json +9 -0
- package/templates/.vscode/launch.json +26 -0
- package/templates/.vscode/settings.json +67 -0
- package/templates/.vscode/tasks.json +21 -0
- package/templates/boilerplate/config/fonts.ts +10 -0
- package/templates/boilerplate/config/navigationUrls.ts +47 -0
- package/templates/boilerplate/config/site.ts +96 -0
- package/templates/boilerplate/libs/I18n.ts +15 -0
- package/templates/boilerplate/libs/I18nNavigation.ts +5 -0
- package/templates/boilerplate/libs/I18nRouting.ts +9 -0
- package/templates/boilerplate/libs/env.ts +21 -0
- package/templates/boilerplate/libs/react-query/ReactQueryProvider.tsx +21 -0
- package/templates/boilerplate/libs/react-query/index.ts +2 -0
- package/templates/boilerplate/libs/react-query/queryClient.ts +62 -0
- package/templates/boilerplate/libs/react-query/queryKeys.ts +5 -0
- package/templates/boilerplate/public/images/index.ts +1 -0
- package/templates/boilerplate/reset.d.ts +2 -0
- package/templates/boilerplate/styles/globals.css +308 -0
- package/templates/boilerplate/types/i18n.ts +10 -0
- package/templates/boilerplate/types/locale.ts +8 -0
- package/templates/boilerplate/utils/file/fileConfig.ts +123 -0
- package/templates/boilerplate/utils/file/fileValidation.ts +78 -0
- package/templates/boilerplate/utils/file/imageCompression.ts +182 -0
- package/templates/boilerplate/utils/file/index.ts +3 -0
- package/templates/boilerplate/utils/helpers.ts +55 -0
- package/templates/boilerplate/validations/auth.validation.ts +92 -0
- package/templates/boilerplate/validations/commonValidations.ts +258 -0
- package/templates/boilerplate/validations/zodErrorMap.ts +101 -0
- package/templates/configs/.env.example +8 -0
- package/templates/configs/.prettierignore +23 -0
- package/templates/configs/.prettierrc.cjs +26 -0
- package/templates/configs/.prettierrc.icons.cjs +11 -0
- package/templates/configs/Dockerfile +6 -0
- package/templates/configs/commitlint.config.ts +8 -0
- package/templates/configs/eslint.config.mjs +119 -0
- package/templates/configs/knip.config.ts +32 -0
- package/templates/configs/lefthook.yml +42 -0
- package/templates/configs/lint-staged.config.js +8 -0
- package/templates/configs/next.config.template.ts +77 -0
- package/templates/configs/next.config.ts +43 -0
- package/templates/configs/package.json +75 -0
- package/templates/configs/postcss.config.mjs +15 -0
- package/templates/configs/svgr.config.mjs +129 -0
- package/templates/configs/tsconfig.json +75 -0
- package/templates/docs/AI_QUICK_REFERENCE.md +379 -0
- package/templates/docs/ARCHITECTURE_PATTERNS.md +927 -0
- package/templates/docs/DOCUMENTATION_INDEX.md +411 -0
- package/templates/docs/FIGMA_TO_CODE_GUIDE.md +768 -0
- package/templates/docs/IMPLEMENTATION_GUIDE.md +892 -0
- package/templates/docs/PROJECT_OVERVIEW.md +302 -0
- package/templates/docs/REFACTOR_PROGRESS.md +1113 -0
- package/templates/docs/SHADCN_TO_HEROUI_MIGRATION.md +1375 -0
- package/templates/docs/UI_COMPONENTS_GUIDE.md +893 -0
|
@@ -0,0 +1,1375 @@
|
|
|
1
|
+
# shadcn/ui to HeroUI Migration Guide
|
|
2
|
+
|
|
3
|
+
This guide provides complete component mappings and conversion patterns for migrating from shadcn/ui to HeroUI.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📚 Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Key Differences](#key-differences)
|
|
10
|
+
2. [Installation & Setup](#installation--setup)
|
|
11
|
+
3. [Component Mappings](#component-mappings)
|
|
12
|
+
4. [Common Patterns](#common-patterns)
|
|
13
|
+
5. [Migration Checklist](#migration-checklist)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Key Differences
|
|
18
|
+
|
|
19
|
+
### Philosophy
|
|
20
|
+
- **shadcn/ui:** Copy-paste components into your project, full customization
|
|
21
|
+
- **HeroUI:** NPM package components, theme-based customization
|
|
22
|
+
|
|
23
|
+
### Styling Approach
|
|
24
|
+
- **shadcn/ui:** Uses `cn()` utility for merging Tailwind classes
|
|
25
|
+
- **HeroUI:** Uses `classNames` prop for different component parts
|
|
26
|
+
|
|
27
|
+
### Import Pattern
|
|
28
|
+
```tsx
|
|
29
|
+
// shadcn/ui
|
|
30
|
+
import { Button } from "@/components/ui/button"
|
|
31
|
+
|
|
32
|
+
// HeroUI
|
|
33
|
+
import { Button } from "@heroui/button"
|
|
34
|
+
// or
|
|
35
|
+
import { Button } from "@heroui/react"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Props Philosophy
|
|
39
|
+
- **shadcn/ui:** Single `className` prop
|
|
40
|
+
- **HeroUI:** Structured `classNames` object for different parts
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation & Setup
|
|
45
|
+
|
|
46
|
+
### Remove shadcn/ui
|
|
47
|
+
```bash
|
|
48
|
+
# Remove shadcn components folder
|
|
49
|
+
rm -rf src/components/ui/
|
|
50
|
+
|
|
51
|
+
# Remove from package.json (if added as dependency)
|
|
52
|
+
pnpm remove @radix-ui/react-* class-variance-authority clsx tailwind-merge
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Install HeroUI
|
|
56
|
+
```bash
|
|
57
|
+
# Already installed in this project
|
|
58
|
+
# If needed in target project:
|
|
59
|
+
pnpm add @heroui/react framer-motion
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Update tailwind.config.js
|
|
63
|
+
```js
|
|
64
|
+
// Remove shadcn config
|
|
65
|
+
// Add HeroUI config (already in this project)
|
|
66
|
+
import { heroui } from "@heroui/react"
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
plugins: [heroui()],
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Component Mappings
|
|
76
|
+
|
|
77
|
+
### Form Components
|
|
78
|
+
|
|
79
|
+
#### Input
|
|
80
|
+
|
|
81
|
+
**shadcn/ui:**
|
|
82
|
+
```tsx
|
|
83
|
+
import { Input } from "@/components/ui/input"
|
|
84
|
+
import { Label } from "@/components/ui/label"
|
|
85
|
+
|
|
86
|
+
<div className="space-y-2">
|
|
87
|
+
<Label htmlFor="email">Email</Label>
|
|
88
|
+
<Input
|
|
89
|
+
id="email"
|
|
90
|
+
type="email"
|
|
91
|
+
placeholder="Enter your email"
|
|
92
|
+
className="w-full"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**HeroUI:**
|
|
98
|
+
```tsx
|
|
99
|
+
import { Input } from "@heroui/input"
|
|
100
|
+
|
|
101
|
+
<Input
|
|
102
|
+
type="email"
|
|
103
|
+
label="Email"
|
|
104
|
+
placeholder="Enter your email"
|
|
105
|
+
variant="bordered"
|
|
106
|
+
classNames={{
|
|
107
|
+
input: "w-full",
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Key Changes:**
|
|
113
|
+
- ✅ Label is built-in via `label` prop
|
|
114
|
+
- ✅ Use `variant="bordered"` for outlined style
|
|
115
|
+
- ✅ Use `classNames` object instead of `className`
|
|
116
|
+
|
|
117
|
+
**Common Variants:**
|
|
118
|
+
```tsx
|
|
119
|
+
// Bordered (most common - similar to shadcn default)
|
|
120
|
+
<Input variant="bordered" />
|
|
121
|
+
|
|
122
|
+
// Flat
|
|
123
|
+
<Input variant="flat" />
|
|
124
|
+
|
|
125
|
+
// Faded
|
|
126
|
+
<Input variant="faded" />
|
|
127
|
+
|
|
128
|
+
// Underlined
|
|
129
|
+
<Input variant="underlined" />
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**With Error State:**
|
|
133
|
+
```tsx
|
|
134
|
+
<Input
|
|
135
|
+
type="email"
|
|
136
|
+
label="Email"
|
|
137
|
+
variant="bordered"
|
|
138
|
+
isInvalid={!!errors.email}
|
|
139
|
+
errorMessage={errors.email?.message}
|
|
140
|
+
/>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
#### Textarea
|
|
146
|
+
|
|
147
|
+
**shadcn/ui:**
|
|
148
|
+
```tsx
|
|
149
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
150
|
+
|
|
151
|
+
<Textarea
|
|
152
|
+
placeholder="Enter description"
|
|
153
|
+
className="min-h-[100px]"
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**HeroUI:**
|
|
158
|
+
```tsx
|
|
159
|
+
import { Textarea } from "@heroui/input"
|
|
160
|
+
|
|
161
|
+
<Textarea
|
|
162
|
+
label="Description"
|
|
163
|
+
placeholder="Enter description"
|
|
164
|
+
variant="bordered"
|
|
165
|
+
minRows={3}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
#### Button
|
|
172
|
+
|
|
173
|
+
**shadcn/ui:**
|
|
174
|
+
```tsx
|
|
175
|
+
import { Button } from "@/components/ui/button"
|
|
176
|
+
|
|
177
|
+
<Button variant="default" size="lg">
|
|
178
|
+
Click me
|
|
179
|
+
</Button>
|
|
180
|
+
|
|
181
|
+
<Button variant="destructive">
|
|
182
|
+
Delete
|
|
183
|
+
</Button>
|
|
184
|
+
|
|
185
|
+
<Button variant="outline">
|
|
186
|
+
Cancel
|
|
187
|
+
</Button>
|
|
188
|
+
|
|
189
|
+
<Button variant="ghost">
|
|
190
|
+
Ghost
|
|
191
|
+
</Button>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**HeroUI:**
|
|
195
|
+
```tsx
|
|
196
|
+
import { Button } from "@heroui/button"
|
|
197
|
+
|
|
198
|
+
<Button color="primary" size="lg">
|
|
199
|
+
Click me
|
|
200
|
+
</Button>
|
|
201
|
+
|
|
202
|
+
<Button color="danger">
|
|
203
|
+
Delete
|
|
204
|
+
</Button>
|
|
205
|
+
|
|
206
|
+
<Button variant="bordered">
|
|
207
|
+
Cancel
|
|
208
|
+
</Button>
|
|
209
|
+
|
|
210
|
+
<Button variant="light">
|
|
211
|
+
Ghost
|
|
212
|
+
</Button>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Variant Mapping:**
|
|
216
|
+
| shadcn/ui | HeroUI |
|
|
217
|
+
|-----------|---------|
|
|
218
|
+
| `variant="default"` | `color="primary"` |
|
|
219
|
+
| `variant="destructive"` | `color="danger"` |
|
|
220
|
+
| `variant="outline"` | `variant="bordered"` |
|
|
221
|
+
| `variant="ghost"` | `variant="light"` |
|
|
222
|
+
| `variant="secondary"` | `color="secondary"` |
|
|
223
|
+
|
|
224
|
+
**With Loading State:**
|
|
225
|
+
```tsx
|
|
226
|
+
// shadcn/ui
|
|
227
|
+
<Button disabled={isLoading}>
|
|
228
|
+
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
229
|
+
Submit
|
|
230
|
+
</Button>
|
|
231
|
+
|
|
232
|
+
// HeroUI
|
|
233
|
+
<Button isLoading={isLoading}>
|
|
234
|
+
Submit
|
|
235
|
+
</Button>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
#### Select
|
|
241
|
+
|
|
242
|
+
**shadcn/ui:**
|
|
243
|
+
```tsx
|
|
244
|
+
import {
|
|
245
|
+
Select,
|
|
246
|
+
SelectContent,
|
|
247
|
+
SelectItem,
|
|
248
|
+
SelectTrigger,
|
|
249
|
+
SelectValue,
|
|
250
|
+
} from "@/components/ui/select"
|
|
251
|
+
|
|
252
|
+
<Select onValueChange={setValue}>
|
|
253
|
+
<SelectTrigger>
|
|
254
|
+
<SelectValue placeholder="Select option" />
|
|
255
|
+
</SelectTrigger>
|
|
256
|
+
<SelectContent>
|
|
257
|
+
<SelectItem value="option1">Option 1</SelectItem>
|
|
258
|
+
<SelectItem value="option2">Option 2</SelectItem>
|
|
259
|
+
</SelectContent>
|
|
260
|
+
</Select>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**HeroUI:**
|
|
264
|
+
```tsx
|
|
265
|
+
import { Select, SelectItem } from "@heroui/select"
|
|
266
|
+
|
|
267
|
+
<Select
|
|
268
|
+
label="Select option"
|
|
269
|
+
placeholder="Choose an option"
|
|
270
|
+
variant="bordered"
|
|
271
|
+
onChange={(e) => setValue(e.target.value)}
|
|
272
|
+
>
|
|
273
|
+
<SelectItem key="option1" value="option1">
|
|
274
|
+
Option 1
|
|
275
|
+
</SelectItem>
|
|
276
|
+
<SelectItem key="option2" value="option2">
|
|
277
|
+
Option 2
|
|
278
|
+
</SelectItem>
|
|
279
|
+
</Select>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Dynamic Options:**
|
|
283
|
+
```tsx
|
|
284
|
+
const options = [
|
|
285
|
+
{ value: "1", label: "Option 1" },
|
|
286
|
+
{ value: "2", label: "Option 2" },
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
<Select
|
|
290
|
+
label="Select option"
|
|
291
|
+
variant="bordered"
|
|
292
|
+
items={options}
|
|
293
|
+
>
|
|
294
|
+
{(item) => (
|
|
295
|
+
<SelectItem key={item.value}>
|
|
296
|
+
{item.label}
|
|
297
|
+
</SelectItem>
|
|
298
|
+
)}
|
|
299
|
+
</Select>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
#### Checkbox
|
|
305
|
+
|
|
306
|
+
**shadcn/ui:**
|
|
307
|
+
```tsx
|
|
308
|
+
import { Checkbox } from "@/components/ui/checkbox"
|
|
309
|
+
|
|
310
|
+
<div className="flex items-center space-x-2">
|
|
311
|
+
<Checkbox id="terms" />
|
|
312
|
+
<label htmlFor="terms">Accept terms</label>
|
|
313
|
+
</div>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**HeroUI:**
|
|
317
|
+
```tsx
|
|
318
|
+
import { Checkbox } from "@heroui/checkbox"
|
|
319
|
+
|
|
320
|
+
<Checkbox>Accept terms</Checkbox>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Controlled:**
|
|
324
|
+
```tsx
|
|
325
|
+
<Checkbox
|
|
326
|
+
isSelected={isAccepted}
|
|
327
|
+
onValueChange={setIsAccepted}
|
|
328
|
+
>
|
|
329
|
+
Accept terms
|
|
330
|
+
</Checkbox>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
#### Radio Group
|
|
336
|
+
|
|
337
|
+
**shadcn/ui:**
|
|
338
|
+
```tsx
|
|
339
|
+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
|
340
|
+
|
|
341
|
+
<RadioGroup value={value} onValueChange={setValue}>
|
|
342
|
+
<div className="flex items-center space-x-2">
|
|
343
|
+
<RadioGroupItem value="option1" id="option1" />
|
|
344
|
+
<Label htmlFor="option1">Option 1</Label>
|
|
345
|
+
</div>
|
|
346
|
+
<div className="flex items-center space-x-2">
|
|
347
|
+
<RadioGroupItem value="option2" id="option2" />
|
|
348
|
+
<Label htmlFor="option2">Option 2</Label>
|
|
349
|
+
</div>
|
|
350
|
+
</RadioGroup>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**HeroUI:**
|
|
354
|
+
```tsx
|
|
355
|
+
import { RadioGroup, Radio } from "@heroui/radio"
|
|
356
|
+
|
|
357
|
+
<RadioGroup
|
|
358
|
+
label="Select option"
|
|
359
|
+
value={value}
|
|
360
|
+
onValueChange={setValue}
|
|
361
|
+
>
|
|
362
|
+
<Radio value="option1">Option 1</Radio>
|
|
363
|
+
<Radio value="option2">Option 2</Radio>
|
|
364
|
+
</RadioGroup>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
#### Switch
|
|
370
|
+
|
|
371
|
+
**shadcn/ui:**
|
|
372
|
+
```tsx
|
|
373
|
+
import { Switch } from "@/components/ui/switch"
|
|
374
|
+
|
|
375
|
+
<Switch checked={isEnabled} onCheckedChange={setIsEnabled} />
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**HeroUI:**
|
|
379
|
+
```tsx
|
|
380
|
+
import { Switch } from "@heroui/switch"
|
|
381
|
+
|
|
382
|
+
<Switch isSelected={isEnabled} onValueChange={setIsEnabled}>
|
|
383
|
+
Enable feature
|
|
384
|
+
</Switch>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
#### Slider
|
|
390
|
+
|
|
391
|
+
**shadcn/ui:**
|
|
392
|
+
```tsx
|
|
393
|
+
import { Slider } from "@/components/ui/slider"
|
|
394
|
+
|
|
395
|
+
<Slider
|
|
396
|
+
value={[value]}
|
|
397
|
+
onValueChange={([val]) => setValue(val)}
|
|
398
|
+
max={100}
|
|
399
|
+
step={1}
|
|
400
|
+
/>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**HeroUI:**
|
|
404
|
+
```tsx
|
|
405
|
+
import { Slider } from "@heroui/slider"
|
|
406
|
+
|
|
407
|
+
<Slider
|
|
408
|
+
label="Price Range"
|
|
409
|
+
value={value}
|
|
410
|
+
onChange={setValue}
|
|
411
|
+
maxValue={100}
|
|
412
|
+
step={1}
|
|
413
|
+
formatOptions={{ style: "currency", currency: "EUR" }}
|
|
414
|
+
/>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Range Slider:**
|
|
418
|
+
```tsx
|
|
419
|
+
<Slider
|
|
420
|
+
label="Price Range"
|
|
421
|
+
value={[minPrice, maxPrice]}
|
|
422
|
+
onChange={([min, max]) => {
|
|
423
|
+
setMinPrice(min)
|
|
424
|
+
setMaxPrice(max)
|
|
425
|
+
}}
|
|
426
|
+
minValue={0}
|
|
427
|
+
maxValue={1000}
|
|
428
|
+
step={10}
|
|
429
|
+
/>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
### Display Components
|
|
435
|
+
|
|
436
|
+
#### Card
|
|
437
|
+
|
|
438
|
+
**shadcn/ui:**
|
|
439
|
+
```tsx
|
|
440
|
+
import {
|
|
441
|
+
Card,
|
|
442
|
+
CardContent,
|
|
443
|
+
CardDescription,
|
|
444
|
+
CardFooter,
|
|
445
|
+
CardHeader,
|
|
446
|
+
CardTitle,
|
|
447
|
+
} from "@/components/ui/card"
|
|
448
|
+
|
|
449
|
+
<Card>
|
|
450
|
+
<CardHeader>
|
|
451
|
+
<CardTitle>Title</CardTitle>
|
|
452
|
+
<CardDescription>Description</CardDescription>
|
|
453
|
+
</CardHeader>
|
|
454
|
+
<CardContent>
|
|
455
|
+
Content goes here
|
|
456
|
+
</CardContent>
|
|
457
|
+
<CardFooter>
|
|
458
|
+
Footer content
|
|
459
|
+
</CardFooter>
|
|
460
|
+
</Card>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**HeroUI:**
|
|
464
|
+
```tsx
|
|
465
|
+
import { Card, CardHeader, CardBody, CardFooter } from "@heroui/card"
|
|
466
|
+
|
|
467
|
+
<Card>
|
|
468
|
+
<CardHeader className="flex flex-col items-start">
|
|
469
|
+
<h4 className="text-lg font-bold">Title</h4>
|
|
470
|
+
<p className="text-sm text-default-500">Description</p>
|
|
471
|
+
</CardHeader>
|
|
472
|
+
<CardBody>
|
|
473
|
+
Content goes here
|
|
474
|
+
</CardBody>
|
|
475
|
+
<CardFooter>
|
|
476
|
+
Footer content
|
|
477
|
+
</CardFooter>
|
|
478
|
+
</Card>
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Variants:**
|
|
482
|
+
```tsx
|
|
483
|
+
// Bordered
|
|
484
|
+
<Card variant="bordered">...</Card>
|
|
485
|
+
|
|
486
|
+
// Elevated (shadow)
|
|
487
|
+
<Card shadow="md">...</Card>
|
|
488
|
+
|
|
489
|
+
// Hoverable
|
|
490
|
+
<Card isHoverable isPressable>...</Card>
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
#### Badge / Chip
|
|
496
|
+
|
|
497
|
+
**shadcn/ui:**
|
|
498
|
+
```tsx
|
|
499
|
+
import { Badge } from "@/components/ui/badge"
|
|
500
|
+
|
|
501
|
+
<Badge variant="default">Badge</Badge>
|
|
502
|
+
<Badge variant="destructive">Error</Badge>
|
|
503
|
+
<Badge variant="outline">Outline</Badge>
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**HeroUI:**
|
|
507
|
+
```tsx
|
|
508
|
+
import { Chip } from "@heroui/chip"
|
|
509
|
+
|
|
510
|
+
<Chip color="primary">Chip</Chip>
|
|
511
|
+
<Chip color="danger">Error</Chip>
|
|
512
|
+
<Chip variant="bordered">Outline</Chip>
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**With Close Button:**
|
|
516
|
+
```tsx
|
|
517
|
+
<Chip
|
|
518
|
+
onClose={() => console.log("closed")}
|
|
519
|
+
variant="flat"
|
|
520
|
+
>
|
|
521
|
+
Closeable
|
|
522
|
+
</Chip>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**As Status:**
|
|
526
|
+
```tsx
|
|
527
|
+
<Chip color="success" variant="dot">Active</Chip>
|
|
528
|
+
<Chip color="warning" variant="dot">Pending</Chip>
|
|
529
|
+
<Chip color="danger" variant="dot">Inactive</Chip>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
#### Avatar
|
|
535
|
+
|
|
536
|
+
**shadcn/ui:**
|
|
537
|
+
```tsx
|
|
538
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
539
|
+
|
|
540
|
+
<Avatar>
|
|
541
|
+
<AvatarImage src="/avatar.jpg" alt="User" />
|
|
542
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
543
|
+
</Avatar>
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**HeroUI:**
|
|
547
|
+
```tsx
|
|
548
|
+
import { Avatar } from "@heroui/avatar"
|
|
549
|
+
|
|
550
|
+
<Avatar
|
|
551
|
+
src="/avatar.jpg"
|
|
552
|
+
alt="User"
|
|
553
|
+
showFallback
|
|
554
|
+
name="John Doe"
|
|
555
|
+
/>
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Sizes:**
|
|
559
|
+
```tsx
|
|
560
|
+
<Avatar size="sm" />
|
|
561
|
+
<Avatar size="md" />
|
|
562
|
+
<Avatar size="lg" />
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**With Badge:**
|
|
566
|
+
```tsx
|
|
567
|
+
<Avatar
|
|
568
|
+
isBordered
|
|
569
|
+
color="success"
|
|
570
|
+
src="/avatar.jpg"
|
|
571
|
+
/>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
#### Alert / Callout
|
|
577
|
+
|
|
578
|
+
**shadcn/ui:**
|
|
579
|
+
```tsx
|
|
580
|
+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
|
581
|
+
|
|
582
|
+
<Alert variant="destructive">
|
|
583
|
+
<AlertCircle className="h-4 w-4" />
|
|
584
|
+
<AlertTitle>Error</AlertTitle>
|
|
585
|
+
<AlertDescription>
|
|
586
|
+
Something went wrong
|
|
587
|
+
</AlertDescription>
|
|
588
|
+
</Alert>
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**HeroUI:**
|
|
592
|
+
```tsx
|
|
593
|
+
// HeroUI doesn't have Alert component
|
|
594
|
+
// Use custom component or Card with colors
|
|
595
|
+
|
|
596
|
+
import { Card, CardBody } from "@heroui/card"
|
|
597
|
+
|
|
598
|
+
<Card className="bg-danger-50 border-l-4 border-danger">
|
|
599
|
+
<CardBody>
|
|
600
|
+
<div className="flex gap-2">
|
|
601
|
+
<AlertCircle className="text-danger" />
|
|
602
|
+
<div>
|
|
603
|
+
<h4 className="text-danger font-semibold">Error</h4>
|
|
604
|
+
<p className="text-danger-600">Something went wrong</p>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
</CardBody>
|
|
608
|
+
</Card>
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
**Or create reusable Alert component:**
|
|
612
|
+
```tsx
|
|
613
|
+
// components/ui/Alert.tsx
|
|
614
|
+
import { Card, CardBody } from "@heroui/card"
|
|
615
|
+
import { cn } from "@/lib/utils"
|
|
616
|
+
|
|
617
|
+
type AlertProps = {
|
|
618
|
+
variant?: "info" | "success" | "warning" | "danger"
|
|
619
|
+
title?: string
|
|
620
|
+
children: React.ReactNode
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export function Alert({ variant = "info", title, children }: AlertProps) {
|
|
624
|
+
const colors = {
|
|
625
|
+
info: "bg-primary-50 border-primary text-primary-900",
|
|
626
|
+
success: "bg-success-50 border-success text-success-900",
|
|
627
|
+
warning: "bg-warning-50 border-warning text-warning-900",
|
|
628
|
+
danger: "bg-danger-50 border-danger text-danger-900",
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return (
|
|
632
|
+
<Card className={cn("border-l-4", colors[variant])}>
|
|
633
|
+
<CardBody>
|
|
634
|
+
{title && <h4 className="font-semibold mb-1">{title}</h4>}
|
|
635
|
+
<div>{children}</div>
|
|
636
|
+
</CardBody>
|
|
637
|
+
</Card>
|
|
638
|
+
)
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
### Navigation Components
|
|
645
|
+
|
|
646
|
+
#### Tabs
|
|
647
|
+
|
|
648
|
+
**shadcn/ui:**
|
|
649
|
+
```tsx
|
|
650
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
651
|
+
|
|
652
|
+
<Tabs defaultValue="tab1">
|
|
653
|
+
<TabsList>
|
|
654
|
+
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
655
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
656
|
+
</TabsList>
|
|
657
|
+
<TabsContent value="tab1">
|
|
658
|
+
Content 1
|
|
659
|
+
</TabsContent>
|
|
660
|
+
<TabsContent value="tab2">
|
|
661
|
+
Content 2
|
|
662
|
+
</TabsContent>
|
|
663
|
+
</Tabs>
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**HeroUI:**
|
|
667
|
+
```tsx
|
|
668
|
+
import { Tabs, Tab } from "@heroui/tabs"
|
|
669
|
+
|
|
670
|
+
<Tabs>
|
|
671
|
+
<Tab key="tab1" title="Tab 1">
|
|
672
|
+
Content 1
|
|
673
|
+
</Tab>
|
|
674
|
+
<Tab key="tab2" title="Tab 2">
|
|
675
|
+
Content 2
|
|
676
|
+
</Tab>
|
|
677
|
+
</Tabs>
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Controlled:**
|
|
681
|
+
```tsx
|
|
682
|
+
<Tabs
|
|
683
|
+
selectedKey={selected}
|
|
684
|
+
onSelectionChange={setSelected}
|
|
685
|
+
>
|
|
686
|
+
<Tab key="tab1" title="Tab 1">Content 1</Tab>
|
|
687
|
+
<Tab key="tab2" title="Tab 2">Content 2</Tab>
|
|
688
|
+
</Tabs>
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**With Icons:**
|
|
692
|
+
```tsx
|
|
693
|
+
import { User, Settings } from "lucide-react"
|
|
694
|
+
|
|
695
|
+
<Tabs>
|
|
696
|
+
<Tab
|
|
697
|
+
key="profile"
|
|
698
|
+
title={
|
|
699
|
+
<div className="flex items-center gap-2">
|
|
700
|
+
<User size={16} />
|
|
701
|
+
Profile
|
|
702
|
+
</div>
|
|
703
|
+
}
|
|
704
|
+
>
|
|
705
|
+
Profile content
|
|
706
|
+
</Tab>
|
|
707
|
+
<Tab
|
|
708
|
+
key="settings"
|
|
709
|
+
title={
|
|
710
|
+
<div className="flex items-center gap-2">
|
|
711
|
+
<Settings size={16} />
|
|
712
|
+
Settings
|
|
713
|
+
</div>
|
|
714
|
+
}
|
|
715
|
+
>
|
|
716
|
+
Settings content
|
|
717
|
+
</Tab>
|
|
718
|
+
</Tabs>
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
---
|
|
722
|
+
|
|
723
|
+
#### Dropdown Menu
|
|
724
|
+
|
|
725
|
+
**shadcn/ui:**
|
|
726
|
+
```tsx
|
|
727
|
+
import {
|
|
728
|
+
DropdownMenu,
|
|
729
|
+
DropdownMenuContent,
|
|
730
|
+
DropdownMenuItem,
|
|
731
|
+
DropdownMenuTrigger,
|
|
732
|
+
} from "@/components/ui/dropdown-menu"
|
|
733
|
+
|
|
734
|
+
<DropdownMenu>
|
|
735
|
+
<DropdownMenuTrigger asChild>
|
|
736
|
+
<Button variant="outline">Open</Button>
|
|
737
|
+
</DropdownMenuTrigger>
|
|
738
|
+
<DropdownMenuContent>
|
|
739
|
+
<DropdownMenuItem>Item 1</DropdownMenuItem>
|
|
740
|
+
<DropdownMenuItem>Item 2</DropdownMenuItem>
|
|
741
|
+
</DropdownMenuContent>
|
|
742
|
+
</DropdownMenu>
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
**HeroUI:**
|
|
746
|
+
```tsx
|
|
747
|
+
import {
|
|
748
|
+
Dropdown,
|
|
749
|
+
DropdownTrigger,
|
|
750
|
+
DropdownMenu,
|
|
751
|
+
DropdownItem,
|
|
752
|
+
} from "@heroui/dropdown"
|
|
753
|
+
import { Button } from "@heroui/button"
|
|
754
|
+
|
|
755
|
+
<Dropdown>
|
|
756
|
+
<DropdownTrigger>
|
|
757
|
+
<Button variant="bordered">Open</Button>
|
|
758
|
+
</DropdownTrigger>
|
|
759
|
+
<DropdownMenu>
|
|
760
|
+
<DropdownItem key="item1">Item 1</DropdownItem>
|
|
761
|
+
<DropdownItem key="item2">Item 2</DropdownItem>
|
|
762
|
+
</DropdownMenu>
|
|
763
|
+
</Dropdown>
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**With Actions:**
|
|
767
|
+
```tsx
|
|
768
|
+
<DropdownMenu
|
|
769
|
+
onAction={(key) => {
|
|
770
|
+
if (key === "edit") handleEdit()
|
|
771
|
+
if (key === "delete") handleDelete()
|
|
772
|
+
}}
|
|
773
|
+
>
|
|
774
|
+
<DropdownItem key="edit">Edit</DropdownItem>
|
|
775
|
+
<DropdownItem key="delete" color="danger">
|
|
776
|
+
Delete
|
|
777
|
+
</DropdownItem>
|
|
778
|
+
</DropdownMenu>
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
#### Breadcrumbs
|
|
784
|
+
|
|
785
|
+
**shadcn/ui:**
|
|
786
|
+
```tsx
|
|
787
|
+
// Usually custom implementation
|
|
788
|
+
<nav className="flex">
|
|
789
|
+
<Link href="/">Home</Link>
|
|
790
|
+
<span>/</span>
|
|
791
|
+
<Link href="/products">Products</Link>
|
|
792
|
+
<span>/</span>
|
|
793
|
+
<span>Current</span>
|
|
794
|
+
</nav>
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
**HeroUI:**
|
|
798
|
+
```tsx
|
|
799
|
+
import { Breadcrumbs, BreadcrumbItem } from "@heroui/breadcrumbs"
|
|
800
|
+
|
|
801
|
+
<Breadcrumbs>
|
|
802
|
+
<BreadcrumbItem href="/">Home</BreadcrumbItem>
|
|
803
|
+
<BreadcrumbItem href="/products">Products</BreadcrumbItem>
|
|
804
|
+
<BreadcrumbItem>Current</BreadcrumbItem>
|
|
805
|
+
</Breadcrumbs>
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
### Overlay Components
|
|
811
|
+
|
|
812
|
+
#### Dialog / Modal
|
|
813
|
+
|
|
814
|
+
**shadcn/ui:**
|
|
815
|
+
```tsx
|
|
816
|
+
import {
|
|
817
|
+
Dialog,
|
|
818
|
+
DialogContent,
|
|
819
|
+
DialogDescription,
|
|
820
|
+
DialogHeader,
|
|
821
|
+
DialogTitle,
|
|
822
|
+
DialogTrigger,
|
|
823
|
+
} from "@/components/ui/dialog"
|
|
824
|
+
|
|
825
|
+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
826
|
+
<DialogTrigger asChild>
|
|
827
|
+
<Button>Open Dialog</Button>
|
|
828
|
+
</DialogTrigger>
|
|
829
|
+
<DialogContent>
|
|
830
|
+
<DialogHeader>
|
|
831
|
+
<DialogTitle>Title</DialogTitle>
|
|
832
|
+
<DialogDescription>Description</DialogDescription>
|
|
833
|
+
</DialogHeader>
|
|
834
|
+
<div>Content</div>
|
|
835
|
+
</DialogContent>
|
|
836
|
+
</Dialog>
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
**HeroUI:**
|
|
840
|
+
```tsx
|
|
841
|
+
import {
|
|
842
|
+
Modal,
|
|
843
|
+
ModalContent,
|
|
844
|
+
ModalHeader,
|
|
845
|
+
ModalBody,
|
|
846
|
+
ModalFooter,
|
|
847
|
+
useDisclosure,
|
|
848
|
+
} from "@heroui/modal"
|
|
849
|
+
import { Button } from "@heroui/button"
|
|
850
|
+
|
|
851
|
+
const { isOpen, onOpen, onClose } = useDisclosure()
|
|
852
|
+
|
|
853
|
+
<>
|
|
854
|
+
<Button onPress={onOpen}>Open Modal</Button>
|
|
855
|
+
<Modal isOpen={isOpen} onClose={onClose}>
|
|
856
|
+
<ModalContent>
|
|
857
|
+
<ModalHeader>Title</ModalHeader>
|
|
858
|
+
<ModalBody>
|
|
859
|
+
<p>Content goes here</p>
|
|
860
|
+
</ModalBody>
|
|
861
|
+
<ModalFooter>
|
|
862
|
+
<Button variant="light" onPress={onClose}>
|
|
863
|
+
Cancel
|
|
864
|
+
</Button>
|
|
865
|
+
<Button color="primary" onPress={onClose}>
|
|
866
|
+
Confirm
|
|
867
|
+
</Button>
|
|
868
|
+
</ModalFooter>
|
|
869
|
+
</ModalContent>
|
|
870
|
+
</Modal>
|
|
871
|
+
</>
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**Key Differences:**
|
|
875
|
+
- Use `useDisclosure()` hook for state management
|
|
876
|
+
- ModalContent wraps all content
|
|
877
|
+
- More structured layout with Header/Body/Footer
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
#### Popover
|
|
882
|
+
|
|
883
|
+
**shadcn/ui:**
|
|
884
|
+
```tsx
|
|
885
|
+
import {
|
|
886
|
+
Popover,
|
|
887
|
+
PopoverContent,
|
|
888
|
+
PopoverTrigger,
|
|
889
|
+
} from "@/components/ui/popover"
|
|
890
|
+
|
|
891
|
+
<Popover>
|
|
892
|
+
<PopoverTrigger asChild>
|
|
893
|
+
<Button>Open</Button>
|
|
894
|
+
</PopoverTrigger>
|
|
895
|
+
<PopoverContent>
|
|
896
|
+
Content here
|
|
897
|
+
</PopoverContent>
|
|
898
|
+
</Popover>
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
**HeroUI:**
|
|
902
|
+
```tsx
|
|
903
|
+
import { Popover, PopoverTrigger, PopoverContent } from "@heroui/popover"
|
|
904
|
+
import { Button } from "@heroui/button"
|
|
905
|
+
|
|
906
|
+
<Popover>
|
|
907
|
+
<PopoverTrigger>
|
|
908
|
+
<Button>Open</Button>
|
|
909
|
+
</PopoverTrigger>
|
|
910
|
+
<PopoverContent>
|
|
911
|
+
<div className="p-2">
|
|
912
|
+
Content here
|
|
913
|
+
</div>
|
|
914
|
+
</PopoverContent>
|
|
915
|
+
</Popover>
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
#### Tooltip
|
|
921
|
+
|
|
922
|
+
**shadcn/ui:**
|
|
923
|
+
```tsx
|
|
924
|
+
import {
|
|
925
|
+
Tooltip,
|
|
926
|
+
TooltipContent,
|
|
927
|
+
TooltipProvider,
|
|
928
|
+
TooltipTrigger,
|
|
929
|
+
} from "@/components/ui/tooltip"
|
|
930
|
+
|
|
931
|
+
<TooltipProvider>
|
|
932
|
+
<Tooltip>
|
|
933
|
+
<TooltipTrigger asChild>
|
|
934
|
+
<Button>Hover me</Button>
|
|
935
|
+
</TooltipTrigger>
|
|
936
|
+
<TooltipContent>
|
|
937
|
+
Tooltip text
|
|
938
|
+
</TooltipContent>
|
|
939
|
+
</Tooltip>
|
|
940
|
+
</TooltipProvider>
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
**HeroUI:**
|
|
944
|
+
```tsx
|
|
945
|
+
import { Tooltip } from "@heroui/tooltip"
|
|
946
|
+
import { Button } from "@heroui/button"
|
|
947
|
+
|
|
948
|
+
<Tooltip content="Tooltip text">
|
|
949
|
+
<Button>Hover me</Button>
|
|
950
|
+
</Tooltip>
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
**Much simpler!** No provider needed.
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
### Feedback Components
|
|
958
|
+
|
|
959
|
+
#### Progress
|
|
960
|
+
|
|
961
|
+
**shadcn/ui:**
|
|
962
|
+
```tsx
|
|
963
|
+
import { Progress } from "@/components/ui/progress"
|
|
964
|
+
|
|
965
|
+
<Progress value={progress} max={100} />
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
**HeroUI:**
|
|
969
|
+
```tsx
|
|
970
|
+
import { Progress } from "@heroui/progress"
|
|
971
|
+
|
|
972
|
+
<Progress
|
|
973
|
+
value={progress}
|
|
974
|
+
maxValue={100}
|
|
975
|
+
color="primary"
|
|
976
|
+
showValueLabel
|
|
977
|
+
/>
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
**Circular Progress:**
|
|
981
|
+
```tsx
|
|
982
|
+
import { CircularProgress } from "@heroui/progress"
|
|
983
|
+
|
|
984
|
+
<CircularProgress
|
|
985
|
+
value={progress}
|
|
986
|
+
color="primary"
|
|
987
|
+
showValueLabel
|
|
988
|
+
/>
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
---
|
|
992
|
+
|
|
993
|
+
#### Skeleton
|
|
994
|
+
|
|
995
|
+
**shadcn/ui:**
|
|
996
|
+
```tsx
|
|
997
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
998
|
+
|
|
999
|
+
<Skeleton className="h-12 w-12 rounded-full" />
|
|
1000
|
+
<Skeleton className="h-4 w-[250px]" />
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
**HeroUI:**
|
|
1004
|
+
```tsx
|
|
1005
|
+
import { Skeleton } from "@heroui/skeleton"
|
|
1006
|
+
|
|
1007
|
+
<Skeleton className="rounded-full w-12 h-12" />
|
|
1008
|
+
<Skeleton className="h-4 w-[250px] rounded-lg" />
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
**With isLoaded:**
|
|
1012
|
+
```tsx
|
|
1013
|
+
<Skeleton isLoaded={!isLoading} className="rounded-lg">
|
|
1014
|
+
<div className="h-24 bg-default-200"></div>
|
|
1015
|
+
</Skeleton>
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
---
|
|
1019
|
+
|
|
1020
|
+
#### Spinner
|
|
1021
|
+
|
|
1022
|
+
**shadcn/ui:**
|
|
1023
|
+
```tsx
|
|
1024
|
+
// Usually custom or lucide-react Loader2
|
|
1025
|
+
import { Loader2 } from "lucide-react"
|
|
1026
|
+
|
|
1027
|
+
<Loader2 className="animate-spin" />
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
**HeroUI:**
|
|
1031
|
+
```tsx
|
|
1032
|
+
import { Spinner } from "@heroui/spinner"
|
|
1033
|
+
|
|
1034
|
+
<Spinner />
|
|
1035
|
+
<Spinner size="sm" />
|
|
1036
|
+
<Spinner size="lg" />
|
|
1037
|
+
<Spinner color="primary" />
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
---
|
|
1041
|
+
|
|
1042
|
+
### Data Display Components
|
|
1043
|
+
|
|
1044
|
+
#### Table
|
|
1045
|
+
|
|
1046
|
+
**shadcn/ui:**
|
|
1047
|
+
```tsx
|
|
1048
|
+
import {
|
|
1049
|
+
Table,
|
|
1050
|
+
TableBody,
|
|
1051
|
+
TableCell,
|
|
1052
|
+
TableHead,
|
|
1053
|
+
TableHeader,
|
|
1054
|
+
TableRow,
|
|
1055
|
+
} from "@/components/ui/table"
|
|
1056
|
+
|
|
1057
|
+
<Table>
|
|
1058
|
+
<TableHeader>
|
|
1059
|
+
<TableRow>
|
|
1060
|
+
<TableHead>Name</TableHead>
|
|
1061
|
+
<TableHead>Email</TableHead>
|
|
1062
|
+
</TableRow>
|
|
1063
|
+
</TableHeader>
|
|
1064
|
+
<TableBody>
|
|
1065
|
+
<TableRow>
|
|
1066
|
+
<TableCell>John</TableCell>
|
|
1067
|
+
<TableCell>john@example.com</TableCell>
|
|
1068
|
+
</TableRow>
|
|
1069
|
+
</TableBody>
|
|
1070
|
+
</Table>
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
**HeroUI:**
|
|
1074
|
+
```tsx
|
|
1075
|
+
import {
|
|
1076
|
+
Table,
|
|
1077
|
+
TableHeader,
|
|
1078
|
+
TableColumn,
|
|
1079
|
+
TableBody,
|
|
1080
|
+
TableRow,
|
|
1081
|
+
TableCell,
|
|
1082
|
+
} from "@heroui/table"
|
|
1083
|
+
|
|
1084
|
+
<Table>
|
|
1085
|
+
<TableHeader>
|
|
1086
|
+
<TableColumn>NAME</TableColumn>
|
|
1087
|
+
<TableColumn>EMAIL</TableColumn>
|
|
1088
|
+
</TableHeader>
|
|
1089
|
+
<TableBody>
|
|
1090
|
+
<TableRow key="1">
|
|
1091
|
+
<TableCell>John</TableCell>
|
|
1092
|
+
<TableCell>john@example.com</TableCell>
|
|
1093
|
+
</TableRow>
|
|
1094
|
+
</TableBody>
|
|
1095
|
+
</Table>
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
**Dynamic Rendering:**
|
|
1099
|
+
```tsx
|
|
1100
|
+
const columns = [
|
|
1101
|
+
{ key: "name", label: "NAME" },
|
|
1102
|
+
{ key: "email", label: "EMAIL" },
|
|
1103
|
+
]
|
|
1104
|
+
|
|
1105
|
+
const rows = [
|
|
1106
|
+
{ id: 1, name: "John", email: "john@example.com" },
|
|
1107
|
+
{ id: 2, name: "Jane", email: "jane@example.com" },
|
|
1108
|
+
]
|
|
1109
|
+
|
|
1110
|
+
<Table>
|
|
1111
|
+
<TableHeader columns={columns}>
|
|
1112
|
+
{(column) => <TableColumn key={column.key}>{column.label}</TableColumn>}
|
|
1113
|
+
</TableHeader>
|
|
1114
|
+
<TableBody items={rows}>
|
|
1115
|
+
{(item) => (
|
|
1116
|
+
<TableRow key={item.id}>
|
|
1117
|
+
{(columnKey) => <TableCell>{item[columnKey]}</TableCell>}
|
|
1118
|
+
</TableRow>
|
|
1119
|
+
)}
|
|
1120
|
+
</TableBody>
|
|
1121
|
+
</Table>
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
---
|
|
1125
|
+
|
|
1126
|
+
#### Pagination
|
|
1127
|
+
|
|
1128
|
+
**shadcn/ui:**
|
|
1129
|
+
```tsx
|
|
1130
|
+
// Usually custom implementation
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
**HeroUI:**
|
|
1134
|
+
```tsx
|
|
1135
|
+
import { Pagination } from "@heroui/pagination"
|
|
1136
|
+
|
|
1137
|
+
<Pagination
|
|
1138
|
+
total={10}
|
|
1139
|
+
page={currentPage}
|
|
1140
|
+
onChange={setCurrentPage}
|
|
1141
|
+
/>
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
**With boundary & siblings:**
|
|
1145
|
+
```tsx
|
|
1146
|
+
<Pagination
|
|
1147
|
+
total={20}
|
|
1148
|
+
initialPage={1}
|
|
1149
|
+
boundaries={2}
|
|
1150
|
+
siblings={1}
|
|
1151
|
+
onChange={setPage}
|
|
1152
|
+
/>
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
---
|
|
1156
|
+
|
|
1157
|
+
## Common Patterns
|
|
1158
|
+
|
|
1159
|
+
### Form with Validation
|
|
1160
|
+
|
|
1161
|
+
**shadcn/ui:**
|
|
1162
|
+
```tsx
|
|
1163
|
+
import { useForm } from "react-hook-form"
|
|
1164
|
+
import { zodResolver } from "@hookform/resolvers/zod"
|
|
1165
|
+
import * as z from "zod"
|
|
1166
|
+
import { Button } from "@/components/ui/button"
|
|
1167
|
+
import { Input } from "@/components/ui/input"
|
|
1168
|
+
import {
|
|
1169
|
+
Form,
|
|
1170
|
+
FormControl,
|
|
1171
|
+
FormField,
|
|
1172
|
+
FormItem,
|
|
1173
|
+
FormLabel,
|
|
1174
|
+
FormMessage,
|
|
1175
|
+
} from "@/components/ui/form"
|
|
1176
|
+
|
|
1177
|
+
const schema = z.object({
|
|
1178
|
+
email: z.string().email(),
|
|
1179
|
+
})
|
|
1180
|
+
|
|
1181
|
+
function MyForm() {
|
|
1182
|
+
const form = useForm({
|
|
1183
|
+
resolver: zodResolver(schema),
|
|
1184
|
+
})
|
|
1185
|
+
|
|
1186
|
+
return (
|
|
1187
|
+
<Form {...form}>
|
|
1188
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
1189
|
+
<FormField
|
|
1190
|
+
control={form.control}
|
|
1191
|
+
name="email"
|
|
1192
|
+
render={({ field }) => (
|
|
1193
|
+
<FormItem>
|
|
1194
|
+
<FormLabel>Email</FormLabel>
|
|
1195
|
+
<FormControl>
|
|
1196
|
+
<Input {...field} />
|
|
1197
|
+
</FormControl>
|
|
1198
|
+
<FormMessage />
|
|
1199
|
+
</FormItem>
|
|
1200
|
+
)}
|
|
1201
|
+
/>
|
|
1202
|
+
<Button type="submit">Submit</Button>
|
|
1203
|
+
</form>
|
|
1204
|
+
</Form>
|
|
1205
|
+
)
|
|
1206
|
+
}
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
**HeroUI:**
|
|
1210
|
+
```tsx
|
|
1211
|
+
import { useForm } from "react-hook-form"
|
|
1212
|
+
import { zodResolver } from "@hookform/resolvers/zod"
|
|
1213
|
+
import * as z from "zod"
|
|
1214
|
+
import { Button } from "@heroui/button"
|
|
1215
|
+
import { Input } from "@heroui/input"
|
|
1216
|
+
|
|
1217
|
+
const schema = z.object({
|
|
1218
|
+
email: z.string().email("Invalid email"),
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
function MyForm() {
|
|
1222
|
+
const {
|
|
1223
|
+
register,
|
|
1224
|
+
handleSubmit,
|
|
1225
|
+
formState: { errors },
|
|
1226
|
+
} = useForm({
|
|
1227
|
+
resolver: zodResolver(schema),
|
|
1228
|
+
})
|
|
1229
|
+
|
|
1230
|
+
return (
|
|
1231
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
1232
|
+
<Input
|
|
1233
|
+
{...register("email")}
|
|
1234
|
+
type="email"
|
|
1235
|
+
label="Email"
|
|
1236
|
+
variant="bordered"
|
|
1237
|
+
isInvalid={!!errors.email}
|
|
1238
|
+
errorMessage={errors.email?.message}
|
|
1239
|
+
/>
|
|
1240
|
+
<Button type="submit" color="primary">
|
|
1241
|
+
Submit
|
|
1242
|
+
</Button>
|
|
1243
|
+
</form>
|
|
1244
|
+
)
|
|
1245
|
+
}
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
**Key Differences:**
|
|
1249
|
+
- No Form wrapper component needed
|
|
1250
|
+
- Simpler field registration
|
|
1251
|
+
- Built-in error display with `isInvalid` and `errorMessage`
|
|
1252
|
+
|
|
1253
|
+
---
|
|
1254
|
+
|
|
1255
|
+
### Loading States
|
|
1256
|
+
|
|
1257
|
+
**shadcn/ui:**
|
|
1258
|
+
```tsx
|
|
1259
|
+
<Button disabled={isLoading}>
|
|
1260
|
+
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
1261
|
+
Submit
|
|
1262
|
+
</Button>
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
**HeroUI:**
|
|
1266
|
+
```tsx
|
|
1267
|
+
<Button isLoading={isLoading}>
|
|
1268
|
+
Submit
|
|
1269
|
+
</Button>
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
Much cleaner! 🎉
|
|
1273
|
+
|
|
1274
|
+
---
|
|
1275
|
+
|
|
1276
|
+
### Conditional Rendering
|
|
1277
|
+
|
|
1278
|
+
**shadcn/ui:**
|
|
1279
|
+
```tsx
|
|
1280
|
+
{isLoading ? (
|
|
1281
|
+
<Skeleton className="h-20 w-full" />
|
|
1282
|
+
) : (
|
|
1283
|
+
<Card>Content</Card>
|
|
1284
|
+
)}
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
**HeroUI:**
|
|
1288
|
+
```tsx
|
|
1289
|
+
<Skeleton isLoaded={!isLoading}>
|
|
1290
|
+
<Card>Content</Card>
|
|
1291
|
+
</Skeleton>
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
---
|
|
1295
|
+
|
|
1296
|
+
## Migration Checklist
|
|
1297
|
+
|
|
1298
|
+
Use this checklist when converting a page:
|
|
1299
|
+
|
|
1300
|
+
### Before Starting
|
|
1301
|
+
- [ ] Read this migration guide
|
|
1302
|
+
- [ ] Identify all shadcn components used in the page
|
|
1303
|
+
- [ ] Check if HeroUI equivalent exists
|
|
1304
|
+
|
|
1305
|
+
### During Migration
|
|
1306
|
+
- [ ] Replace imports
|
|
1307
|
+
- [ ] Update component names
|
|
1308
|
+
- [ ] Convert `className` to `classNames` where needed
|
|
1309
|
+
- [ ] Update variant prop names
|
|
1310
|
+
- [ ] Update boolean prop names (checked → isSelected, disabled → isDisabled)
|
|
1311
|
+
- [ ] Replace custom loading spinners with `isLoading` prop
|
|
1312
|
+
- [ ] Update form validation display
|
|
1313
|
+
- [ ] Test all interactive states
|
|
1314
|
+
|
|
1315
|
+
### After Migration
|
|
1316
|
+
- [ ] Remove unused shadcn component files
|
|
1317
|
+
- [ ] Test all functionality
|
|
1318
|
+
- [ ] Check mobile responsiveness
|
|
1319
|
+
- [ ] Verify accessibility
|
|
1320
|
+
- [ ] Check loading states
|
|
1321
|
+
- [ ] Verify error states
|
|
1322
|
+
- [ ] Test form validation
|
|
1323
|
+
|
|
1324
|
+
---
|
|
1325
|
+
|
|
1326
|
+
## Quick Reference: Prop Name Changes
|
|
1327
|
+
|
|
1328
|
+
| shadcn/ui | HeroUI |
|
|
1329
|
+
|-----------|---------|
|
|
1330
|
+
| `className` | `classNames` (object) or `className` (string) |
|
|
1331
|
+
| `checked` | `isSelected` |
|
|
1332
|
+
| `disabled` | `isDisabled` |
|
|
1333
|
+
| `onCheckedChange` | `onValueChange` |
|
|
1334
|
+
| `onValueChange` (select) | `onChange` |
|
|
1335
|
+
| `open` | `isOpen` |
|
|
1336
|
+
| `onOpenChange` | `onClose` / `onOpenChange` |
|
|
1337
|
+
| `variant="default"` | `color="primary"` |
|
|
1338
|
+
| `variant="destructive"` | `color="danger"` |
|
|
1339
|
+
| `variant="outline"` | `variant="bordered"` |
|
|
1340
|
+
|
|
1341
|
+
---
|
|
1342
|
+
|
|
1343
|
+
## Tips
|
|
1344
|
+
|
|
1345
|
+
1. **Start Simple:** Begin with simple components (Button, Input) before tackling complex ones
|
|
1346
|
+
|
|
1347
|
+
2. **Use TypeScript:** HeroUI has excellent TypeScript support - use it!
|
|
1348
|
+
|
|
1349
|
+
3. **Leverage Built-in States:** HeroUI components have built-in loading, disabled, invalid states
|
|
1350
|
+
|
|
1351
|
+
4. **Consistent Variants:** Use `variant="bordered"` consistently for form inputs to match shadcn style
|
|
1352
|
+
|
|
1353
|
+
5. **Check Examples:** See `UI_COMPONENTS_GUIDE.md` for complete HeroUI examples
|
|
1354
|
+
|
|
1355
|
+
6. **Theme Customization:** If you need custom colors, use HeroUI's theme configuration
|
|
1356
|
+
|
|
1357
|
+
7. **Accessibility:** HeroUI components are accessible by default - maintain this!
|
|
1358
|
+
|
|
1359
|
+
---
|
|
1360
|
+
|
|
1361
|
+
## Need Help?
|
|
1362
|
+
|
|
1363
|
+
- **HeroUI Docs:** https://www.heroui.com/docs
|
|
1364
|
+
- **This Project Examples:** Check `src/components/` folder for real implementations
|
|
1365
|
+
- **UI Components Guide:** See `UI_COMPONENTS_GUIDE.md` for detailed HeroUI patterns
|
|
1366
|
+
|
|
1367
|
+
---
|
|
1368
|
+
|
|
1369
|
+
**Remember:** The goal is not just to replace components, but to leverage HeroUI's strengths:
|
|
1370
|
+
- Built-in states (loading, disabled, invalid)
|
|
1371
|
+
- Better TypeScript support
|
|
1372
|
+
- Consistent theming
|
|
1373
|
+
- Less custom code
|
|
1374
|
+
|
|
1375
|
+
Good luck with your migration! 🚀
|