shadcn-ui-react 0.7.1 โ 0.7.5
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 +450 -336
- package/dist/index.cjs +2183 -1455
- package/dist/index.d.cts +273 -912
- package/dist/index.d.ts +273 -912
- package/dist/index.js +2207 -1494
- package/dist/style.css +169 -44
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,54 +1,48 @@
|
|
|
1
1
|
# ๐งฉ shadcn-ui-react
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A modern React UI kit inspired by shadcn/ui. It includes accessible Radix primitives, production-ready shared components, form helpers for `react-hook-form`, Tailwind CSS styling, and reusable demos you can copy into your app.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## ๐ฆ Installation
|
|
8
8
|
|
|
9
|
-
Install via your preferred package manager:
|
|
10
|
-
|
|
11
9
|
```bash
|
|
12
10
|
npm install shadcn-ui-react
|
|
13
11
|
# or
|
|
14
|
-
yarn add shadcn-ui-react
|
|
15
|
-
# or
|
|
16
12
|
pnpm add shadcn-ui-react
|
|
13
|
+
# or
|
|
14
|
+
yarn add shadcn-ui-react
|
|
17
15
|
```
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
### Tailwind IntelliSense (VS Code)
|
|
22
|
-
|
|
23
|
-
To enable Tailwind CSS IntelliSense to detect classes within `cn(...)` and other utilities:
|
|
17
|
+
### Peer dependencies
|
|
24
18
|
|
|
25
19
|
```bash
|
|
26
|
-
|
|
20
|
+
npm install react react-dom
|
|
27
21
|
```
|
|
28
22
|
|
|
29
23
|
---
|
|
30
24
|
|
|
31
25
|
## ๐ Getting Started
|
|
32
26
|
|
|
33
|
-
### 1. Import
|
|
27
|
+
### 1. Import global styles
|
|
34
28
|
|
|
35
|
-
In your `main.tsx
|
|
29
|
+
In your `main.tsx`, `App.tsx`, or layout file:
|
|
36
30
|
|
|
37
31
|
```tsx
|
|
38
|
-
import
|
|
32
|
+
import 'shadcn-ui-react/dist/style.css';
|
|
39
33
|
```
|
|
40
34
|
|
|
41
|
-
### 2. Use
|
|
35
|
+
### 2. Use components
|
|
42
36
|
|
|
43
37
|
```tsx
|
|
44
|
-
import { Button } from
|
|
38
|
+
import { Button } from 'shadcn-ui-react';
|
|
45
39
|
|
|
46
40
|
export default function App() {
|
|
47
41
|
return (
|
|
48
|
-
<div className="p-
|
|
49
|
-
<Button
|
|
50
|
-
<Button
|
|
51
|
-
|
|
42
|
+
<div className="p-6">
|
|
43
|
+
<Button>Save</Button>
|
|
44
|
+
<Button variant="outline" className="ml-2">
|
|
45
|
+
Cancel
|
|
52
46
|
</Button>
|
|
53
47
|
</div>
|
|
54
48
|
);
|
|
@@ -57,121 +51,108 @@ export default function App() {
|
|
|
57
51
|
|
|
58
52
|
---
|
|
59
53
|
|
|
60
|
-
##
|
|
61
|
-
|
|
62
|
-
### ๐ฆ UI Primitives
|
|
63
|
-
|
|
64
|
-
- `Accordion`
|
|
65
|
-
- `Alert`
|
|
66
|
-
- `Avatar`
|
|
67
|
-
- `Badge`
|
|
68
|
-
- `Breadcrumb`
|
|
69
|
-
- `Button`
|
|
70
|
-
- `Calendar`
|
|
71
|
-
- `Card`
|
|
72
|
-
- `Carousel`
|
|
73
|
-
- `Checkbox`
|
|
74
|
-
- `Collapsible`
|
|
75
|
-
- `Command`
|
|
76
|
-
- `ContextMenu`
|
|
77
|
-
- `Dialog`
|
|
78
|
-
- `Drawer`
|
|
79
|
-
- `DropdownMenu`
|
|
80
|
-
- `Dropzone`
|
|
81
|
-
- `HoverCard`
|
|
82
|
-
- `Input`
|
|
83
|
-
- `InputOtp`
|
|
84
|
-
- `Label`
|
|
85
|
-
- `Menubar`
|
|
86
|
-
- `Modal`
|
|
87
|
-
- `NavigationMenu`
|
|
88
|
-
- `Pagination`
|
|
89
|
-
- `Popover`
|
|
90
|
-
- `Progress`
|
|
91
|
-
- `RadioGroup`
|
|
92
|
-
- `Resizable`
|
|
93
|
-
- `ScrollArea`
|
|
94
|
-
- `Select`
|
|
95
|
-
- `Separator`
|
|
96
|
-
- `Sheet`
|
|
97
|
-
- `Skeleton`
|
|
98
|
-
- `Slider`
|
|
99
|
-
- `Sonner`
|
|
100
|
-
- `Switch`
|
|
101
|
-
- `Table`
|
|
102
|
-
- `Tabs`
|
|
103
|
-
- `Textarea`
|
|
104
|
-
- `Toast`
|
|
105
|
-
- `Toggle`
|
|
106
|
-
- `ToggleGroup`
|
|
107
|
-
- `Tooltip`
|
|
54
|
+
## ๐ญ Production scripts
|
|
108
55
|
|
|
109
|
-
|
|
56
|
+
```bash
|
|
57
|
+
npm run typecheck
|
|
58
|
+
npm run test
|
|
59
|
+
npm run build
|
|
60
|
+
```
|
|
110
61
|
|
|
111
|
-
|
|
62
|
+
The package is configured as an ESM library and exports compiled CSS:
|
|
112
63
|
|
|
113
|
-
|
|
114
|
-
-
|
|
64
|
+
```tsx
|
|
65
|
+
import 'shadcn-ui-react/dist/style.css';
|
|
66
|
+
import { Button, FormField, DataTable } from 'shadcn-ui-react';
|
|
67
|
+
```
|
|
115
68
|
|
|
116
69
|
---
|
|
117
70
|
|
|
118
|
-
|
|
71
|
+
## ๐งฑ Available components
|
|
119
72
|
|
|
120
|
-
|
|
73
|
+
### UI primitives
|
|
121
74
|
|
|
122
|
-
|
|
75
|
+
`Accordion`, `Alert`, `AlertDialog`, `Avatar`, `Badge`, `Breadcrumb`, `Button`, `IconButton`, `Calendar`, `Card`, `Carousel`, `Checkbox`, `Collapsible`, `Command`, `ContextMenu`, `Dialog`, `Drawer`, `DropdownMenu`, `HoverCard`, `Input`, `InputOtp`, `Label`, `Menubar`, `Modal`, `NavigationMenu`, `Pagination`, `Popover`, `Progress`, `RadioGroup`, `Resizable`, `ScrollArea`, `Select`, `Separator`, `Sheet`, `Skeleton`, `Slider`, `Sonner`, `Switch`, `Table`, `Tabs`, `Textarea`, `Toast`, `Toaster`, `Toggle`, `ToggleGroup`, `Tooltip`.
|
|
123
76
|
|
|
124
|
-
###
|
|
77
|
+
### Form helpers
|
|
125
78
|
|
|
126
|
-
|
|
127
|
-
- `Icons`
|
|
79
|
+
`Form`, `FormField`, `FormSelect`, `FormCheckbox`, `FormItem`, `FormControl`, `FormLabel`, `FormDescription`, `FormMessage`, `UiInput`, `UiSelect`, `UiCheckbox`.
|
|
128
80
|
|
|
129
|
-
|
|
81
|
+
### Shared components
|
|
82
|
+
|
|
83
|
+
`AlertModal`, `Breadcrumbs`, `DataTable`, `DataTableSkeleton`, `Dropzone`, `FileUpload`, `Heading`, `PageHead`, `PaginationSection`, `SearchInput`, `SearchableSelect`.
|
|
130
84
|
|
|
131
|
-
###
|
|
85
|
+
### Utilities
|
|
132
86
|
|
|
133
|
-
|
|
134
|
-
- `Breadcrumbs`
|
|
135
|
-
- `DataTable` (with pagination)
|
|
136
|
-
- `DataTableSkeleton`
|
|
137
|
-
- `FileUpload`
|
|
138
|
-
- `Heading`
|
|
139
|
-
- `PageHead`
|
|
140
|
-
- `PaginationSection`
|
|
87
|
+
`cn`, `useToast`, `toast`, `Icons`, `useSidebar`.
|
|
141
88
|
|
|
142
89
|
---
|
|
143
90
|
|
|
144
|
-
|
|
91
|
+
# โจ Demos
|
|
92
|
+
|
|
93
|
+
All demos use neutral sample data so they can be published in public documentation without referencing any private product, client, tenant, store, or internal business flow.
|
|
145
94
|
|
|
146
|
-
|
|
95
|
+
## 1. Button
|
|
147
96
|
|
|
148
97
|
```tsx
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
98
|
+
import { Button } from 'shadcn-ui-react';
|
|
99
|
+
|
|
100
|
+
export function ButtonDemo() {
|
|
101
|
+
return (
|
|
102
|
+
<div className="flex flex-wrap gap-3">
|
|
103
|
+
<Button>Default</Button>
|
|
104
|
+
<Button variant="outline">Outline</Button>
|
|
105
|
+
<Button variant="secondary">Secondary</Button>
|
|
106
|
+
<Button variant="destructive">Delete</Button>
|
|
107
|
+
<Button variant="soft">Soft</Button>
|
|
108
|
+
<Button variant="gradient">Gradient</Button>
|
|
109
|
+
<Button loading>Loading</Button>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
152
113
|
```
|
|
153
114
|
|
|
154
|
-
|
|
115
|
+
## 2. IconButton
|
|
155
116
|
|
|
156
|
-
|
|
117
|
+
```tsx
|
|
118
|
+
import { Pencil, Trash2 } from 'lucide-react';
|
|
119
|
+
import { IconButton } from 'shadcn-ui-react';
|
|
120
|
+
|
|
121
|
+
export function IconButtonDemo() {
|
|
122
|
+
return (
|
|
123
|
+
<div className="flex gap-2">
|
|
124
|
+
<IconButton aria-label="Edit item" variant="outline">
|
|
125
|
+
<Pencil />
|
|
126
|
+
</IconButton>
|
|
127
|
+
|
|
128
|
+
<IconButton aria-label="Delete item" variant="softDestructive">
|
|
129
|
+
<Trash2 />
|
|
130
|
+
</IconButton>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 3. UiInput
|
|
157
137
|
|
|
158
138
|
```tsx
|
|
159
|
-
import
|
|
139
|
+
import * as React from 'react';
|
|
140
|
+
import { UiInput } from 'shadcn-ui-react';
|
|
160
141
|
|
|
161
|
-
export
|
|
162
|
-
const [name, setName] = React.useState(
|
|
142
|
+
export function UiInputDemo() {
|
|
143
|
+
const [name, setName] = React.useState('');
|
|
163
144
|
|
|
164
145
|
return (
|
|
165
146
|
<UiInput
|
|
166
147
|
htmlFormItemId="name"
|
|
167
|
-
variant="outline"
|
|
168
148
|
label="Name"
|
|
169
|
-
|
|
149
|
+
requiredLabel
|
|
150
|
+
placeholder="Enter your name"
|
|
170
151
|
value={name}
|
|
171
|
-
onChange={(
|
|
152
|
+
onChange={(event) => setName(event.target.value)}
|
|
172
153
|
errorMessage={
|
|
173
154
|
name.length > 0 && name.trim().length < 2
|
|
174
|
-
?
|
|
155
|
+
? 'Minimum 2 characters'
|
|
175
156
|
: undefined
|
|
176
157
|
}
|
|
177
158
|
/>
|
|
@@ -179,330 +160,463 @@ export default function Example() {
|
|
|
179
160
|
}
|
|
180
161
|
```
|
|
181
162
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
### ๐งพ UiSelect
|
|
185
|
-
|
|
186
|
-
Using `items`:
|
|
163
|
+
## 4. UiSelect
|
|
187
164
|
|
|
188
165
|
```tsx
|
|
189
|
-
import
|
|
166
|
+
import * as React from 'react';
|
|
167
|
+
import { UiSelect } from 'shadcn-ui-react';
|
|
190
168
|
|
|
191
|
-
export
|
|
192
|
-
const [
|
|
169
|
+
export function UiSelectDemo() {
|
|
170
|
+
const [language, setLanguage] = React.useState('');
|
|
193
171
|
|
|
194
172
|
return (
|
|
195
173
|
<UiSelect
|
|
196
174
|
htmlFormItemId="language"
|
|
197
175
|
label="Language"
|
|
198
176
|
placeholder="Select a language"
|
|
199
|
-
value={
|
|
200
|
-
onChange={
|
|
201
|
-
variant="outline"
|
|
202
|
-
size="md"
|
|
177
|
+
value={language}
|
|
178
|
+
onChange={setLanguage}
|
|
203
179
|
items={[
|
|
204
|
-
{ label:
|
|
205
|
-
{ label:
|
|
206
|
-
{ label:
|
|
207
|
-
{ label:
|
|
208
|
-
{ label:
|
|
209
|
-
{ label: "Korean (disabled)", value: "ko", disabled: true },
|
|
180
|
+
{ label: 'English', value: 'en' },
|
|
181
|
+
{ label: 'Spanish', value: 'es' },
|
|
182
|
+
{ label: 'Portuguese', value: 'pt' },
|
|
183
|
+
{ label: 'French', value: 'fr' },
|
|
184
|
+
{ label: 'Japanese', value: 'ja', disabled: true }
|
|
210
185
|
]}
|
|
211
|
-
errorMessage={!
|
|
186
|
+
errorMessage={!language ? 'Select a language.' : undefined}
|
|
212
187
|
/>
|
|
213
188
|
);
|
|
214
189
|
}
|
|
215
190
|
```
|
|
216
191
|
|
|
217
|
-
|
|
192
|
+
## 5. UiCheckbox
|
|
218
193
|
|
|
219
194
|
```tsx
|
|
220
|
-
import
|
|
221
|
-
|
|
222
|
-
SelectGroup,
|
|
223
|
-
SelectItem,
|
|
224
|
-
SelectLabel,
|
|
225
|
-
SelectSeparator,
|
|
226
|
-
} from "shadcn-ui-react";
|
|
195
|
+
import * as React from 'react';
|
|
196
|
+
import { UiCheckbox } from 'shadcn-ui-react';
|
|
227
197
|
|
|
228
|
-
export
|
|
229
|
-
const [
|
|
198
|
+
export function UiCheckboxDemo() {
|
|
199
|
+
const [accepted, setAccepted] = React.useState(false);
|
|
230
200
|
|
|
231
201
|
return (
|
|
232
|
-
<
|
|
233
|
-
htmlFormItemId="
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
>
|
|
242
|
-
<SelectGroup>
|
|
243
|
-
<SelectLabel>Common</SelectLabel>
|
|
244
|
-
<SelectItem value="es">Spanish</SelectItem>
|
|
245
|
-
<SelectItem value="en">English</SelectItem>
|
|
246
|
-
<SelectItem value="pt">Portuguese</SelectItem>
|
|
247
|
-
|
|
248
|
-
<SelectSeparator />
|
|
249
|
-
|
|
250
|
-
<SelectLabel>Asia</SelectLabel>
|
|
251
|
-
<SelectItem value="zh">Chinese</SelectItem>
|
|
252
|
-
<SelectItem value="ja">Japanese</SelectItem>
|
|
253
|
-
<SelectItem value="ko" disabled>
|
|
254
|
-
Korean (disabled)
|
|
255
|
-
</SelectItem>
|
|
256
|
-
</SelectGroup>
|
|
257
|
-
</UiSelect>
|
|
202
|
+
<UiCheckbox
|
|
203
|
+
htmlFormItemId="terms"
|
|
204
|
+
checked={accepted}
|
|
205
|
+
onCheckedChange={(value) => setAccepted(value === true)}
|
|
206
|
+
label="I accept the terms and conditions"
|
|
207
|
+
description="You can change this option later from your account settings."
|
|
208
|
+
requiredLabel
|
|
209
|
+
errorMessage={!accepted ? 'You must accept the terms.' : undefined}
|
|
210
|
+
/>
|
|
258
211
|
);
|
|
259
212
|
}
|
|
260
213
|
```
|
|
261
214
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
### ๐ช Accordion
|
|
215
|
+
## 6. React Hook Form + Zod
|
|
265
216
|
|
|
266
217
|
```tsx
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<AccordionContent>Hidden content revealed!</AccordionContent>
|
|
271
|
-
</AccordionItem>
|
|
272
|
-
</Accordion>
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
### โ ๏ธ Alert
|
|
278
|
-
|
|
279
|
-
```tsx
|
|
280
|
-
<Alert>This is an alert message</Alert>
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
### ๐งพ Form
|
|
286
|
-
|
|
287
|
-
```tsx
|
|
288
|
-
import { zodResolver } from "@hookform/resolvers/zod";
|
|
289
|
-
import { useForm } from "react-hook-form";
|
|
218
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
219
|
+
import { useForm } from 'react-hook-form';
|
|
220
|
+
import { z } from 'zod';
|
|
290
221
|
import {
|
|
291
222
|
Button,
|
|
292
223
|
Form,
|
|
293
|
-
|
|
224
|
+
FormCheckbox,
|
|
294
225
|
FormField,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
pattern: z.regexes.email,
|
|
305
|
-
error: "Email is required",
|
|
306
|
-
}),
|
|
307
|
-
password: z
|
|
308
|
-
.string()
|
|
309
|
-
.min(8, { message: "Password must be at least 8 characters" }),
|
|
310
|
-
gender: z.enum(["male", "female"]).optional().nullable(),
|
|
226
|
+
FormSelect
|
|
227
|
+
} from 'shadcn-ui-react';
|
|
228
|
+
|
|
229
|
+
const schema = z.object({
|
|
230
|
+
name: z.string().min(2, 'Minimum 2 characters'),
|
|
231
|
+
email: z.string().email('Invalid email'),
|
|
232
|
+
password: z.string().min(8, 'Minimum 8 characters'),
|
|
233
|
+
role: z.enum(['owner', 'editor', 'viewer']),
|
|
234
|
+
accepted: z.boolean().refine(Boolean, 'You must accept the terms')
|
|
311
235
|
});
|
|
312
236
|
|
|
313
|
-
type
|
|
314
|
-
|
|
315
|
-
export
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
237
|
+
type FormValues = z.infer<typeof schema>;
|
|
238
|
+
|
|
239
|
+
export function FormDemo() {
|
|
240
|
+
const form = useForm<FormValues>({
|
|
241
|
+
resolver: zodResolver(schema),
|
|
242
|
+
defaultValues: {
|
|
243
|
+
name: '',
|
|
244
|
+
email: '',
|
|
245
|
+
password: '',
|
|
246
|
+
role: 'viewer',
|
|
247
|
+
accepted: false
|
|
248
|
+
}
|
|
323
249
|
});
|
|
324
250
|
|
|
325
|
-
const onSubmit =
|
|
251
|
+
const onSubmit = (values: FormValues) => {
|
|
252
|
+
console.log(values);
|
|
253
|
+
};
|
|
326
254
|
|
|
327
255
|
return (
|
|
328
|
-
<Form methods={form} onSubmit={onSubmit}>
|
|
256
|
+
<Form methods={form} onSubmit={onSubmit} formProps={{ className: 'space-y-4' }}>
|
|
257
|
+
<FormField
|
|
258
|
+
control={form.control}
|
|
259
|
+
name="name"
|
|
260
|
+
label="Name"
|
|
261
|
+
placeholder="Jane Doe"
|
|
262
|
+
requiredLabel
|
|
263
|
+
/>
|
|
264
|
+
|
|
329
265
|
<FormField
|
|
330
266
|
control={form.control}
|
|
331
267
|
name="email"
|
|
332
|
-
type="email"
|
|
333
|
-
placeholder="Enter your email"
|
|
334
268
|
label="Email"
|
|
335
|
-
|
|
336
|
-
|
|
269
|
+
type="email"
|
|
270
|
+
placeholder="jane@example.com"
|
|
271
|
+
requiredLabel
|
|
337
272
|
/>
|
|
338
273
|
|
|
339
274
|
<FormField
|
|
340
275
|
control={form.control}
|
|
341
276
|
name="password"
|
|
342
|
-
type="password"
|
|
343
|
-
placeholder="Enter password"
|
|
344
277
|
label="Password"
|
|
345
|
-
|
|
346
|
-
|
|
278
|
+
type="password"
|
|
279
|
+
placeholder="โขโขโขโขโขโขโขโข"
|
|
280
|
+
requiredLabel
|
|
347
281
|
/>
|
|
348
282
|
|
|
349
283
|
<FormSelect
|
|
350
284
|
control={form.control}
|
|
351
|
-
name="
|
|
352
|
-
label="
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
{ label:
|
|
357
|
-
{ label:
|
|
285
|
+
name="role"
|
|
286
|
+
label="Role"
|
|
287
|
+
placeholder="Select a role"
|
|
288
|
+
options={[
|
|
289
|
+
{ label: 'Owner', value: 'owner' },
|
|
290
|
+
{ label: 'Editor', value: 'editor' },
|
|
291
|
+
{ label: 'Viewer', value: 'viewer' }
|
|
358
292
|
]}
|
|
359
293
|
/>
|
|
360
294
|
|
|
361
|
-
<
|
|
362
|
-
|
|
295
|
+
<FormCheckbox
|
|
296
|
+
control={form.control}
|
|
297
|
+
name="accepted"
|
|
298
|
+
label="I accept the terms"
|
|
299
|
+
description="This field is required to continue."
|
|
300
|
+
/>
|
|
301
|
+
|
|
302
|
+
<Button type="submit" className="w-full">
|
|
303
|
+
Create account
|
|
363
304
|
</Button>
|
|
364
305
|
</Form>
|
|
365
306
|
);
|
|
366
307
|
}
|
|
367
308
|
```
|
|
368
309
|
|
|
369
|
-
|
|
310
|
+
## 7. SearchableSelect standalone
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import * as React from 'react';
|
|
314
|
+
import { SearchableSelect } from 'shadcn-ui-react';
|
|
315
|
+
|
|
316
|
+
export function SearchableSelectDemo() {
|
|
317
|
+
const [fruit, setFruit] = React.useState('');
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<SearchableSelect
|
|
321
|
+
value={fruit}
|
|
322
|
+
onValueChange={setFruit}
|
|
323
|
+
placeholder="Select a fruit"
|
|
324
|
+
searchPlaceholder="Search fruit..."
|
|
325
|
+
items={[
|
|
326
|
+
{ label: 'Apple', value: 'apple', keywords: 'fruit red green sweet' },
|
|
327
|
+
{ label: 'Banana', value: 'banana', keywords: 'fruit yellow tropical' },
|
|
328
|
+
{ label: 'Orange', value: 'orange', keywords: 'fruit citrus vitamin c' },
|
|
329
|
+
{ label: 'Strawberry', value: 'strawberry', keywords: 'fruit berry red sweet' },
|
|
330
|
+
{ label: 'Pineapple', value: 'pineapple', keywords: 'fruit tropical sweet' }
|
|
331
|
+
]}
|
|
332
|
+
/>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## 8. Accordion
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
import {
|
|
341
|
+
Accordion,
|
|
342
|
+
AccordionContent,
|
|
343
|
+
AccordionItem,
|
|
344
|
+
AccordionTrigger
|
|
345
|
+
} from 'shadcn-ui-react';
|
|
346
|
+
|
|
347
|
+
export function AccordionDemo() {
|
|
348
|
+
return (
|
|
349
|
+
<Accordion type="single" collapsible className="w-full">
|
|
350
|
+
<AccordionItem value="item-1">
|
|
351
|
+
<AccordionTrigger>What is included?</AccordionTrigger>
|
|
352
|
+
<AccordionContent>
|
|
353
|
+
Accessible components, Tailwind styling, and reusable form helpers.
|
|
354
|
+
</AccordionContent>
|
|
355
|
+
</AccordionItem>
|
|
356
|
+
</Accordion>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## 9. Dialog
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
import {
|
|
365
|
+
Button,
|
|
366
|
+
Dialog,
|
|
367
|
+
DialogContent,
|
|
368
|
+
DialogHeader,
|
|
369
|
+
DialogTitle,
|
|
370
|
+
DialogTrigger
|
|
371
|
+
} from 'shadcn-ui-react';
|
|
372
|
+
|
|
373
|
+
export function DialogDemo() {
|
|
374
|
+
return (
|
|
375
|
+
<Dialog>
|
|
376
|
+
<DialogTrigger asChild>
|
|
377
|
+
<Button>Open dialog</Button>
|
|
378
|
+
</DialogTrigger>
|
|
379
|
+
|
|
380
|
+
<DialogContent>
|
|
381
|
+
<DialogHeader>
|
|
382
|
+
<DialogTitle>Create note</DialogTitle>
|
|
383
|
+
</DialogHeader>
|
|
384
|
+
|
|
385
|
+
<p className="text-sm text-muted-foreground">
|
|
386
|
+
Use this area for a form, message, or confirmation content.
|
|
387
|
+
</p>
|
|
388
|
+
</DialogContent>
|
|
389
|
+
</Dialog>
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## 10. AlertModal
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
import * as React from 'react';
|
|
398
|
+
import { AlertModal, Button } from 'shadcn-ui-react';
|
|
399
|
+
|
|
400
|
+
export function AlertModalDemo() {
|
|
401
|
+
const [open, setOpen] = React.useState(false);
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<>
|
|
405
|
+
<Button variant="destructive" onClick={() => setOpen(true)}>
|
|
406
|
+
Delete item
|
|
407
|
+
</Button>
|
|
408
|
+
|
|
409
|
+
<AlertModal
|
|
410
|
+
isOpen={open}
|
|
411
|
+
onClose={() => setOpen(false)}
|
|
412
|
+
onConfirm={() => setOpen(false)}
|
|
413
|
+
loading={false}
|
|
414
|
+
/>
|
|
415
|
+
</>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
```
|
|
370
419
|
|
|
371
|
-
|
|
420
|
+
## 11. DataTable
|
|
372
421
|
|
|
373
422
|
```tsx
|
|
374
|
-
import
|
|
375
|
-
import type { ColumnDef } from
|
|
376
|
-
import { DataTable } from
|
|
423
|
+
import * as React from 'react';
|
|
424
|
+
import type { ColumnDef } from '@tanstack/react-table';
|
|
425
|
+
import { DataTable } from 'shadcn-ui-react';
|
|
377
426
|
|
|
378
|
-
type
|
|
427
|
+
type Project = {
|
|
379
428
|
id: number;
|
|
380
429
|
name: string;
|
|
381
|
-
|
|
382
|
-
|
|
430
|
+
owner: string;
|
|
431
|
+
status: 'Active' | 'Paused' | 'Completed';
|
|
383
432
|
};
|
|
384
433
|
|
|
385
|
-
const columns: ColumnDef<
|
|
386
|
-
{ accessorKey:
|
|
387
|
-
{
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
cell: ({ row }) => (
|
|
391
|
-
<div className="flex items-center gap-2">
|
|
392
|
-
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
393
|
-
<span className="font-medium">{row.original.name}</span>
|
|
394
|
-
</div>
|
|
395
|
-
),
|
|
396
|
-
},
|
|
397
|
-
{ accessorKey: "email", header: "Email" },
|
|
398
|
-
{
|
|
399
|
-
accessorKey: "role",
|
|
400
|
-
header: "Role",
|
|
401
|
-
cell: ({ row }) => (
|
|
402
|
-
<span className="inline-flex items-center rounded-full border px-2 py-0.5 text-xs">
|
|
403
|
-
{row.original.role}
|
|
404
|
-
</span>
|
|
405
|
-
),
|
|
406
|
-
},
|
|
434
|
+
const columns: ColumnDef<Project>[] = [
|
|
435
|
+
{ accessorKey: 'id', header: 'ID' },
|
|
436
|
+
{ accessorKey: 'name', header: 'Project' },
|
|
437
|
+
{ accessorKey: 'owner', header: 'Owner' },
|
|
438
|
+
{ accessorKey: 'status', header: 'Status' }
|
|
407
439
|
];
|
|
408
440
|
|
|
409
|
-
const data:
|
|
410
|
-
{ id: 1, name:
|
|
411
|
-
{ id: 2, name:
|
|
412
|
-
{ id: 3, name:
|
|
413
|
-
{ id: 4, name: "Alice Brown", email: "alice@example.com", role: "Editor" },
|
|
414
|
-
{ id: 5, name: "Bob White", email: "bob@example.com", role: "Viewer" },
|
|
415
|
-
{ id: 6, name: "Charlie Black", email: "charlie@example.com", role: "Admin" },
|
|
416
|
-
{ id: 7, name: "Diana Green", email: "diana@example.com", role: "Viewer" },
|
|
417
|
-
{ id: 8, name: "Eve Blue", email: "eve@example.com", role: "Editor" },
|
|
418
|
-
{ id: 9, name: "Frank Yellow", email: "frank@example.com", role: "Viewer" },
|
|
419
|
-
{ id: 10, name: "Grace Red", email: "grace@example.com", role: "Admin" },
|
|
441
|
+
const data: Project[] = [
|
|
442
|
+
{ id: 1, name: 'Website Redesign', owner: 'Jane Cooper', status: 'Active' },
|
|
443
|
+
{ id: 2, name: 'Mobile App', owner: 'Wade Warren', status: 'Paused' },
|
|
444
|
+
{ id: 3, name: 'Design System', owner: 'Esther Howard', status: 'Completed' }
|
|
420
445
|
];
|
|
421
446
|
|
|
422
|
-
|
|
423
|
-
const [page, setPage] = useState(1);
|
|
424
|
-
const [perPage, setPerPage] = useState(5);
|
|
447
|
+
export function DataTableDemo() {
|
|
448
|
+
const [page, setPage] = React.useState(1);
|
|
449
|
+
const [perPage, setPerPage] = React.useState(5);
|
|
450
|
+
const pageCount = Math.ceil(data.length / perPage);
|
|
451
|
+
const visibleRows = data.slice((page - 1) * perPage, page * perPage);
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<DataTable
|
|
455
|
+
columns={columns}
|
|
456
|
+
data={visibleRows}
|
|
457
|
+
page={page}
|
|
458
|
+
perPage={perPage}
|
|
459
|
+
pageCount={pageCount}
|
|
460
|
+
totalRows={data.length}
|
|
461
|
+
onPageChange={setPage}
|
|
462
|
+
onPageSizeChange={(size) => {
|
|
463
|
+
setPerPage(size);
|
|
464
|
+
setPage(1);
|
|
465
|
+
}}
|
|
466
|
+
template="neo"
|
|
467
|
+
accent="primary"
|
|
468
|
+
stickyHeader
|
|
469
|
+
animate
|
|
470
|
+
heightClassName="h-[420px]"
|
|
471
|
+
/>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## 12. SearchInput
|
|
425
477
|
|
|
426
|
-
|
|
478
|
+
```tsx
|
|
479
|
+
import * as React from 'react';
|
|
480
|
+
import { SearchInput } from 'shadcn-ui-react';
|
|
427
481
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const end = page * perPage;
|
|
431
|
-
return data.slice(start, end);
|
|
432
|
-
}, [page, perPage]);
|
|
482
|
+
export function SearchInputDemo() {
|
|
483
|
+
const [search, setSearch] = React.useState('');
|
|
433
484
|
|
|
434
485
|
return (
|
|
435
|
-
<
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
columns={columns}
|
|
445
|
-
data={paginatedData}
|
|
446
|
-
pageCount={pageCount}
|
|
447
|
-
page={page}
|
|
448
|
-
perPage={perPage}
|
|
449
|
-
onPageChange={setPage}
|
|
450
|
-
onPageSizeChange={(size) => {
|
|
451
|
-
setPerPage(size);
|
|
452
|
-
setPage(1);
|
|
453
|
-
}}
|
|
454
|
-
totalRows={data.length}
|
|
455
|
-
isRowsSelected={false}
|
|
456
|
-
rowPerPageLabel="Rows per page"
|
|
457
|
-
pageLabel="Page"
|
|
458
|
-
ofLabel="of"
|
|
459
|
-
rowsSelectedLabel="rows selected"
|
|
460
|
-
emptyData={<div className="py-10 text-center">No data available</div>}
|
|
461
|
-
|
|
462
|
-
// โจ NEW (v0.6.6)
|
|
463
|
-
template="neo" // neo | glass | compact | minimal | clean | elevated | grid | cards
|
|
464
|
-
accent="primary" // primary | emerald | indigo | rose | amber | zinc
|
|
465
|
-
stickyHeader={true}
|
|
466
|
-
headerScroll={false}
|
|
467
|
-
animate={true}
|
|
468
|
-
heightClassName="h-[420px]" // Important: Define the height for the ScrollArea to work.
|
|
469
|
-
|
|
470
|
-
onClick={(row) => {
|
|
471
|
-
// demo: click in row
|
|
472
|
-
console.log("Row clicked:", row);
|
|
473
|
-
}}
|
|
486
|
+
<SearchInput
|
|
487
|
+
value={search}
|
|
488
|
+
placeholder="Search documents..."
|
|
489
|
+
debounceTime={400}
|
|
490
|
+
onSearch={(value) => setSearch(value ?? '')}
|
|
491
|
+
/>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
```
|
|
474
495
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
496
|
+
## 13. FileUpload / Dropzone
|
|
497
|
+
|
|
498
|
+
```tsx
|
|
499
|
+
import { Dropzone } from 'shadcn-ui-react';
|
|
500
|
+
|
|
501
|
+
export function DropzoneDemo() {
|
|
502
|
+
return (
|
|
503
|
+
<Dropzone
|
|
504
|
+
onDrop={(files) => {
|
|
505
|
+
console.log(files);
|
|
506
|
+
}}
|
|
507
|
+
/>
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## 14. Toast
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
import { Button, Toaster, useToast } from 'shadcn-ui-react';
|
|
516
|
+
|
|
517
|
+
export function ToastDemo() {
|
|
518
|
+
const { toast } = useToast();
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<>
|
|
522
|
+
<Button
|
|
523
|
+
onClick={() => {
|
|
524
|
+
toast({
|
|
525
|
+
title: 'Saved',
|
|
526
|
+
description: 'Your changes were saved successfully.'
|
|
527
|
+
});
|
|
480
528
|
}}
|
|
481
|
-
|
|
482
|
-
|
|
529
|
+
>
|
|
530
|
+
Show toast
|
|
531
|
+
</Button>
|
|
532
|
+
|
|
533
|
+
<Toaster />
|
|
534
|
+
</>
|
|
483
535
|
);
|
|
484
|
-
}
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## 15. Sonner
|
|
540
|
+
|
|
541
|
+
```tsx
|
|
542
|
+
import { toast } from 'sonner';
|
|
543
|
+
import { Button, ToasterSonner } from 'shadcn-ui-react';
|
|
485
544
|
|
|
486
|
-
export
|
|
545
|
+
export function SonnerDemo() {
|
|
546
|
+
return (
|
|
547
|
+
<>
|
|
548
|
+
<Button onClick={() => toast.success('Operation completed')}>
|
|
549
|
+
Notify
|
|
550
|
+
</Button>
|
|
551
|
+
<ToasterSonner />
|
|
552
|
+
</>
|
|
553
|
+
);
|
|
554
|
+
}
|
|
487
555
|
```
|
|
488
556
|
|
|
489
|
-
|
|
557
|
+
## 16. Card + Badge
|
|
490
558
|
|
|
491
|
-
|
|
559
|
+
```tsx
|
|
560
|
+
import {
|
|
561
|
+
Badge,
|
|
562
|
+
Card,
|
|
563
|
+
CardContent,
|
|
564
|
+
CardHeader,
|
|
565
|
+
CardTitle
|
|
566
|
+
} from 'shadcn-ui-react';
|
|
567
|
+
|
|
568
|
+
export function CardDemo() {
|
|
569
|
+
return (
|
|
570
|
+
<Card className="max-w-md">
|
|
571
|
+
<CardHeader>
|
|
572
|
+
<CardTitle className="flex items-center justify-between">
|
|
573
|
+
Starter Kit
|
|
574
|
+
<Badge>New</Badge>
|
|
575
|
+
</CardTitle>
|
|
576
|
+
</CardHeader>
|
|
577
|
+
<CardContent className="text-sm text-muted-foreground">
|
|
578
|
+
A simple card example with a title, badge, and supporting description.
|
|
579
|
+
</CardContent>
|
|
580
|
+
</Card>
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
```
|
|
492
584
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
585
|
+
## 17. Tabs
|
|
586
|
+
|
|
587
|
+
```tsx
|
|
588
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'shadcn-ui-react';
|
|
589
|
+
|
|
590
|
+
export function TabsDemo() {
|
|
591
|
+
return (
|
|
592
|
+
<Tabs defaultValue="overview">
|
|
593
|
+
<TabsList>
|
|
594
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
595
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
596
|
+
</TabsList>
|
|
597
|
+
<TabsContent value="overview">General information goes here.</TabsContent>
|
|
598
|
+
<TabsContent value="settings">Preferences and configuration go here.</TabsContent>
|
|
599
|
+
</Tabs>
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
```
|
|
496
603
|
|
|
497
604
|
---
|
|
498
605
|
|
|
499
|
-
##
|
|
606
|
+
## ๐
Theming
|
|
607
|
+
|
|
608
|
+
This package uses Tailwind CSS variables. You can override the default theme from your app CSS:
|
|
500
609
|
|
|
501
|
-
|
|
502
|
-
|
|
610
|
+
```css
|
|
611
|
+
:root {
|
|
612
|
+
--primary: oklch(0.45 0.18 260);
|
|
613
|
+
--primary-foreground: oklch(0.98 0 0);
|
|
614
|
+
--radius: 0.75rem;
|
|
615
|
+
}
|
|
616
|
+
```
|
|
503
617
|
|
|
504
618
|
---
|
|
505
619
|
|
|
506
620
|
## ๐ License
|
|
507
621
|
|
|
508
|
-
|
|
622
|
+
MIT.
|