@organcli/composed-cli 0.1.2 → 0.1.3
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/dist/index.js +1356 -1241
- package/dist/registry-bundle.json +1543 -1508
- package/package.json +1 -1
|
@@ -1,1509 +1,1544 @@
|
|
|
1
|
-
{
|
|
2
|
-
"registry": [
|
|
3
|
-
{
|
|
4
|
-
"name": "faq-accordion",
|
|
5
|
-
"type": "composed",
|
|
6
|
-
"slug": "accordion",
|
|
7
|
-
"files": [
|
|
8
|
-
{
|
|
9
|
-
"path": "src/components/ui/composed/accordion/faq-accordion.tsx",
|
|
10
|
-
"exports": [
|
|
11
|
-
"FaqAccordion"
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
|
-
"dependencies": {
|
|
16
|
-
"shadcn": [
|
|
17
|
-
"accordion"
|
|
18
|
-
],
|
|
19
|
-
"npm": [],
|
|
20
|
-
"local": [
|
|
21
|
-
"@/lib/utils"
|
|
22
|
-
]
|
|
23
|
-
},
|
|
24
|
-
"i18n": {
|
|
25
|
-
"required": false,
|
|
26
|
-
"keys": []
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"name": "error-alert",
|
|
31
|
-
"type": "composed",
|
|
32
|
-
"slug": "alert",
|
|
33
|
-
"files": [
|
|
34
|
-
{
|
|
35
|
-
"path": "src/components/ui/composed/alert/error-alert.tsx",
|
|
36
|
-
"exports": [
|
|
37
|
-
"ErrorAlert"
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"shadcn": [
|
|
43
|
-
"alert"
|
|
44
|
-
],
|
|
45
|
-
"npm": [
|
|
46
|
-
"lucide-react"
|
|
47
|
-
],
|
|
48
|
-
"local": [
|
|
49
|
-
"@/lib/utils"
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
"i18n": {
|
|
53
|
-
"required": false,
|
|
54
|
-
"keys": []
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"name": "user-avatar",
|
|
59
|
-
"type": "composed",
|
|
60
|
-
"slug": "avatar",
|
|
61
|
-
"files": [
|
|
62
|
-
{
|
|
63
|
-
"path": "src/components/ui/composed/avatar/user-avatar.tsx",
|
|
64
|
-
"exports": [
|
|
65
|
-
"UserAvatar"
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
],
|
|
69
|
-
"dependencies": {
|
|
70
|
-
"shadcn": [
|
|
71
|
-
"avatar"
|
|
72
|
-
],
|
|
73
|
-
"npm": [],
|
|
74
|
-
"local": [
|
|
75
|
-
"@/lib/utils"
|
|
76
|
-
]
|
|
77
|
-
},
|
|
78
|
-
"i18n": {
|
|
79
|
-
"required": false,
|
|
80
|
-
"keys": []
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"name": "status-badge",
|
|
85
|
-
"type": "composed",
|
|
86
|
-
"slug": "badge",
|
|
87
|
-
"files": [
|
|
88
|
-
{
|
|
89
|
-
"path": "src/components/ui/composed/badge/status-badge.tsx",
|
|
90
|
-
"exports": [
|
|
91
|
-
"StatusBadge"
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
],
|
|
95
|
-
"dependencies": {
|
|
96
|
-
"shadcn": [
|
|
97
|
-
"badge"
|
|
98
|
-
],
|
|
99
|
-
"npm": [],
|
|
100
|
-
"local": [
|
|
101
|
-
"@/lib/utils"
|
|
102
|
-
]
|
|
103
|
-
},
|
|
104
|
-
"i18n": {
|
|
105
|
-
"required": false,
|
|
106
|
-
"keys": []
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"name": "loading-button",
|
|
111
|
-
"type": "composed",
|
|
112
|
-
"slug": "button",
|
|
113
|
-
"files": [
|
|
114
|
-
{
|
|
115
|
-
"path": "src/components/ui/composed/button/loading-button.tsx",
|
|
116
|
-
"exports": [
|
|
117
|
-
"LoadingButton"
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
],
|
|
121
|
-
"dependencies": {
|
|
122
|
-
"shadcn": [
|
|
123
|
-
"button"
|
|
124
|
-
],
|
|
125
|
-
"npm": [
|
|
126
|
-
"lucide-react"
|
|
127
|
-
],
|
|
128
|
-
"local": []
|
|
129
|
-
},
|
|
130
|
-
"i18n": {
|
|
131
|
-
"required": false,
|
|
132
|
-
"keys": []
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
"name": "theme-toggle",
|
|
137
|
-
"type": "composed",
|
|
138
|
-
"slug": "button",
|
|
139
|
-
"files": [
|
|
140
|
-
{
|
|
141
|
-
"path": "src/components/ui/composed/button/theme-toggle.tsx",
|
|
142
|
-
"exports": [
|
|
143
|
-
"ThemeToggle"
|
|
144
|
-
]
|
|
145
|
-
}
|
|
146
|
-
],
|
|
147
|
-
"dependencies": {
|
|
148
|
-
"shadcn": [
|
|
149
|
-
"button"
|
|
150
|
-
],
|
|
151
|
-
"npm": [
|
|
152
|
-
"lucide-react",
|
|
153
|
-
"next-themes"
|
|
154
|
-
],
|
|
155
|
-
"local": []
|
|
156
|
-
},
|
|
157
|
-
"i18n": {
|
|
158
|
-
"required": true,
|
|
159
|
-
"keys": [
|
|
160
|
-
"ui_toggle_theme"
|
|
161
|
-
]
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
"name": "stats-card",
|
|
166
|
-
"type": "composed",
|
|
167
|
-
"slug": "card",
|
|
168
|
-
"files": [
|
|
169
|
-
{
|
|
170
|
-
"path": "src/components/ui/composed/card/stats-card.tsx",
|
|
171
|
-
"exports": [
|
|
172
|
-
"StatsCard"
|
|
173
|
-
]
|
|
174
|
-
}
|
|
175
|
-
],
|
|
176
|
-
"dependencies": {
|
|
177
|
-
"shadcn": [
|
|
178
|
-
"card"
|
|
179
|
-
],
|
|
180
|
-
"npm": [
|
|
181
|
-
"lucide-react"
|
|
182
|
-
],
|
|
183
|
-
"local": [
|
|
184
|
-
"@/lib/utils"
|
|
185
|
-
]
|
|
186
|
-
},
|
|
187
|
-
"i18n": {
|
|
188
|
-
"required": false,
|
|
189
|
-
"keys": []
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
"name": "media-carousel",
|
|
194
|
-
"type": "composed",
|
|
195
|
-
"slug": "carousel",
|
|
196
|
-
"files": [
|
|
197
|
-
{
|
|
198
|
-
"path": "src/components/ui/composed/carousel/media-carousel.tsx",
|
|
199
|
-
"exports": [
|
|
200
|
-
"MediaCarousel"
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
],
|
|
204
|
-
"dependencies": {
|
|
205
|
-
"shadcn": [
|
|
206
|
-
"carousel",
|
|
207
|
-
"button"
|
|
208
|
-
],
|
|
209
|
-
"npm": [
|
|
210
|
-
"embla-carousel-react",
|
|
211
|
-
"lucide-react"
|
|
212
|
-
],
|
|
213
|
-
"local": [
|
|
214
|
-
"@/lib/utils"
|
|
215
|
-
]
|
|
216
|
-
},
|
|
217
|
-
"i18n": {
|
|
218
|
-
"required": false,
|
|
219
|
-
"keys": []
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
"name": "checkbox-field",
|
|
224
|
-
"type": "composed",
|
|
225
|
-
"slug": "checkbox",
|
|
226
|
-
"files": [
|
|
227
|
-
{
|
|
228
|
-
"path": "src/components/ui/composed/checkbox/checkbox-field.tsx",
|
|
229
|
-
"exports": [
|
|
230
|
-
"CheckboxField"
|
|
231
|
-
]
|
|
232
|
-
}
|
|
233
|
-
],
|
|
234
|
-
"dependencies": {
|
|
235
|
-
"shadcn": [
|
|
236
|
-
"checkbox",
|
|
237
|
-
"label"
|
|
238
|
-
],
|
|
239
|
-
"npm": [],
|
|
240
|
-
"local": [
|
|
241
|
-
"@/lib/utils"
|
|
242
|
-
]
|
|
243
|
-
},
|
|
244
|
-
"i18n": {
|
|
245
|
-
"required": false,
|
|
246
|
-
"keys": []
|
|
247
|
-
},
|
|
248
|
-
"controller": "CheckboxController"
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
"name": "checkbox-group",
|
|
252
|
-
"type": "composed",
|
|
253
|
-
"slug": "checkbox",
|
|
254
|
-
"files": [
|
|
255
|
-
{
|
|
256
|
-
"path": "src/components/ui/composed/checkbox/checkbox-group.tsx",
|
|
257
|
-
"exports": [
|
|
258
|
-
"CheckboxGroup"
|
|
259
|
-
]
|
|
260
|
-
}
|
|
261
|
-
],
|
|
262
|
-
"dependencies": {
|
|
263
|
-
"shadcn": [
|
|
264
|
-
"checkbox",
|
|
265
|
-
"label"
|
|
266
|
-
],
|
|
267
|
-
"npm": [],
|
|
268
|
-
"local": [
|
|
269
|
-
"@/lib/utils"
|
|
270
|
-
]
|
|
271
|
-
},
|
|
272
|
-
"i18n": {
|
|
273
|
-
"required": false,
|
|
274
|
-
"keys": []
|
|
275
|
-
},
|
|
276
|
-
"controller": "CheckboxGroupController"
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
"name": "searchable-select",
|
|
280
|
-
"type": "composed",
|
|
281
|
-
"slug": "combobox",
|
|
282
|
-
"files": [
|
|
283
|
-
{
|
|
284
|
-
"path": "src/components/ui/composed/combobox/searchable-select.tsx",
|
|
285
|
-
"exports": [
|
|
286
|
-
"SearchableSelect"
|
|
287
|
-
]
|
|
288
|
-
}
|
|
289
|
-
],
|
|
290
|
-
"dependencies": {
|
|
291
|
-
"shadcn": [
|
|
292
|
-
"command",
|
|
293
|
-
"popover",
|
|
294
|
-
"button",
|
|
295
|
-
"label",
|
|
296
|
-
"dialog",
|
|
297
|
-
"input-group"
|
|
298
|
-
],
|
|
299
|
-
"npm": [
|
|
300
|
-
"cmdk",
|
|
301
|
-
"lucide-react"
|
|
302
|
-
],
|
|
303
|
-
"local": [
|
|
304
|
-
"@/lib/utils"
|
|
305
|
-
]
|
|
306
|
-
},
|
|
307
|
-
"i18n": {
|
|
308
|
-
"required": true,
|
|
309
|
-
"keys": [
|
|
310
|
-
"ui_no_results",
|
|
311
|
-
"ui_search_placeholder",
|
|
312
|
-
"ui_select_placeholder"
|
|
313
|
-
]
|
|
314
|
-
},
|
|
315
|
-
"controller": "ComboboxController"
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
"name": "date-picker-field",
|
|
319
|
-
"type": "composed",
|
|
320
|
-
"slug": "date-picker",
|
|
321
|
-
"files": [
|
|
322
|
-
{
|
|
323
|
-
"path": "src/components/ui/composed/date-picker/date-picker-field.tsx",
|
|
324
|
-
"exports": [
|
|
325
|
-
"DatePickerField"
|
|
326
|
-
]
|
|
327
|
-
}
|
|
328
|
-
],
|
|
329
|
-
"dependencies": {
|
|
330
|
-
"shadcn": [
|
|
331
|
-
"calendar",
|
|
332
|
-
"button",
|
|
333
|
-
"label",
|
|
334
|
-
"popover"
|
|
335
|
-
],
|
|
336
|
-
"npm": [
|
|
337
|
-
"react-day-picker",
|
|
338
|
-
"lucide-react"
|
|
339
|
-
],
|
|
340
|
-
"local": [
|
|
341
|
-
"@/lib/utils"
|
|
342
|
-
]
|
|
343
|
-
},
|
|
344
|
-
"i18n": {
|
|
345
|
-
"required": true,
|
|
346
|
-
"keys": [
|
|
347
|
-
"ui_pick_date"
|
|
348
|
-
]
|
|
349
|
-
},
|
|
350
|
-
"controller": "DatePickerController"
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
"name": "confirmation-dialog",
|
|
354
|
-
"type": "composed",
|
|
355
|
-
"slug": "dialog",
|
|
356
|
-
"files": [
|
|
357
|
-
{
|
|
358
|
-
"path": "src/components/ui/composed/dialog/confirmation-dialog.tsx",
|
|
359
|
-
"exports": [
|
|
360
|
-
"ConfirmationDialog"
|
|
361
|
-
]
|
|
362
|
-
}
|
|
363
|
-
],
|
|
364
|
-
"dependencies": {
|
|
365
|
-
"shadcn": [
|
|
366
|
-
"dialog",
|
|
367
|
-
"button"
|
|
368
|
-
],
|
|
369
|
-
"npm": [
|
|
370
|
-
"lucide-react"
|
|
371
|
-
],
|
|
372
|
-
"local": []
|
|
373
|
-
},
|
|
374
|
-
"i18n": {
|
|
375
|
-
"required": true,
|
|
376
|
-
"keys": [
|
|
377
|
-
"ui_cancel",
|
|
378
|
-
"ui_confirm"
|
|
379
|
-
]
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
"name": "action-drawer",
|
|
384
|
-
"type": "composed",
|
|
385
|
-
"slug": "drawer",
|
|
386
|
-
"files": [
|
|
387
|
-
{
|
|
388
|
-
"path": "src/components/ui/composed/drawer/action-drawer.tsx",
|
|
389
|
-
"exports": [
|
|
390
|
-
"ActionDrawer"
|
|
391
|
-
]
|
|
392
|
-
}
|
|
393
|
-
],
|
|
394
|
-
"dependencies": {
|
|
395
|
-
"shadcn": [
|
|
396
|
-
"drawer"
|
|
397
|
-
],
|
|
398
|
-
"npm": [
|
|
399
|
-
"vaul"
|
|
400
|
-
],
|
|
401
|
-
"local": []
|
|
402
|
-
},
|
|
403
|
-
"i18n": {
|
|
404
|
-
"required": false,
|
|
405
|
-
"keys": []
|
|
406
|
-
}
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
"name": "actions-dropdown",
|
|
410
|
-
"type": "composed",
|
|
411
|
-
"slug": "dropdown-menu",
|
|
412
|
-
"files": [
|
|
413
|
-
{
|
|
414
|
-
"path": "src/components/ui/composed/dropdown-menu/actions-dropdown.tsx",
|
|
415
|
-
"exports": [
|
|
416
|
-
"ActionsDropdown"
|
|
417
|
-
]
|
|
418
|
-
}
|
|
419
|
-
],
|
|
420
|
-
"dependencies": {
|
|
421
|
-
"shadcn": [
|
|
422
|
-
"dropdown-menu",
|
|
423
|
-
"button"
|
|
424
|
-
],
|
|
425
|
-
"npm": [
|
|
426
|
-
"lucide-react"
|
|
427
|
-
],
|
|
428
|
-
"local": []
|
|
429
|
-
},
|
|
430
|
-
"i18n": {
|
|
431
|
-
"required": true,
|
|
432
|
-
"keys": [
|
|
433
|
-
"ui_open_actions"
|
|
434
|
-
]
|
|
435
|
-
}
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
"name": "empty-state",
|
|
439
|
-
"type": "composed",
|
|
440
|
-
"slug": "empty",
|
|
441
|
-
"files": [
|
|
442
|
-
{
|
|
443
|
-
"path": "src/components/ui/composed/empty/empty-state.tsx",
|
|
444
|
-
"exports": [
|
|
445
|
-
"EmptyState"
|
|
446
|
-
]
|
|
447
|
-
}
|
|
448
|
-
],
|
|
449
|
-
"dependencies": {
|
|
450
|
-
"shadcn": [
|
|
451
|
-
"empty"
|
|
452
|
-
],
|
|
453
|
-
"npm": [],
|
|
454
|
-
"local": [
|
|
455
|
-
"@/lib/utils"
|
|
456
|
-
]
|
|
457
|
-
},
|
|
458
|
-
"i18n": {
|
|
459
|
-
"required": false,
|
|
460
|
-
"keys": []
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
"name": "labeled-input",
|
|
465
|
-
"type": "composed",
|
|
466
|
-
"slug": "input",
|
|
467
|
-
"files": [
|
|
468
|
-
{
|
|
469
|
-
"path": "src/components/ui/composed/input/labeled-input.tsx",
|
|
470
|
-
"exports": [
|
|
471
|
-
"LabeledInput"
|
|
472
|
-
]
|
|
473
|
-
}
|
|
474
|
-
],
|
|
475
|
-
"dependencies": {
|
|
476
|
-
"shadcn": [
|
|
477
|
-
"input",
|
|
478
|
-
"label"
|
|
479
|
-
],
|
|
480
|
-
"npm": [],
|
|
481
|
-
"local": [
|
|
482
|
-
"@/lib/utils"
|
|
483
|
-
]
|
|
484
|
-
},
|
|
485
|
-
"i18n": {
|
|
486
|
-
"required": false,
|
|
487
|
-
"keys": []
|
|
488
|
-
},
|
|
489
|
-
"controller": "InputFieldController",
|
|
490
|
-
"knownIssues": [
|
|
491
|
-
"labeled-input.tsx imports `type InputProps` from '@/components/ui/input', but src/components/ui/input.tsx only exports `Input` (no InputProps type). This is a pre-existing type error in the boilerplate itself (reproduced by `bun run type-check`), not something introduced by the registry. A CLI copying this component will carry the same error until the source file is fixed upstream."
|
|
492
|
-
]
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
"name": "search-input",
|
|
496
|
-
"type": "composed",
|
|
497
|
-
"slug": "input-group",
|
|
498
|
-
"files": [
|
|
499
|
-
{
|
|
500
|
-
"path": "src/components/ui/composed/input-group/search-input.tsx",
|
|
501
|
-
"exports": [
|
|
502
|
-
"SearchInput"
|
|
503
|
-
]
|
|
504
|
-
}
|
|
505
|
-
],
|
|
506
|
-
"dependencies": {
|
|
507
|
-
"shadcn": [
|
|
508
|
-
"input-group",
|
|
509
|
-
"button",
|
|
510
|
-
"input",
|
|
511
|
-
"textarea"
|
|
512
|
-
],
|
|
513
|
-
"npm": [
|
|
514
|
-
"lucide-react"
|
|
515
|
-
],
|
|
516
|
-
"local": [
|
|
517
|
-
"@/lib/utils"
|
|
518
|
-
]
|
|
519
|
-
},
|
|
520
|
-
"i18n": {
|
|
521
|
-
"required": true,
|
|
522
|
-
"keys": [
|
|
523
|
-
"ui_clear_search",
|
|
524
|
-
"ui_search_placeholder"
|
|
525
|
-
]
|
|
526
|
-
}
|
|
527
|
-
},
|
|
528
|
-
{
|
|
529
|
-
"name": "otp-field",
|
|
530
|
-
"type": "composed",
|
|
531
|
-
"slug": "input-otp",
|
|
532
|
-
"files": [
|
|
533
|
-
{
|
|
534
|
-
"path": "src/components/ui/composed/input-otp/otp-field.tsx",
|
|
535
|
-
"exports": [
|
|
536
|
-
"OtpField"
|
|
537
|
-
]
|
|
538
|
-
}
|
|
539
|
-
],
|
|
540
|
-
"dependencies": {
|
|
541
|
-
"shadcn": [
|
|
542
|
-
"input-otp",
|
|
543
|
-
"label"
|
|
544
|
-
],
|
|
545
|
-
"npm": [
|
|
546
|
-
"input-otp"
|
|
547
|
-
],
|
|
548
|
-
"local": [
|
|
549
|
-
"@/lib/utils"
|
|
550
|
-
]
|
|
551
|
-
},
|
|
552
|
-
"i18n": {
|
|
553
|
-
"required": false,
|
|
554
|
-
"keys": []
|
|
555
|
-
},
|
|
556
|
-
"controller": "OtpFieldController"
|
|
557
|
-
},
|
|
558
|
-
{
|
|
559
|
-
"name": "site-nav",
|
|
560
|
-
"type": "composed",
|
|
561
|
-
"slug": "navigation-menu",
|
|
562
|
-
"files": [
|
|
563
|
-
{
|
|
564
|
-
"path": "src/components/ui/composed/navigation-menu/site-nav.tsx",
|
|
565
|
-
"exports": [
|
|
566
|
-
"SiteNav"
|
|
567
|
-
]
|
|
568
|
-
}
|
|
569
|
-
],
|
|
570
|
-
"dependencies": {
|
|
571
|
-
"shadcn": [
|
|
572
|
-
"navigation-menu"
|
|
573
|
-
],
|
|
574
|
-
"npm": [],
|
|
575
|
-
"local": [
|
|
576
|
-
"@/lib/utils"
|
|
577
|
-
]
|
|
578
|
-
},
|
|
579
|
-
"i18n": {
|
|
580
|
-
"required": false,
|
|
581
|
-
"keys": []
|
|
582
|
-
}
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
"name": "data-pagination",
|
|
586
|
-
"type": "composed",
|
|
587
|
-
"slug": "pagination",
|
|
588
|
-
"files": [
|
|
589
|
-
{
|
|
590
|
-
"path": "src/components/ui/composed/pagination/data-pagination.tsx",
|
|
591
|
-
"exports": [
|
|
592
|
-
"DataPagination"
|
|
593
|
-
]
|
|
594
|
-
}
|
|
595
|
-
],
|
|
596
|
-
"dependencies": {
|
|
597
|
-
"shadcn": [
|
|
598
|
-
"pagination",
|
|
599
|
-
"button"
|
|
600
|
-
],
|
|
601
|
-
"npm": [],
|
|
602
|
-
"local": [
|
|
603
|
-
"@/lib/utils"
|
|
604
|
-
]
|
|
605
|
-
},
|
|
606
|
-
"i18n": {
|
|
607
|
-
"required": true,
|
|
608
|
-
"keys": [
|
|
609
|
-
"ui_next",
|
|
610
|
-
"ui_next_page",
|
|
611
|
-
"ui_previous",
|
|
612
|
-
"ui_previous_page"
|
|
613
|
-
]
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
"name": "info-popover",
|
|
618
|
-
"type": "composed",
|
|
619
|
-
"slug": "popover",
|
|
620
|
-
"files": [
|
|
621
|
-
{
|
|
622
|
-
"path": "src/components/ui/composed/popover/info-popover.tsx",
|
|
623
|
-
"exports": [
|
|
624
|
-
"InfoPopover"
|
|
625
|
-
]
|
|
626
|
-
}
|
|
627
|
-
],
|
|
628
|
-
"dependencies": {
|
|
629
|
-
"shadcn": [
|
|
630
|
-
"popover",
|
|
631
|
-
"button"
|
|
632
|
-
],
|
|
633
|
-
"npm": [
|
|
634
|
-
"lucide-react"
|
|
635
|
-
],
|
|
636
|
-
"local": [
|
|
637
|
-
"@/lib/utils"
|
|
638
|
-
]
|
|
639
|
-
},
|
|
640
|
-
"i18n": {
|
|
641
|
-
"required": true,
|
|
642
|
-
"keys": [
|
|
643
|
-
"ui_more_information"
|
|
644
|
-
]
|
|
645
|
-
}
|
|
646
|
-
},
|
|
647
|
-
{
|
|
648
|
-
"name": "options-radio-group",
|
|
649
|
-
"type": "composed",
|
|
650
|
-
"slug": "radio-group",
|
|
651
|
-
"files": [
|
|
652
|
-
{
|
|
653
|
-
"path": "src/components/ui/composed/radio-group/options-radio-group.tsx",
|
|
654
|
-
"exports": [
|
|
655
|
-
"OptionsRadioGroup"
|
|
656
|
-
]
|
|
657
|
-
}
|
|
658
|
-
],
|
|
659
|
-
"dependencies": {
|
|
660
|
-
"shadcn": [
|
|
661
|
-
"radio-group",
|
|
662
|
-
"label"
|
|
663
|
-
],
|
|
664
|
-
"npm": [],
|
|
665
|
-
"local": [
|
|
666
|
-
"@/lib/utils"
|
|
667
|
-
]
|
|
668
|
-
},
|
|
669
|
-
"i18n": {
|
|
670
|
-
"required": false,
|
|
671
|
-
"keys": []
|
|
672
|
-
},
|
|
673
|
-
"controller": "RadioGroupController"
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
"name": "labeled-select",
|
|
677
|
-
"type": "composed",
|
|
678
|
-
"slug": "select",
|
|
679
|
-
"files": [
|
|
680
|
-
{
|
|
681
|
-
"path": "src/components/ui/composed/select/labeled-select.tsx",
|
|
682
|
-
"exports": [
|
|
683
|
-
"LabeledSelect"
|
|
684
|
-
]
|
|
685
|
-
}
|
|
686
|
-
],
|
|
687
|
-
"dependencies": {
|
|
688
|
-
"shadcn": [
|
|
689
|
-
"select",
|
|
690
|
-
"label"
|
|
691
|
-
],
|
|
692
|
-
"npm": [],
|
|
693
|
-
"local": [
|
|
694
|
-
"@/lib/utils"
|
|
695
|
-
]
|
|
696
|
-
},
|
|
697
|
-
"i18n": {
|
|
698
|
-
"required": true,
|
|
699
|
-
"keys": [
|
|
700
|
-
"ui_select_placeholder"
|
|
701
|
-
]
|
|
702
|
-
},
|
|
703
|
-
"controller": "SelectController"
|
|
704
|
-
},
|
|
705
|
-
{
|
|
706
|
-
"name": "select-groups",
|
|
707
|
-
"type": "composed",
|
|
708
|
-
"slug": "select",
|
|
709
|
-
"files": [
|
|
710
|
-
{
|
|
711
|
-
"path": "src/components/ui/composed/select/select-groups.tsx",
|
|
712
|
-
"exports": [
|
|
713
|
-
"SelectGroups"
|
|
714
|
-
]
|
|
715
|
-
}
|
|
716
|
-
],
|
|
717
|
-
"dependencies": {
|
|
718
|
-
"shadcn": [
|
|
719
|
-
"select",
|
|
720
|
-
"label"
|
|
721
|
-
],
|
|
722
|
-
"npm": [],
|
|
723
|
-
"local": [
|
|
724
|
-
"@/lib/utils"
|
|
725
|
-
]
|
|
726
|
-
},
|
|
727
|
-
"i18n": {
|
|
728
|
-
"required": true,
|
|
729
|
-
"keys": [
|
|
730
|
-
"ui_select_placeholder"
|
|
731
|
-
]
|
|
732
|
-
},
|
|
733
|
-
"controller": "SelectGroupsController"
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
"name": "filter-sheet",
|
|
737
|
-
"type": "composed",
|
|
738
|
-
"slug": "sheet",
|
|
739
|
-
"files": [
|
|
740
|
-
{
|
|
741
|
-
"path": "src/components/ui/composed/sheet/filter-sheet.tsx",
|
|
742
|
-
"exports": [
|
|
743
|
-
"FilterSheet"
|
|
744
|
-
]
|
|
745
|
-
}
|
|
746
|
-
],
|
|
747
|
-
"dependencies": {
|
|
748
|
-
"shadcn": [
|
|
749
|
-
"sheet"
|
|
750
|
-
],
|
|
751
|
-
"npm": [],
|
|
752
|
-
"local": []
|
|
753
|
-
},
|
|
754
|
-
"i18n": {
|
|
755
|
-
"required": false,
|
|
756
|
-
"keys": []
|
|
757
|
-
}
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
"name": "card-skeleton",
|
|
761
|
-
"type": "composed",
|
|
762
|
-
"slug": "skeleton",
|
|
763
|
-
"files": [
|
|
764
|
-
{
|
|
765
|
-
"path": "src/components/ui/composed/skeleton/card-skeleton.tsx",
|
|
766
|
-
"exports": [
|
|
767
|
-
"CardSkeleton"
|
|
768
|
-
]
|
|
769
|
-
}
|
|
770
|
-
],
|
|
771
|
-
"dependencies": {
|
|
772
|
-
"shadcn": [
|
|
773
|
-
"skeleton",
|
|
774
|
-
"card"
|
|
775
|
-
],
|
|
776
|
-
"npm": [],
|
|
777
|
-
"local": [
|
|
778
|
-
"@/lib/utils"
|
|
779
|
-
]
|
|
780
|
-
},
|
|
781
|
-
"i18n": {
|
|
782
|
-
"required": false,
|
|
783
|
-
"keys": []
|
|
784
|
-
}
|
|
785
|
-
},
|
|
786
|
-
{
|
|
787
|
-
"name": "simple-data-table",
|
|
788
|
-
"type": "composed",
|
|
789
|
-
"slug": "table",
|
|
790
|
-
"files": [
|
|
791
|
-
{
|
|
792
|
-
"path": "src/components/ui/composed/table/simple-data-table.tsx",
|
|
793
|
-
"exports": [
|
|
794
|
-
"SimpleDataTable"
|
|
795
|
-
]
|
|
796
|
-
}
|
|
797
|
-
],
|
|
798
|
-
"dependencies": {
|
|
799
|
-
"shadcn": [
|
|
800
|
-
"table"
|
|
801
|
-
],
|
|
802
|
-
"npm": [],
|
|
803
|
-
"local": [
|
|
804
|
-
"@/lib/utils"
|
|
805
|
-
]
|
|
806
|
-
},
|
|
807
|
-
"i18n": {
|
|
808
|
-
"required": true,
|
|
809
|
-
"keys": [
|
|
810
|
-
"ui_no_results"
|
|
811
|
-
]
|
|
812
|
-
}
|
|
813
|
-
},
|
|
814
|
-
{
|
|
815
|
-
"name": "icon-tabs",
|
|
816
|
-
"type": "composed",
|
|
817
|
-
"slug": "tabs",
|
|
818
|
-
"files": [
|
|
819
|
-
{
|
|
820
|
-
"path": "src/components/ui/composed/tabs/icon-tabs.tsx",
|
|
821
|
-
"exports": [
|
|
822
|
-
"IconTabs"
|
|
823
|
-
]
|
|
824
|
-
}
|
|
825
|
-
],
|
|
826
|
-
"dependencies": {
|
|
827
|
-
"shadcn": [
|
|
828
|
-
"tabs"
|
|
829
|
-
],
|
|
830
|
-
"npm": [],
|
|
831
|
-
"local": [
|
|
832
|
-
"@/lib/utils"
|
|
833
|
-
]
|
|
834
|
-
},
|
|
835
|
-
"i18n": {
|
|
836
|
-
"required": false,
|
|
837
|
-
"keys": []
|
|
838
|
-
}
|
|
839
|
-
},
|
|
840
|
-
{
|
|
841
|
-
"name": "labeled-textarea",
|
|
842
|
-
"type": "composed",
|
|
843
|
-
"slug": "textarea",
|
|
844
|
-
"files": [
|
|
845
|
-
{
|
|
846
|
-
"path": "src/components/ui/composed/textarea/labeled-textarea.tsx",
|
|
847
|
-
"exports": [
|
|
848
|
-
"LabeledTextarea"
|
|
849
|
-
]
|
|
850
|
-
}
|
|
851
|
-
],
|
|
852
|
-
"dependencies": {
|
|
853
|
-
"shadcn": [
|
|
854
|
-
"textarea",
|
|
855
|
-
"label"
|
|
856
|
-
],
|
|
857
|
-
"npm": [],
|
|
858
|
-
"local": [
|
|
859
|
-
"@/lib/utils"
|
|
860
|
-
]
|
|
861
|
-
},
|
|
862
|
-
"i18n": {
|
|
863
|
-
"required": false,
|
|
864
|
-
"keys": []
|
|
865
|
-
},
|
|
866
|
-
"controller": "TextareaController"
|
|
867
|
-
},
|
|
868
|
-
{
|
|
869
|
-
"name": "icon-tooltip",
|
|
870
|
-
"type": "composed",
|
|
871
|
-
"slug": "tooltip",
|
|
872
|
-
"files": [
|
|
873
|
-
{
|
|
874
|
-
"path": "src/components/ui/composed/tooltip/icon-tooltip.tsx",
|
|
875
|
-
"exports": [
|
|
876
|
-
"IconTooltip"
|
|
877
|
-
]
|
|
878
|
-
}
|
|
879
|
-
],
|
|
880
|
-
"dependencies": {
|
|
881
|
-
"shadcn": [
|
|
882
|
-
"tooltip"
|
|
883
|
-
],
|
|
884
|
-
"npm": [],
|
|
885
|
-
"local": [
|
|
886
|
-
"@/lib/utils"
|
|
887
|
-
]
|
|
888
|
-
},
|
|
889
|
-
"i18n": {
|
|
890
|
-
"required": false,
|
|
891
|
-
"keys": []
|
|
892
|
-
}
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
"name": "file-dropzone",
|
|
896
|
-
"type": "composed",
|
|
897
|
-
"slug": "upload",
|
|
898
|
-
"files": [
|
|
899
|
-
{
|
|
900
|
-
"path": "src/components/ui/composed/upload/file-dropzone.tsx",
|
|
901
|
-
"exports": [
|
|
902
|
-
"FileDropzone"
|
|
903
|
-
]
|
|
904
|
-
}
|
|
905
|
-
],
|
|
906
|
-
"dependencies": {
|
|
907
|
-
"shadcn": [
|
|
908
|
-
"button",
|
|
909
|
-
"input",
|
|
910
|
-
"label"
|
|
911
|
-
],
|
|
912
|
-
"npm": [
|
|
913
|
-
"lucide-react"
|
|
914
|
-
],
|
|
915
|
-
"local": [
|
|
916
|
-
"@/lib/utils"
|
|
917
|
-
]
|
|
918
|
-
},
|
|
919
|
-
"i18n": {
|
|
920
|
-
"required": true,
|
|
921
|
-
"keys": [
|
|
922
|
-
"ui_browse_files",
|
|
923
|
-
"ui_dropzone_prompt",
|
|
924
|
-
"ui_max_size",
|
|
925
|
-
"ui_remove_file"
|
|
926
|
-
]
|
|
927
|
-
},
|
|
928
|
-
"controller": "FileDropzoneController"
|
|
929
|
-
}
|
|
930
|
-
],
|
|
931
|
-
"featureRegistry": [
|
|
932
|
-
{
|
|
933
|
-
"name": "login",
|
|
934
|
-
"description": "Email/password login form, posting to the auth API and redirecting into the protected area on success.",
|
|
935
|
-
"frameworks": {
|
|
936
|
-
"tanstack": {
|
|
937
|
-
"routeScope": "public",
|
|
938
|
-
"files": [
|
|
939
|
-
"src/routes/(public-pages)/(auth)/route.tsx",
|
|
940
|
-
"src/routes/(public-pages)/(auth)/login.tsx",
|
|
941
|
-
"src/components/screens/login/index.ts",
|
|
942
|
-
"src/components/screens/login/login-screen.tsx",
|
|
943
|
-
"src/components/screens/login/login-form.tsx",
|
|
944
|
-
"src/components/screens/login/login-schema.ts",
|
|
945
|
-
"src/components/layouts/index.ts",
|
|
946
|
-
"src/components/layouts/auth-layout.tsx",
|
|
947
|
-
"src/apis/auth/client/login.ts",
|
|
948
|
-
"src/apis/auth/client/register.ts",
|
|
949
|
-
"src/apis/auth/client/index.ts",
|
|
950
|
-
"src/apis/auth/client/get-profile.ts",
|
|
951
|
-
"src/apis/auth/hooks/index.ts",
|
|
952
|
-
"src/apis/auth/hooks/mutations/index.ts",
|
|
953
|
-
"src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
954
|
-
"src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
955
|
-
"src/apis/query-keys/auth.ts",
|
|
956
|
-
"src/store/auth-state.ts",
|
|
957
|
-
"src/lib/auth/token-cookie.ts",
|
|
958
|
-
"src/@types/user.ts",
|
|
959
|
-
"src/components/common/controllers/input-field-controller.tsx",
|
|
960
|
-
"src/components/ui/composed/button/loading-button.tsx",
|
|
961
|
-
"src/hooks/use-response-error.ts"
|
|
962
|
-
],
|
|
963
|
-
"i18nKeys": [
|
|
964
|
-
"login_page",
|
|
965
|
-
"login_title",
|
|
966
|
-
"login_intro",
|
|
967
|
-
"login_email_label",
|
|
968
|
-
"login_email_placeholder",
|
|
969
|
-
"login_password_label",
|
|
970
|
-
"login_password_placeholder",
|
|
971
|
-
"login_submitting",
|
|
972
|
-
"login_submit",
|
|
973
|
-
"login_success",
|
|
974
|
-
"validation_email_required",
|
|
975
|
-
"validation_email_invalid",
|
|
976
|
-
"validation_password_required"
|
|
977
|
-
]
|
|
978
|
-
},
|
|
979
|
-
"nextjs": {
|
|
980
|
-
"routeScope": "public",
|
|
981
|
-
"sourcePrefix": "cli/templates/nextjs/",
|
|
982
|
-
"files": [
|
|
983
|
-
"cli/templates/nextjs/src/app/(public)/(auth)/layout.tsx",
|
|
984
|
-
"cli/templates/nextjs/src/app/(public)/(auth)/login/page.tsx",
|
|
985
|
-
"src/components/screens/login/index.ts",
|
|
986
|
-
"src/components/screens/login/login-screen.tsx",
|
|
987
|
-
"src/components/screens/login/login-form.tsx",
|
|
988
|
-
"src/components/screens/login/login-schema.ts",
|
|
989
|
-
"src/components/layouts/index.ts",
|
|
990
|
-
"src/components/layouts/auth-layout.tsx",
|
|
991
|
-
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
992
|
-
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
993
|
-
"src/apis/auth/client/register.ts",
|
|
994
|
-
"src/apis/auth/client/login.ts",
|
|
995
|
-
"src/apis/auth/client/index.ts",
|
|
996
|
-
"src/apis/auth/client/get-profile.ts",
|
|
997
|
-
"src/apis/auth/hooks/index.ts",
|
|
998
|
-
"src/apis/auth/hooks/mutations/index.ts",
|
|
999
|
-
"src/apis/query-keys/auth.ts",
|
|
1000
|
-
"src/store/auth-state.ts",
|
|
1001
|
-
"src/lib/auth/token-cookie.ts",
|
|
1002
|
-
"src/lib/query-client.ts",
|
|
1003
|
-
"src/@types/user.ts",
|
|
1004
|
-
"src/components/common/controllers/input-field-controller.tsx",
|
|
1005
|
-
"src/components/ui/composed/button/loading-button.tsx",
|
|
1006
|
-
"src/hooks/use-response-error.ts"
|
|
1007
|
-
],
|
|
1008
|
-
"i18nKeys": [
|
|
1009
|
-
"login_page",
|
|
1010
|
-
"login_title",
|
|
1011
|
-
"login_intro",
|
|
1012
|
-
"login_email_label",
|
|
1013
|
-
"login_email_placeholder",
|
|
1014
|
-
"login_password_label",
|
|
1015
|
-
"login_password_placeholder",
|
|
1016
|
-
"login_submitting",
|
|
1017
|
-
"login_submit",
|
|
1018
|
-
"login_success",
|
|
1019
|
-
"validation_email_required",
|
|
1020
|
-
"validation_email_invalid",
|
|
1021
|
-
"validation_password_required"
|
|
1022
|
-
]
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
},
|
|
1026
|
-
{
|
|
1027
|
-
"name": "signup",
|
|
1028
|
-
"description": "Name/email/password registration form, posting to the auth API and auto-logging in on success.",
|
|
1029
|
-
"frameworks": {
|
|
1030
|
-
"tanstack": {
|
|
1031
|
-
"routeScope": "public",
|
|
1032
|
-
"files": [
|
|
1033
|
-
"src/routes/(public-pages)/(auth)/route.tsx",
|
|
1034
|
-
"src/routes/(public-pages)/(auth)/signup.tsx",
|
|
1035
|
-
"src/components/screens/signup/index.ts",
|
|
1036
|
-
"src/components/screens/signup/signup-screen.tsx",
|
|
1037
|
-
"src/components/screens/signup/signup-form.tsx",
|
|
1038
|
-
"src/components/screens/signup/signup-schema.ts",
|
|
1039
|
-
"src/components/layouts/index.ts",
|
|
1040
|
-
"src/components/layouts/auth-layout.tsx",
|
|
1041
|
-
"src/apis/auth/client/register.ts",
|
|
1042
|
-
"src/apis/auth/client/login.ts",
|
|
1043
|
-
"src/apis/auth/client/index.ts",
|
|
1044
|
-
"src/apis/auth/client/get-profile.ts",
|
|
1045
|
-
"src/apis/auth/hooks/index.ts",
|
|
1046
|
-
"src/apis/auth/hooks/mutations/index.ts",
|
|
1047
|
-
"src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
1048
|
-
"src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
1049
|
-
"src/apis/query-keys/auth.ts",
|
|
1050
|
-
"src/store/auth-state.ts",
|
|
1051
|
-
"src/lib/auth/token-cookie.ts",
|
|
1052
|
-
"src/@types/user.ts",
|
|
1053
|
-
"src/components/common/controllers/input-field-controller.tsx",
|
|
1054
|
-
"src/components/ui/composed/button/loading-button.tsx",
|
|
1055
|
-
"src/hooks/use-response-error.ts"
|
|
1056
|
-
],
|
|
1057
|
-
"i18nKeys": [
|
|
1058
|
-
"signup_page",
|
|
1059
|
-
"signup_title",
|
|
1060
|
-
"signup_intro",
|
|
1061
|
-
"signup_name_label",
|
|
1062
|
-
"signup_name_placeholder",
|
|
1063
|
-
"signup_email_label",
|
|
1064
|
-
"signup_email_placeholder",
|
|
1065
|
-
"signup_password_label",
|
|
1066
|
-
"signup_password_placeholder",
|
|
1067
|
-
"signup_confirm_password_label",
|
|
1068
|
-
"signup_confirm_password_placeholder",
|
|
1069
|
-
"signup_submitting",
|
|
1070
|
-
"signup_submit",
|
|
1071
|
-
"signup_success",
|
|
1072
|
-
"validation_name_required",
|
|
1073
|
-
"validation_email_required",
|
|
1074
|
-
"validation_email_invalid",
|
|
1075
|
-
"validation_password_required",
|
|
1076
|
-
"validation_confirm_password_required",
|
|
1077
|
-
"validation_password_mismatch"
|
|
1078
|
-
]
|
|
1079
|
-
},
|
|
1080
|
-
"nextjs": {
|
|
1081
|
-
"routeScope": "public",
|
|
1082
|
-
"sourcePrefix": "cli/templates/nextjs/",
|
|
1083
|
-
"files": [
|
|
1084
|
-
"cli/templates/nextjs/src/app/(public)/(auth)/layout.tsx",
|
|
1085
|
-
"cli/templates/nextjs/src/app/(public)/(auth)/signup/page.tsx",
|
|
1086
|
-
"src/components/screens/signup/index.ts",
|
|
1087
|
-
"src/components/screens/signup/signup-screen.tsx",
|
|
1088
|
-
"src/components/screens/signup/signup-form.tsx",
|
|
1089
|
-
"src/components/screens/signup/signup-schema.ts",
|
|
1090
|
-
"src/components/layouts/index.ts",
|
|
1091
|
-
"src/components/layouts/auth-layout.tsx",
|
|
1092
|
-
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
1093
|
-
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
1094
|
-
"src/apis/auth/client/register.ts",
|
|
1095
|
-
"src/apis/auth/client/login.ts",
|
|
1096
|
-
"src/apis/auth/client/index.ts",
|
|
1097
|
-
"src/apis/auth/client/get-profile.ts",
|
|
1098
|
-
"src/apis/auth/hooks/index.ts",
|
|
1099
|
-
"src/apis/auth/hooks/mutations/index.ts",
|
|
1100
|
-
"src/apis/query-keys/auth.ts",
|
|
1101
|
-
"src/store/auth-state.ts",
|
|
1102
|
-
"src/lib/auth/token-cookie.ts",
|
|
1103
|
-
"src/lib/query-client.ts",
|
|
1104
|
-
"src/@types/user.ts",
|
|
1105
|
-
"src/components/common/controllers/input-field-controller.tsx",
|
|
1106
|
-
"src/components/ui/composed/button/loading-button.tsx",
|
|
1107
|
-
"src/hooks/use-response-error.ts"
|
|
1108
|
-
],
|
|
1109
|
-
"i18nKeys": [
|
|
1110
|
-
"signup_page",
|
|
1111
|
-
"signup_title",
|
|
1112
|
-
"signup_intro",
|
|
1113
|
-
"signup_name_label",
|
|
1114
|
-
"signup_name_placeholder",
|
|
1115
|
-
"signup_email_label",
|
|
1116
|
-
"signup_email_placeholder",
|
|
1117
|
-
"signup_password_label",
|
|
1118
|
-
"signup_password_placeholder",
|
|
1119
|
-
"signup_confirm_password_label",
|
|
1120
|
-
"signup_confirm_password_placeholder",
|
|
1121
|
-
"signup_submitting",
|
|
1122
|
-
"signup_submit",
|
|
1123
|
-
"signup_success",
|
|
1124
|
-
"validation_name_required",
|
|
1125
|
-
"validation_email_required",
|
|
1126
|
-
"validation_email_invalid",
|
|
1127
|
-
"validation_password_required",
|
|
1128
|
-
"validation_confirm_password_required",
|
|
1129
|
-
"validation_password_mismatch"
|
|
1130
|
-
]
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
},
|
|
1134
|
-
{
|
|
1135
|
-
"name": "dashboard",
|
|
1136
|
-
"description": "Protected landing screen behind the auth guard, with the current profile fetched via React Query into the auth store.",
|
|
1137
|
-
"frameworks": {
|
|
1138
|
-
"tanstack": {
|
|
1139
|
-
"routeScope": "protected",
|
|
1140
|
-
"files": [
|
|
1141
|
-
"src/routes/(protected-pages)/route.tsx",
|
|
1142
|
-
"src/routes/(protected-pages)/dashboard.tsx",
|
|
1143
|
-
"src/components/screens/dashboard/index.ts",
|
|
1144
|
-
"src/components/screens/dashboard/dashboard-screen.tsx",
|
|
1145
|
-
"src/components/layouts/index.ts",
|
|
1146
|
-
"src/components/layouts/dashboard-layout.tsx",
|
|
1147
|
-
"src/components/layouts/dashboard-sidebar.tsx",
|
|
1148
|
-
"src/apis/auth/client/get-profile.ts",
|
|
1149
|
-
"src/apis/auth/client/login.ts",
|
|
1150
|
-
"src/apis/auth/client/register.ts",
|
|
1151
|
-
"src/apis/auth/client/index.ts",
|
|
1152
|
-
"src/apis/query-keys/auth.ts",
|
|
1153
|
-
"src/store/auth-state.ts",
|
|
1154
|
-
"src/lib/auth/token-cookie.ts",
|
|
1155
|
-
"src/@types/user.ts"
|
|
1156
|
-
],
|
|
1157
|
-
"i18nKeys": [
|
|
1158
|
-
"dashboard_page",
|
|
1159
|
-
"dashboard_title",
|
|
1160
|
-
"dashboard_intro",
|
|
1161
|
-
"dashboard_signed_in_as",
|
|
1162
|
-
"dashboard_logout"
|
|
1163
|
-
]
|
|
1164
|
-
},
|
|
1165
|
-
"nextjs": {
|
|
1166
|
-
"routeScope": "protected",
|
|
1167
|
-
"sourcePrefix": "cli/templates/nextjs/",
|
|
1168
|
-
"files": [
|
|
1169
|
-
"cli/templates/nextjs/src/app/(protected)/layout.tsx",
|
|
1170
|
-
"cli/templates/nextjs/src/app/(protected)/dashboard/page.tsx",
|
|
1171
|
-
"cli/templates/nextjs/src/components/layouts/dashboard-sidebar.tsx",
|
|
1172
|
-
"src/components/screens/dashboard/index.ts",
|
|
1173
|
-
"src/components/screens/dashboard/dashboard-screen.tsx",
|
|
1174
|
-
"src/components/layouts/index.ts",
|
|
1175
|
-
"src/components/layouts/dashboard-layout.tsx",
|
|
1176
|
-
"src/apis/auth/client/get-profile.ts",
|
|
1177
|
-
"src/apis/auth/client/login.ts",
|
|
1178
|
-
"src/apis/auth/client/register.ts",
|
|
1179
|
-
"src/apis/auth/client/index.ts",
|
|
1180
|
-
"src/apis/query-keys/auth.ts",
|
|
1181
|
-
"src/store/auth-state.ts",
|
|
1182
|
-
"src/lib/auth/token-cookie.ts",
|
|
1183
|
-
"src/lib/query-client.ts",
|
|
1184
|
-
"src/@types/user.ts"
|
|
1185
|
-
],
|
|
1186
|
-
"i18nKeys": [
|
|
1187
|
-
"dashboard_page",
|
|
1188
|
-
"dashboard_title",
|
|
1189
|
-
"dashboard_intro",
|
|
1190
|
-
"dashboard_signed_in_as",
|
|
1191
|
-
"dashboard_logout"
|
|
1192
|
-
]
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
},
|
|
1196
|
-
{
|
|
1197
|
-
"name": "about",
|
|
1198
|
-
"description": "Static public \"About\" page.",
|
|
1199
|
-
"frameworks": {
|
|
1200
|
-
"tanstack": {
|
|
1201
|
-
"routeScope": "public",
|
|
1202
|
-
"files": [
|
|
1203
|
-
"src/routes/(public-pages)/about.tsx",
|
|
1204
|
-
"src/components/screens/about/index.ts",
|
|
1205
|
-
"src/components/screens/about/about-screen.tsx"
|
|
1206
|
-
],
|
|
1207
|
-
"i18nKeys": [
|
|
1208
|
-
"about_page",
|
|
1209
|
-
"about_title",
|
|
1210
|
-
"about_intro"
|
|
1211
|
-
]
|
|
1212
|
-
},
|
|
1213
|
-
"nextjs": {
|
|
1214
|
-
"routeScope": "public",
|
|
1215
|
-
"sourcePrefix": "cli/templates/nextjs/",
|
|
1216
|
-
"files": [
|
|
1217
|
-
"cli/templates/nextjs/src/app/(public)/about/page.tsx",
|
|
1218
|
-
"src/components/screens/about/index.ts",
|
|
1219
|
-
"src/components/screens/about/about-screen.tsx"
|
|
1220
|
-
],
|
|
1221
|
-
"i18nKeys": [
|
|
1222
|
-
"about_page",
|
|
1223
|
-
"about_title",
|
|
1224
|
-
"about_intro"
|
|
1225
|
-
]
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
},
|
|
1229
|
-
{
|
|
1230
|
-
"name": "contact",
|
|
1231
|
-
"description": "Static public \"Contact\" page.",
|
|
1232
|
-
"frameworks": {
|
|
1233
|
-
"tanstack": {
|
|
1234
|
-
"routeScope": "public",
|
|
1235
|
-
"files": [
|
|
1236
|
-
"src/routes/(public-pages)/contact.tsx",
|
|
1237
|
-
"src/components/screens/contact/index.ts",
|
|
1238
|
-
"src/components/screens/contact/contact-screen.tsx"
|
|
1239
|
-
],
|
|
1240
|
-
"i18nKeys": [
|
|
1241
|
-
"contact_page",
|
|
1242
|
-
"contact_title",
|
|
1243
|
-
"contact_intro"
|
|
1244
|
-
]
|
|
1245
|
-
},
|
|
1246
|
-
"nextjs": {
|
|
1247
|
-
"routeScope": "public",
|
|
1248
|
-
"sourcePrefix": "cli/templates/nextjs/",
|
|
1249
|
-
"files": [
|
|
1250
|
-
"cli/templates/nextjs/src/app/(public)/contact/page.tsx",
|
|
1251
|
-
"src/components/screens/contact/index.ts",
|
|
1252
|
-
"src/components/screens/contact/contact-screen.tsx"
|
|
1253
|
-
],
|
|
1254
|
-
"i18nKeys": [
|
|
1255
|
-
"contact_page",
|
|
1256
|
-
"contact_title",
|
|
1257
|
-
"contact_intro"
|
|
1258
|
-
]
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
],
|
|
1263
|
-
"files": {
|
|
1264
|
-
"src/components/ui/
|
|
1265
|
-
"src/components/ui/
|
|
1266
|
-
"src/components/ui/
|
|
1267
|
-
"src/components/ui/
|
|
1268
|
-
"src/components/ui/
|
|
1269
|
-
"src/components/ui/
|
|
1270
|
-
"src/components/ui/
|
|
1271
|
-
"src/components/ui/
|
|
1272
|
-
"src/components/ui/
|
|
1273
|
-
"src/components/ui/composed/checkbox/checkbox-group.tsx": "import * as React from 'react';\r\nimport { Checkbox } from '@/components/ui/checkbox';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype CheckboxOption = {\r\n label: string;\r\n value: string;\r\n description?: string;\r\n disabled?: boolean;\r\n};\r\n\r\ntype CheckboxGroupProps = {\r\n options: CheckboxOption[];\r\n value: string[];\r\n onValueChange: (value: string[]) => void;\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n orientation?: 'vertical' | 'horizontal';\r\n className?: string;\r\n};\r\n\r\nexport function CheckboxGroup({\r\n options,\r\n value,\r\n onValueChange,\r\n label,\r\n description,\r\n error,\r\n orientation = 'vertical',\r\n className,\r\n}: CheckboxGroupProps) {\r\n const groupId = React.useId();\r\n\r\n const toggle = (optionValue: string) => {\r\n if (value.includes(optionValue)) {\r\n onValueChange(value.filter(v => v !== optionValue));\r\n } else {\r\n onValueChange([...value, optionValue]);\r\n }\r\n };\r\n\r\n return (\r\n <fieldset className={cn('flex flex-col gap-2', className)}>\r\n {label && (\r\n <legend className='mb-1 text-sm font-medium leading-none'>\r\n {label}\r\n </legend>\r\n )}\r\n {description && (\r\n <p className='text-muted-foreground text-sm'>{description}</p>\r\n )}\r\n <div\r\n className={cn(\r\n 'flex gap-3',\r\n orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap'\r\n )}\r\n >\r\n {options.map(option => {\r\n const id = `${groupId}-${option.value}`;\r\n return (\r\n <div key={option.value} className='flex items-start gap-2'>\r\n <Checkbox\r\n id={id}\r\n checked={value.includes(option.value)}\r\n onCheckedChange={() => toggle(option.value)}\r\n disabled={option.disabled}\r\n className='mt-0.5'\r\n />\r\n <div className='flex flex-col gap-0.5'>\r\n <Label\r\n htmlFor={id}\r\n className={cn(option.disabled && 'opacity-50')}\r\n >\r\n {option.label}\r\n </Label>\r\n {option.description && (\r\n <span className='text-muted-foreground text-xs'>\r\n {option.description}\r\n </span>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </fieldset>\r\n );\r\n}\r\n",
|
|
1274
|
-
"src/components/ui/
|
|
1275
|
-
"src/components/ui/composed/
|
|
1276
|
-
"src/components/ui/composed/
|
|
1277
|
-
"src/components/ui/composed/
|
|
1278
|
-
"src/components/ui/composed/
|
|
1279
|
-
"src/components/ui/composed/
|
|
1280
|
-
"src/components/ui/composed/
|
|
1281
|
-
"src/components/ui/composed/
|
|
1282
|
-
"src/components/ui/composed/
|
|
1283
|
-
"src/components/ui/composed/
|
|
1284
|
-
"src/components/ui/composed/
|
|
1285
|
-
"src/components/ui/composed/
|
|
1286
|
-
"src/components/ui/composed/
|
|
1287
|
-
"src/components/ui/composed/
|
|
1288
|
-
"src/components/ui/composed/
|
|
1289
|
-
"src/components/ui/composed/
|
|
1290
|
-
"src/components/ui/composed/
|
|
1291
|
-
"src/components/ui/composed/
|
|
1292
|
-
"src/components/ui/composed/
|
|
1293
|
-
"src/components/ui/composed/
|
|
1294
|
-
"src/components/ui/composed/
|
|
1295
|
-
"src/components/ui/composed/
|
|
1296
|
-
"src/
|
|
1297
|
-
"src/
|
|
1298
|
-
"src/components/
|
|
1299
|
-
"src/components/
|
|
1300
|
-
"src/components/
|
|
1301
|
-
"src/components/
|
|
1302
|
-
"src/components/
|
|
1303
|
-
"src/components/
|
|
1304
|
-
"src/
|
|
1305
|
-
"src/
|
|
1306
|
-
"src/
|
|
1307
|
-
"src/
|
|
1308
|
-
"src/
|
|
1309
|
-
"src/
|
|
1310
|
-
"src/
|
|
1311
|
-
"src/
|
|
1312
|
-
"src/
|
|
1313
|
-
"src/
|
|
1314
|
-
"src/
|
|
1315
|
-
"src
|
|
1316
|
-
"src/components/
|
|
1317
|
-
"src/
|
|
1318
|
-
"
|
|
1319
|
-
"
|
|
1320
|
-
"
|
|
1321
|
-
"
|
|
1322
|
-
"src/
|
|
1323
|
-
"src/
|
|
1324
|
-
"src/components/screens/signup/index.ts": "export { SignupScreen } from './signup-screen';\r\n",
|
|
1325
|
-
"src/components/
|
|
1326
|
-
"src/components/
|
|
1327
|
-
"src/components/
|
|
1328
|
-
"
|
|
1329
|
-
"src/
|
|
1330
|
-
"src/
|
|
1331
|
-
"src/
|
|
1332
|
-
"src/
|
|
1333
|
-
"src/components/
|
|
1334
|
-
"src/components/
|
|
1335
|
-
"
|
|
1336
|
-
"
|
|
1337
|
-
"
|
|
1338
|
-
"src/
|
|
1339
|
-
"src/
|
|
1340
|
-
"src/
|
|
1341
|
-
"
|
|
1342
|
-
"src/
|
|
1343
|
-
"src/
|
|
1344
|
-
"src/
|
|
1345
|
-
"
|
|
1346
|
-
"src/
|
|
1347
|
-
"src/
|
|
1348
|
-
"src/
|
|
1349
|
-
"src/
|
|
1350
|
-
"src/
|
|
1351
|
-
"src/components/common/controllers/
|
|
1352
|
-
"src/
|
|
1353
|
-
"src/
|
|
1354
|
-
"src/
|
|
1355
|
-
"src/
|
|
1356
|
-
"src/
|
|
1357
|
-
"src/lib/
|
|
1358
|
-
},
|
|
1359
|
-
|
|
1360
|
-
"
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
"
|
|
1397
|
-
"
|
|
1398
|
-
"
|
|
1399
|
-
"
|
|
1400
|
-
"
|
|
1401
|
-
"
|
|
1402
|
-
"
|
|
1403
|
-
"
|
|
1404
|
-
"
|
|
1405
|
-
"
|
|
1406
|
-
"
|
|
1407
|
-
"
|
|
1408
|
-
"
|
|
1409
|
-
"
|
|
1410
|
-
"
|
|
1411
|
-
"
|
|
1412
|
-
"
|
|
1413
|
-
"
|
|
1414
|
-
"
|
|
1415
|
-
"
|
|
1416
|
-
"
|
|
1417
|
-
"
|
|
1418
|
-
"
|
|
1419
|
-
"
|
|
1420
|
-
"
|
|
1421
|
-
"
|
|
1422
|
-
"
|
|
1423
|
-
"
|
|
1424
|
-
"
|
|
1425
|
-
"
|
|
1426
|
-
"
|
|
1427
|
-
"
|
|
1428
|
-
"
|
|
1429
|
-
"
|
|
1430
|
-
"
|
|
1431
|
-
"
|
|
1432
|
-
"
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
"
|
|
1436
|
-
"
|
|
1437
|
-
"
|
|
1438
|
-
"
|
|
1439
|
-
"
|
|
1440
|
-
"
|
|
1441
|
-
"
|
|
1442
|
-
"
|
|
1443
|
-
"
|
|
1444
|
-
"
|
|
1445
|
-
"
|
|
1446
|
-
"
|
|
1447
|
-
"
|
|
1448
|
-
"
|
|
1449
|
-
"
|
|
1450
|
-
"
|
|
1451
|
-
"
|
|
1452
|
-
"
|
|
1453
|
-
"
|
|
1454
|
-
"
|
|
1455
|
-
"
|
|
1456
|
-
"
|
|
1457
|
-
"
|
|
1458
|
-
"
|
|
1459
|
-
"
|
|
1460
|
-
"
|
|
1461
|
-
"
|
|
1462
|
-
"
|
|
1463
|
-
"
|
|
1464
|
-
"
|
|
1465
|
-
"
|
|
1466
|
-
"
|
|
1467
|
-
"
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
"
|
|
1471
|
-
"
|
|
1472
|
-
"
|
|
1473
|
-
"
|
|
1474
|
-
"
|
|
1475
|
-
"
|
|
1476
|
-
"
|
|
1477
|
-
"
|
|
1478
|
-
"
|
|
1479
|
-
"
|
|
1480
|
-
"
|
|
1481
|
-
"
|
|
1482
|
-
"
|
|
1483
|
-
"
|
|
1484
|
-
"
|
|
1485
|
-
"
|
|
1486
|
-
"
|
|
1487
|
-
"
|
|
1488
|
-
"
|
|
1489
|
-
"
|
|
1490
|
-
"
|
|
1491
|
-
"
|
|
1492
|
-
"
|
|
1493
|
-
"
|
|
1494
|
-
"
|
|
1495
|
-
"
|
|
1496
|
-
"
|
|
1497
|
-
"
|
|
1498
|
-
"
|
|
1499
|
-
"
|
|
1500
|
-
"
|
|
1501
|
-
"
|
|
1502
|
-
"
|
|
1503
|
-
"
|
|
1504
|
-
"
|
|
1505
|
-
"
|
|
1506
|
-
"
|
|
1507
|
-
|
|
1508
|
-
|
|
1
|
+
{
|
|
2
|
+
"registry": [
|
|
3
|
+
{
|
|
4
|
+
"name": "faq-accordion",
|
|
5
|
+
"type": "composed",
|
|
6
|
+
"slug": "accordion",
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"path": "src/components/ui/composed/accordion/faq-accordion.tsx",
|
|
10
|
+
"exports": [
|
|
11
|
+
"FaqAccordion"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"shadcn": [
|
|
17
|
+
"accordion"
|
|
18
|
+
],
|
|
19
|
+
"npm": [],
|
|
20
|
+
"local": [
|
|
21
|
+
"@/lib/utils"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"i18n": {
|
|
25
|
+
"required": false,
|
|
26
|
+
"keys": []
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "error-alert",
|
|
31
|
+
"type": "composed",
|
|
32
|
+
"slug": "alert",
|
|
33
|
+
"files": [
|
|
34
|
+
{
|
|
35
|
+
"path": "src/components/ui/composed/alert/error-alert.tsx",
|
|
36
|
+
"exports": [
|
|
37
|
+
"ErrorAlert"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"shadcn": [
|
|
43
|
+
"alert"
|
|
44
|
+
],
|
|
45
|
+
"npm": [
|
|
46
|
+
"lucide-react"
|
|
47
|
+
],
|
|
48
|
+
"local": [
|
|
49
|
+
"@/lib/utils"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"i18n": {
|
|
53
|
+
"required": false,
|
|
54
|
+
"keys": []
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "user-avatar",
|
|
59
|
+
"type": "composed",
|
|
60
|
+
"slug": "avatar",
|
|
61
|
+
"files": [
|
|
62
|
+
{
|
|
63
|
+
"path": "src/components/ui/composed/avatar/user-avatar.tsx",
|
|
64
|
+
"exports": [
|
|
65
|
+
"UserAvatar"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"shadcn": [
|
|
71
|
+
"avatar"
|
|
72
|
+
],
|
|
73
|
+
"npm": [],
|
|
74
|
+
"local": [
|
|
75
|
+
"@/lib/utils"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"i18n": {
|
|
79
|
+
"required": false,
|
|
80
|
+
"keys": []
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "status-badge",
|
|
85
|
+
"type": "composed",
|
|
86
|
+
"slug": "badge",
|
|
87
|
+
"files": [
|
|
88
|
+
{
|
|
89
|
+
"path": "src/components/ui/composed/badge/status-badge.tsx",
|
|
90
|
+
"exports": [
|
|
91
|
+
"StatusBadge"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"dependencies": {
|
|
96
|
+
"shadcn": [
|
|
97
|
+
"badge"
|
|
98
|
+
],
|
|
99
|
+
"npm": [],
|
|
100
|
+
"local": [
|
|
101
|
+
"@/lib/utils"
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"i18n": {
|
|
105
|
+
"required": false,
|
|
106
|
+
"keys": []
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "loading-button",
|
|
111
|
+
"type": "composed",
|
|
112
|
+
"slug": "button",
|
|
113
|
+
"files": [
|
|
114
|
+
{
|
|
115
|
+
"path": "src/components/ui/composed/button/loading-button.tsx",
|
|
116
|
+
"exports": [
|
|
117
|
+
"LoadingButton"
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"dependencies": {
|
|
122
|
+
"shadcn": [
|
|
123
|
+
"button"
|
|
124
|
+
],
|
|
125
|
+
"npm": [
|
|
126
|
+
"lucide-react"
|
|
127
|
+
],
|
|
128
|
+
"local": []
|
|
129
|
+
},
|
|
130
|
+
"i18n": {
|
|
131
|
+
"required": false,
|
|
132
|
+
"keys": []
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "theme-toggle",
|
|
137
|
+
"type": "composed",
|
|
138
|
+
"slug": "button",
|
|
139
|
+
"files": [
|
|
140
|
+
{
|
|
141
|
+
"path": "src/components/ui/composed/button/theme-toggle.tsx",
|
|
142
|
+
"exports": [
|
|
143
|
+
"ThemeToggle"
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"dependencies": {
|
|
148
|
+
"shadcn": [
|
|
149
|
+
"button"
|
|
150
|
+
],
|
|
151
|
+
"npm": [
|
|
152
|
+
"lucide-react",
|
|
153
|
+
"next-themes"
|
|
154
|
+
],
|
|
155
|
+
"local": []
|
|
156
|
+
},
|
|
157
|
+
"i18n": {
|
|
158
|
+
"required": true,
|
|
159
|
+
"keys": [
|
|
160
|
+
"ui_toggle_theme"
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "stats-card",
|
|
166
|
+
"type": "composed",
|
|
167
|
+
"slug": "card",
|
|
168
|
+
"files": [
|
|
169
|
+
{
|
|
170
|
+
"path": "src/components/ui/composed/card/stats-card.tsx",
|
|
171
|
+
"exports": [
|
|
172
|
+
"StatsCard"
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"dependencies": {
|
|
177
|
+
"shadcn": [
|
|
178
|
+
"card"
|
|
179
|
+
],
|
|
180
|
+
"npm": [
|
|
181
|
+
"lucide-react"
|
|
182
|
+
],
|
|
183
|
+
"local": [
|
|
184
|
+
"@/lib/utils"
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
"i18n": {
|
|
188
|
+
"required": false,
|
|
189
|
+
"keys": []
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "media-carousel",
|
|
194
|
+
"type": "composed",
|
|
195
|
+
"slug": "carousel",
|
|
196
|
+
"files": [
|
|
197
|
+
{
|
|
198
|
+
"path": "src/components/ui/composed/carousel/media-carousel.tsx",
|
|
199
|
+
"exports": [
|
|
200
|
+
"MediaCarousel"
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"dependencies": {
|
|
205
|
+
"shadcn": [
|
|
206
|
+
"carousel",
|
|
207
|
+
"button"
|
|
208
|
+
],
|
|
209
|
+
"npm": [
|
|
210
|
+
"embla-carousel-react",
|
|
211
|
+
"lucide-react"
|
|
212
|
+
],
|
|
213
|
+
"local": [
|
|
214
|
+
"@/lib/utils"
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
"i18n": {
|
|
218
|
+
"required": false,
|
|
219
|
+
"keys": []
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "checkbox-field",
|
|
224
|
+
"type": "composed",
|
|
225
|
+
"slug": "checkbox",
|
|
226
|
+
"files": [
|
|
227
|
+
{
|
|
228
|
+
"path": "src/components/ui/composed/checkbox/checkbox-field.tsx",
|
|
229
|
+
"exports": [
|
|
230
|
+
"CheckboxField"
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
"dependencies": {
|
|
235
|
+
"shadcn": [
|
|
236
|
+
"checkbox",
|
|
237
|
+
"label"
|
|
238
|
+
],
|
|
239
|
+
"npm": [],
|
|
240
|
+
"local": [
|
|
241
|
+
"@/lib/utils"
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
"i18n": {
|
|
245
|
+
"required": false,
|
|
246
|
+
"keys": []
|
|
247
|
+
},
|
|
248
|
+
"controller": "CheckboxController"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"name": "checkbox-group",
|
|
252
|
+
"type": "composed",
|
|
253
|
+
"slug": "checkbox",
|
|
254
|
+
"files": [
|
|
255
|
+
{
|
|
256
|
+
"path": "src/components/ui/composed/checkbox/checkbox-group.tsx",
|
|
257
|
+
"exports": [
|
|
258
|
+
"CheckboxGroup"
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
],
|
|
262
|
+
"dependencies": {
|
|
263
|
+
"shadcn": [
|
|
264
|
+
"checkbox",
|
|
265
|
+
"label"
|
|
266
|
+
],
|
|
267
|
+
"npm": [],
|
|
268
|
+
"local": [
|
|
269
|
+
"@/lib/utils"
|
|
270
|
+
]
|
|
271
|
+
},
|
|
272
|
+
"i18n": {
|
|
273
|
+
"required": false,
|
|
274
|
+
"keys": []
|
|
275
|
+
},
|
|
276
|
+
"controller": "CheckboxGroupController"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"name": "searchable-select",
|
|
280
|
+
"type": "composed",
|
|
281
|
+
"slug": "combobox",
|
|
282
|
+
"files": [
|
|
283
|
+
{
|
|
284
|
+
"path": "src/components/ui/composed/combobox/searchable-select.tsx",
|
|
285
|
+
"exports": [
|
|
286
|
+
"SearchableSelect"
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
],
|
|
290
|
+
"dependencies": {
|
|
291
|
+
"shadcn": [
|
|
292
|
+
"command",
|
|
293
|
+
"popover",
|
|
294
|
+
"button",
|
|
295
|
+
"label",
|
|
296
|
+
"dialog",
|
|
297
|
+
"input-group"
|
|
298
|
+
],
|
|
299
|
+
"npm": [
|
|
300
|
+
"cmdk",
|
|
301
|
+
"lucide-react"
|
|
302
|
+
],
|
|
303
|
+
"local": [
|
|
304
|
+
"@/lib/utils"
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
"i18n": {
|
|
308
|
+
"required": true,
|
|
309
|
+
"keys": [
|
|
310
|
+
"ui_no_results",
|
|
311
|
+
"ui_search_placeholder",
|
|
312
|
+
"ui_select_placeholder"
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
"controller": "ComboboxController"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"name": "date-picker-field",
|
|
319
|
+
"type": "composed",
|
|
320
|
+
"slug": "date-picker",
|
|
321
|
+
"files": [
|
|
322
|
+
{
|
|
323
|
+
"path": "src/components/ui/composed/date-picker/date-picker-field.tsx",
|
|
324
|
+
"exports": [
|
|
325
|
+
"DatePickerField"
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
],
|
|
329
|
+
"dependencies": {
|
|
330
|
+
"shadcn": [
|
|
331
|
+
"calendar",
|
|
332
|
+
"button",
|
|
333
|
+
"label",
|
|
334
|
+
"popover"
|
|
335
|
+
],
|
|
336
|
+
"npm": [
|
|
337
|
+
"react-day-picker",
|
|
338
|
+
"lucide-react"
|
|
339
|
+
],
|
|
340
|
+
"local": [
|
|
341
|
+
"@/lib/utils"
|
|
342
|
+
]
|
|
343
|
+
},
|
|
344
|
+
"i18n": {
|
|
345
|
+
"required": true,
|
|
346
|
+
"keys": [
|
|
347
|
+
"ui_pick_date"
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
"controller": "DatePickerController"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"name": "confirmation-dialog",
|
|
354
|
+
"type": "composed",
|
|
355
|
+
"slug": "dialog",
|
|
356
|
+
"files": [
|
|
357
|
+
{
|
|
358
|
+
"path": "src/components/ui/composed/dialog/confirmation-dialog.tsx",
|
|
359
|
+
"exports": [
|
|
360
|
+
"ConfirmationDialog"
|
|
361
|
+
]
|
|
362
|
+
}
|
|
363
|
+
],
|
|
364
|
+
"dependencies": {
|
|
365
|
+
"shadcn": [
|
|
366
|
+
"dialog",
|
|
367
|
+
"button"
|
|
368
|
+
],
|
|
369
|
+
"npm": [
|
|
370
|
+
"lucide-react"
|
|
371
|
+
],
|
|
372
|
+
"local": []
|
|
373
|
+
},
|
|
374
|
+
"i18n": {
|
|
375
|
+
"required": true,
|
|
376
|
+
"keys": [
|
|
377
|
+
"ui_cancel",
|
|
378
|
+
"ui_confirm"
|
|
379
|
+
]
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"name": "action-drawer",
|
|
384
|
+
"type": "composed",
|
|
385
|
+
"slug": "drawer",
|
|
386
|
+
"files": [
|
|
387
|
+
{
|
|
388
|
+
"path": "src/components/ui/composed/drawer/action-drawer.tsx",
|
|
389
|
+
"exports": [
|
|
390
|
+
"ActionDrawer"
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
],
|
|
394
|
+
"dependencies": {
|
|
395
|
+
"shadcn": [
|
|
396
|
+
"drawer"
|
|
397
|
+
],
|
|
398
|
+
"npm": [
|
|
399
|
+
"vaul"
|
|
400
|
+
],
|
|
401
|
+
"local": []
|
|
402
|
+
},
|
|
403
|
+
"i18n": {
|
|
404
|
+
"required": false,
|
|
405
|
+
"keys": []
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"name": "actions-dropdown",
|
|
410
|
+
"type": "composed",
|
|
411
|
+
"slug": "dropdown-menu",
|
|
412
|
+
"files": [
|
|
413
|
+
{
|
|
414
|
+
"path": "src/components/ui/composed/dropdown-menu/actions-dropdown.tsx",
|
|
415
|
+
"exports": [
|
|
416
|
+
"ActionsDropdown"
|
|
417
|
+
]
|
|
418
|
+
}
|
|
419
|
+
],
|
|
420
|
+
"dependencies": {
|
|
421
|
+
"shadcn": [
|
|
422
|
+
"dropdown-menu",
|
|
423
|
+
"button"
|
|
424
|
+
],
|
|
425
|
+
"npm": [
|
|
426
|
+
"lucide-react"
|
|
427
|
+
],
|
|
428
|
+
"local": []
|
|
429
|
+
},
|
|
430
|
+
"i18n": {
|
|
431
|
+
"required": true,
|
|
432
|
+
"keys": [
|
|
433
|
+
"ui_open_actions"
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"name": "empty-state",
|
|
439
|
+
"type": "composed",
|
|
440
|
+
"slug": "empty",
|
|
441
|
+
"files": [
|
|
442
|
+
{
|
|
443
|
+
"path": "src/components/ui/composed/empty/empty-state.tsx",
|
|
444
|
+
"exports": [
|
|
445
|
+
"EmptyState"
|
|
446
|
+
]
|
|
447
|
+
}
|
|
448
|
+
],
|
|
449
|
+
"dependencies": {
|
|
450
|
+
"shadcn": [
|
|
451
|
+
"empty"
|
|
452
|
+
],
|
|
453
|
+
"npm": [],
|
|
454
|
+
"local": [
|
|
455
|
+
"@/lib/utils"
|
|
456
|
+
]
|
|
457
|
+
},
|
|
458
|
+
"i18n": {
|
|
459
|
+
"required": false,
|
|
460
|
+
"keys": []
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"name": "labeled-input",
|
|
465
|
+
"type": "composed",
|
|
466
|
+
"slug": "input",
|
|
467
|
+
"files": [
|
|
468
|
+
{
|
|
469
|
+
"path": "src/components/ui/composed/input/labeled-input.tsx",
|
|
470
|
+
"exports": [
|
|
471
|
+
"LabeledInput"
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
],
|
|
475
|
+
"dependencies": {
|
|
476
|
+
"shadcn": [
|
|
477
|
+
"input",
|
|
478
|
+
"label"
|
|
479
|
+
],
|
|
480
|
+
"npm": [],
|
|
481
|
+
"local": [
|
|
482
|
+
"@/lib/utils"
|
|
483
|
+
]
|
|
484
|
+
},
|
|
485
|
+
"i18n": {
|
|
486
|
+
"required": false,
|
|
487
|
+
"keys": []
|
|
488
|
+
},
|
|
489
|
+
"controller": "InputFieldController",
|
|
490
|
+
"knownIssues": [
|
|
491
|
+
"labeled-input.tsx imports `type InputProps` from '@/components/ui/input', but src/components/ui/input.tsx only exports `Input` (no InputProps type). This is a pre-existing type error in the boilerplate itself (reproduced by `bun run type-check`), not something introduced by the registry. A CLI copying this component will carry the same error until the source file is fixed upstream."
|
|
492
|
+
]
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"name": "search-input",
|
|
496
|
+
"type": "composed",
|
|
497
|
+
"slug": "input-group",
|
|
498
|
+
"files": [
|
|
499
|
+
{
|
|
500
|
+
"path": "src/components/ui/composed/input-group/search-input.tsx",
|
|
501
|
+
"exports": [
|
|
502
|
+
"SearchInput"
|
|
503
|
+
]
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
"dependencies": {
|
|
507
|
+
"shadcn": [
|
|
508
|
+
"input-group",
|
|
509
|
+
"button",
|
|
510
|
+
"input",
|
|
511
|
+
"textarea"
|
|
512
|
+
],
|
|
513
|
+
"npm": [
|
|
514
|
+
"lucide-react"
|
|
515
|
+
],
|
|
516
|
+
"local": [
|
|
517
|
+
"@/lib/utils"
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
"i18n": {
|
|
521
|
+
"required": true,
|
|
522
|
+
"keys": [
|
|
523
|
+
"ui_clear_search",
|
|
524
|
+
"ui_search_placeholder"
|
|
525
|
+
]
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
"name": "otp-field",
|
|
530
|
+
"type": "composed",
|
|
531
|
+
"slug": "input-otp",
|
|
532
|
+
"files": [
|
|
533
|
+
{
|
|
534
|
+
"path": "src/components/ui/composed/input-otp/otp-field.tsx",
|
|
535
|
+
"exports": [
|
|
536
|
+
"OtpField"
|
|
537
|
+
]
|
|
538
|
+
}
|
|
539
|
+
],
|
|
540
|
+
"dependencies": {
|
|
541
|
+
"shadcn": [
|
|
542
|
+
"input-otp",
|
|
543
|
+
"label"
|
|
544
|
+
],
|
|
545
|
+
"npm": [
|
|
546
|
+
"input-otp"
|
|
547
|
+
],
|
|
548
|
+
"local": [
|
|
549
|
+
"@/lib/utils"
|
|
550
|
+
]
|
|
551
|
+
},
|
|
552
|
+
"i18n": {
|
|
553
|
+
"required": false,
|
|
554
|
+
"keys": []
|
|
555
|
+
},
|
|
556
|
+
"controller": "OtpFieldController"
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
"name": "site-nav",
|
|
560
|
+
"type": "composed",
|
|
561
|
+
"slug": "navigation-menu",
|
|
562
|
+
"files": [
|
|
563
|
+
{
|
|
564
|
+
"path": "src/components/ui/composed/navigation-menu/site-nav.tsx",
|
|
565
|
+
"exports": [
|
|
566
|
+
"SiteNav"
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
],
|
|
570
|
+
"dependencies": {
|
|
571
|
+
"shadcn": [
|
|
572
|
+
"navigation-menu"
|
|
573
|
+
],
|
|
574
|
+
"npm": [],
|
|
575
|
+
"local": [
|
|
576
|
+
"@/lib/utils"
|
|
577
|
+
]
|
|
578
|
+
},
|
|
579
|
+
"i18n": {
|
|
580
|
+
"required": false,
|
|
581
|
+
"keys": []
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
"name": "data-pagination",
|
|
586
|
+
"type": "composed",
|
|
587
|
+
"slug": "pagination",
|
|
588
|
+
"files": [
|
|
589
|
+
{
|
|
590
|
+
"path": "src/components/ui/composed/pagination/data-pagination.tsx",
|
|
591
|
+
"exports": [
|
|
592
|
+
"DataPagination"
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
],
|
|
596
|
+
"dependencies": {
|
|
597
|
+
"shadcn": [
|
|
598
|
+
"pagination",
|
|
599
|
+
"button"
|
|
600
|
+
],
|
|
601
|
+
"npm": [],
|
|
602
|
+
"local": [
|
|
603
|
+
"@/lib/utils"
|
|
604
|
+
]
|
|
605
|
+
},
|
|
606
|
+
"i18n": {
|
|
607
|
+
"required": true,
|
|
608
|
+
"keys": [
|
|
609
|
+
"ui_next",
|
|
610
|
+
"ui_next_page",
|
|
611
|
+
"ui_previous",
|
|
612
|
+
"ui_previous_page"
|
|
613
|
+
]
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
"name": "info-popover",
|
|
618
|
+
"type": "composed",
|
|
619
|
+
"slug": "popover",
|
|
620
|
+
"files": [
|
|
621
|
+
{
|
|
622
|
+
"path": "src/components/ui/composed/popover/info-popover.tsx",
|
|
623
|
+
"exports": [
|
|
624
|
+
"InfoPopover"
|
|
625
|
+
]
|
|
626
|
+
}
|
|
627
|
+
],
|
|
628
|
+
"dependencies": {
|
|
629
|
+
"shadcn": [
|
|
630
|
+
"popover",
|
|
631
|
+
"button"
|
|
632
|
+
],
|
|
633
|
+
"npm": [
|
|
634
|
+
"lucide-react"
|
|
635
|
+
],
|
|
636
|
+
"local": [
|
|
637
|
+
"@/lib/utils"
|
|
638
|
+
]
|
|
639
|
+
},
|
|
640
|
+
"i18n": {
|
|
641
|
+
"required": true,
|
|
642
|
+
"keys": [
|
|
643
|
+
"ui_more_information"
|
|
644
|
+
]
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
"name": "options-radio-group",
|
|
649
|
+
"type": "composed",
|
|
650
|
+
"slug": "radio-group",
|
|
651
|
+
"files": [
|
|
652
|
+
{
|
|
653
|
+
"path": "src/components/ui/composed/radio-group/options-radio-group.tsx",
|
|
654
|
+
"exports": [
|
|
655
|
+
"OptionsRadioGroup"
|
|
656
|
+
]
|
|
657
|
+
}
|
|
658
|
+
],
|
|
659
|
+
"dependencies": {
|
|
660
|
+
"shadcn": [
|
|
661
|
+
"radio-group",
|
|
662
|
+
"label"
|
|
663
|
+
],
|
|
664
|
+
"npm": [],
|
|
665
|
+
"local": [
|
|
666
|
+
"@/lib/utils"
|
|
667
|
+
]
|
|
668
|
+
},
|
|
669
|
+
"i18n": {
|
|
670
|
+
"required": false,
|
|
671
|
+
"keys": []
|
|
672
|
+
},
|
|
673
|
+
"controller": "RadioGroupController"
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
"name": "labeled-select",
|
|
677
|
+
"type": "composed",
|
|
678
|
+
"slug": "select",
|
|
679
|
+
"files": [
|
|
680
|
+
{
|
|
681
|
+
"path": "src/components/ui/composed/select/labeled-select.tsx",
|
|
682
|
+
"exports": [
|
|
683
|
+
"LabeledSelect"
|
|
684
|
+
]
|
|
685
|
+
}
|
|
686
|
+
],
|
|
687
|
+
"dependencies": {
|
|
688
|
+
"shadcn": [
|
|
689
|
+
"select",
|
|
690
|
+
"label"
|
|
691
|
+
],
|
|
692
|
+
"npm": [],
|
|
693
|
+
"local": [
|
|
694
|
+
"@/lib/utils"
|
|
695
|
+
]
|
|
696
|
+
},
|
|
697
|
+
"i18n": {
|
|
698
|
+
"required": true,
|
|
699
|
+
"keys": [
|
|
700
|
+
"ui_select_placeholder"
|
|
701
|
+
]
|
|
702
|
+
},
|
|
703
|
+
"controller": "SelectController"
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
"name": "select-groups",
|
|
707
|
+
"type": "composed",
|
|
708
|
+
"slug": "select",
|
|
709
|
+
"files": [
|
|
710
|
+
{
|
|
711
|
+
"path": "src/components/ui/composed/select/select-groups.tsx",
|
|
712
|
+
"exports": [
|
|
713
|
+
"SelectGroups"
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
],
|
|
717
|
+
"dependencies": {
|
|
718
|
+
"shadcn": [
|
|
719
|
+
"select",
|
|
720
|
+
"label"
|
|
721
|
+
],
|
|
722
|
+
"npm": [],
|
|
723
|
+
"local": [
|
|
724
|
+
"@/lib/utils"
|
|
725
|
+
]
|
|
726
|
+
},
|
|
727
|
+
"i18n": {
|
|
728
|
+
"required": true,
|
|
729
|
+
"keys": [
|
|
730
|
+
"ui_select_placeholder"
|
|
731
|
+
]
|
|
732
|
+
},
|
|
733
|
+
"controller": "SelectGroupsController"
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
"name": "filter-sheet",
|
|
737
|
+
"type": "composed",
|
|
738
|
+
"slug": "sheet",
|
|
739
|
+
"files": [
|
|
740
|
+
{
|
|
741
|
+
"path": "src/components/ui/composed/sheet/filter-sheet.tsx",
|
|
742
|
+
"exports": [
|
|
743
|
+
"FilterSheet"
|
|
744
|
+
]
|
|
745
|
+
}
|
|
746
|
+
],
|
|
747
|
+
"dependencies": {
|
|
748
|
+
"shadcn": [
|
|
749
|
+
"sheet"
|
|
750
|
+
],
|
|
751
|
+
"npm": [],
|
|
752
|
+
"local": []
|
|
753
|
+
},
|
|
754
|
+
"i18n": {
|
|
755
|
+
"required": false,
|
|
756
|
+
"keys": []
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
"name": "card-skeleton",
|
|
761
|
+
"type": "composed",
|
|
762
|
+
"slug": "skeleton",
|
|
763
|
+
"files": [
|
|
764
|
+
{
|
|
765
|
+
"path": "src/components/ui/composed/skeleton/card-skeleton.tsx",
|
|
766
|
+
"exports": [
|
|
767
|
+
"CardSkeleton"
|
|
768
|
+
]
|
|
769
|
+
}
|
|
770
|
+
],
|
|
771
|
+
"dependencies": {
|
|
772
|
+
"shadcn": [
|
|
773
|
+
"skeleton",
|
|
774
|
+
"card"
|
|
775
|
+
],
|
|
776
|
+
"npm": [],
|
|
777
|
+
"local": [
|
|
778
|
+
"@/lib/utils"
|
|
779
|
+
]
|
|
780
|
+
},
|
|
781
|
+
"i18n": {
|
|
782
|
+
"required": false,
|
|
783
|
+
"keys": []
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
"name": "simple-data-table",
|
|
788
|
+
"type": "composed",
|
|
789
|
+
"slug": "table",
|
|
790
|
+
"files": [
|
|
791
|
+
{
|
|
792
|
+
"path": "src/components/ui/composed/table/simple-data-table.tsx",
|
|
793
|
+
"exports": [
|
|
794
|
+
"SimpleDataTable"
|
|
795
|
+
]
|
|
796
|
+
}
|
|
797
|
+
],
|
|
798
|
+
"dependencies": {
|
|
799
|
+
"shadcn": [
|
|
800
|
+
"table"
|
|
801
|
+
],
|
|
802
|
+
"npm": [],
|
|
803
|
+
"local": [
|
|
804
|
+
"@/lib/utils"
|
|
805
|
+
]
|
|
806
|
+
},
|
|
807
|
+
"i18n": {
|
|
808
|
+
"required": true,
|
|
809
|
+
"keys": [
|
|
810
|
+
"ui_no_results"
|
|
811
|
+
]
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
"name": "icon-tabs",
|
|
816
|
+
"type": "composed",
|
|
817
|
+
"slug": "tabs",
|
|
818
|
+
"files": [
|
|
819
|
+
{
|
|
820
|
+
"path": "src/components/ui/composed/tabs/icon-tabs.tsx",
|
|
821
|
+
"exports": [
|
|
822
|
+
"IconTabs"
|
|
823
|
+
]
|
|
824
|
+
}
|
|
825
|
+
],
|
|
826
|
+
"dependencies": {
|
|
827
|
+
"shadcn": [
|
|
828
|
+
"tabs"
|
|
829
|
+
],
|
|
830
|
+
"npm": [],
|
|
831
|
+
"local": [
|
|
832
|
+
"@/lib/utils"
|
|
833
|
+
]
|
|
834
|
+
},
|
|
835
|
+
"i18n": {
|
|
836
|
+
"required": false,
|
|
837
|
+
"keys": []
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
"name": "labeled-textarea",
|
|
842
|
+
"type": "composed",
|
|
843
|
+
"slug": "textarea",
|
|
844
|
+
"files": [
|
|
845
|
+
{
|
|
846
|
+
"path": "src/components/ui/composed/textarea/labeled-textarea.tsx",
|
|
847
|
+
"exports": [
|
|
848
|
+
"LabeledTextarea"
|
|
849
|
+
]
|
|
850
|
+
}
|
|
851
|
+
],
|
|
852
|
+
"dependencies": {
|
|
853
|
+
"shadcn": [
|
|
854
|
+
"textarea",
|
|
855
|
+
"label"
|
|
856
|
+
],
|
|
857
|
+
"npm": [],
|
|
858
|
+
"local": [
|
|
859
|
+
"@/lib/utils"
|
|
860
|
+
]
|
|
861
|
+
},
|
|
862
|
+
"i18n": {
|
|
863
|
+
"required": false,
|
|
864
|
+
"keys": []
|
|
865
|
+
},
|
|
866
|
+
"controller": "TextareaController"
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
"name": "icon-tooltip",
|
|
870
|
+
"type": "composed",
|
|
871
|
+
"slug": "tooltip",
|
|
872
|
+
"files": [
|
|
873
|
+
{
|
|
874
|
+
"path": "src/components/ui/composed/tooltip/icon-tooltip.tsx",
|
|
875
|
+
"exports": [
|
|
876
|
+
"IconTooltip"
|
|
877
|
+
]
|
|
878
|
+
}
|
|
879
|
+
],
|
|
880
|
+
"dependencies": {
|
|
881
|
+
"shadcn": [
|
|
882
|
+
"tooltip"
|
|
883
|
+
],
|
|
884
|
+
"npm": [],
|
|
885
|
+
"local": [
|
|
886
|
+
"@/lib/utils"
|
|
887
|
+
]
|
|
888
|
+
},
|
|
889
|
+
"i18n": {
|
|
890
|
+
"required": false,
|
|
891
|
+
"keys": []
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
"name": "file-dropzone",
|
|
896
|
+
"type": "composed",
|
|
897
|
+
"slug": "upload",
|
|
898
|
+
"files": [
|
|
899
|
+
{
|
|
900
|
+
"path": "src/components/ui/composed/upload/file-dropzone.tsx",
|
|
901
|
+
"exports": [
|
|
902
|
+
"FileDropzone"
|
|
903
|
+
]
|
|
904
|
+
}
|
|
905
|
+
],
|
|
906
|
+
"dependencies": {
|
|
907
|
+
"shadcn": [
|
|
908
|
+
"button",
|
|
909
|
+
"input",
|
|
910
|
+
"label"
|
|
911
|
+
],
|
|
912
|
+
"npm": [
|
|
913
|
+
"lucide-react"
|
|
914
|
+
],
|
|
915
|
+
"local": [
|
|
916
|
+
"@/lib/utils"
|
|
917
|
+
]
|
|
918
|
+
},
|
|
919
|
+
"i18n": {
|
|
920
|
+
"required": true,
|
|
921
|
+
"keys": [
|
|
922
|
+
"ui_browse_files",
|
|
923
|
+
"ui_dropzone_prompt",
|
|
924
|
+
"ui_max_size",
|
|
925
|
+
"ui_remove_file"
|
|
926
|
+
]
|
|
927
|
+
},
|
|
928
|
+
"controller": "FileDropzoneController"
|
|
929
|
+
}
|
|
930
|
+
],
|
|
931
|
+
"featureRegistry": [
|
|
932
|
+
{
|
|
933
|
+
"name": "login",
|
|
934
|
+
"description": "Email/password login form, posting to the auth API and redirecting into the protected area on success.",
|
|
935
|
+
"frameworks": {
|
|
936
|
+
"tanstack": {
|
|
937
|
+
"routeScope": "public",
|
|
938
|
+
"files": [
|
|
939
|
+
"src/routes/(public-pages)/(auth)/route.tsx",
|
|
940
|
+
"src/routes/(public-pages)/(auth)/login.tsx",
|
|
941
|
+
"src/components/screens/login/index.ts",
|
|
942
|
+
"src/components/screens/login/login-screen.tsx",
|
|
943
|
+
"src/components/screens/login/login-form.tsx",
|
|
944
|
+
"src/components/screens/login/login-schema.ts",
|
|
945
|
+
"src/components/layouts/index.ts",
|
|
946
|
+
"src/components/layouts/auth-layout.tsx",
|
|
947
|
+
"src/apis/auth/client/login.ts",
|
|
948
|
+
"src/apis/auth/client/register.ts",
|
|
949
|
+
"src/apis/auth/client/index.ts",
|
|
950
|
+
"src/apis/auth/client/get-profile.ts",
|
|
951
|
+
"src/apis/auth/hooks/index.ts",
|
|
952
|
+
"src/apis/auth/hooks/mutations/index.ts",
|
|
953
|
+
"src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
954
|
+
"src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
955
|
+
"src/apis/query-keys/auth.ts",
|
|
956
|
+
"src/store/auth-state.ts",
|
|
957
|
+
"src/lib/auth/token-cookie.ts",
|
|
958
|
+
"src/@types/user.ts",
|
|
959
|
+
"src/components/common/controllers/input-field-controller.tsx",
|
|
960
|
+
"src/components/ui/composed/button/loading-button.tsx",
|
|
961
|
+
"src/hooks/use-response-error.ts"
|
|
962
|
+
],
|
|
963
|
+
"i18nKeys": [
|
|
964
|
+
"login_page",
|
|
965
|
+
"login_title",
|
|
966
|
+
"login_intro",
|
|
967
|
+
"login_email_label",
|
|
968
|
+
"login_email_placeholder",
|
|
969
|
+
"login_password_label",
|
|
970
|
+
"login_password_placeholder",
|
|
971
|
+
"login_submitting",
|
|
972
|
+
"login_submit",
|
|
973
|
+
"login_success",
|
|
974
|
+
"validation_email_required",
|
|
975
|
+
"validation_email_invalid",
|
|
976
|
+
"validation_password_required"
|
|
977
|
+
]
|
|
978
|
+
},
|
|
979
|
+
"nextjs": {
|
|
980
|
+
"routeScope": "public",
|
|
981
|
+
"sourcePrefix": "cli/templates/nextjs/",
|
|
982
|
+
"files": [
|
|
983
|
+
"cli/templates/nextjs/src/app/(public)/(auth)/layout.tsx",
|
|
984
|
+
"cli/templates/nextjs/src/app/(public)/(auth)/login/page.tsx",
|
|
985
|
+
"src/components/screens/login/index.ts",
|
|
986
|
+
"src/components/screens/login/login-screen.tsx",
|
|
987
|
+
"src/components/screens/login/login-form.tsx",
|
|
988
|
+
"src/components/screens/login/login-schema.ts",
|
|
989
|
+
"src/components/layouts/index.ts",
|
|
990
|
+
"src/components/layouts/auth-layout.tsx",
|
|
991
|
+
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
992
|
+
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
993
|
+
"src/apis/auth/client/register.ts",
|
|
994
|
+
"src/apis/auth/client/login.ts",
|
|
995
|
+
"src/apis/auth/client/index.ts",
|
|
996
|
+
"src/apis/auth/client/get-profile.ts",
|
|
997
|
+
"src/apis/auth/hooks/index.ts",
|
|
998
|
+
"src/apis/auth/hooks/mutations/index.ts",
|
|
999
|
+
"src/apis/query-keys/auth.ts",
|
|
1000
|
+
"src/store/auth-state.ts",
|
|
1001
|
+
"src/lib/auth/token-cookie.ts",
|
|
1002
|
+
"src/lib/query-client.ts",
|
|
1003
|
+
"src/@types/user.ts",
|
|
1004
|
+
"src/components/common/controllers/input-field-controller.tsx",
|
|
1005
|
+
"src/components/ui/composed/button/loading-button.tsx",
|
|
1006
|
+
"src/hooks/use-response-error.ts"
|
|
1007
|
+
],
|
|
1008
|
+
"i18nKeys": [
|
|
1009
|
+
"login_page",
|
|
1010
|
+
"login_title",
|
|
1011
|
+
"login_intro",
|
|
1012
|
+
"login_email_label",
|
|
1013
|
+
"login_email_placeholder",
|
|
1014
|
+
"login_password_label",
|
|
1015
|
+
"login_password_placeholder",
|
|
1016
|
+
"login_submitting",
|
|
1017
|
+
"login_submit",
|
|
1018
|
+
"login_success",
|
|
1019
|
+
"validation_email_required",
|
|
1020
|
+
"validation_email_invalid",
|
|
1021
|
+
"validation_password_required"
|
|
1022
|
+
]
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
"name": "signup",
|
|
1028
|
+
"description": "Name/email/password registration form, posting to the auth API and auto-logging in on success.",
|
|
1029
|
+
"frameworks": {
|
|
1030
|
+
"tanstack": {
|
|
1031
|
+
"routeScope": "public",
|
|
1032
|
+
"files": [
|
|
1033
|
+
"src/routes/(public-pages)/(auth)/route.tsx",
|
|
1034
|
+
"src/routes/(public-pages)/(auth)/signup.tsx",
|
|
1035
|
+
"src/components/screens/signup/index.ts",
|
|
1036
|
+
"src/components/screens/signup/signup-screen.tsx",
|
|
1037
|
+
"src/components/screens/signup/signup-form.tsx",
|
|
1038
|
+
"src/components/screens/signup/signup-schema.ts",
|
|
1039
|
+
"src/components/layouts/index.ts",
|
|
1040
|
+
"src/components/layouts/auth-layout.tsx",
|
|
1041
|
+
"src/apis/auth/client/register.ts",
|
|
1042
|
+
"src/apis/auth/client/login.ts",
|
|
1043
|
+
"src/apis/auth/client/index.ts",
|
|
1044
|
+
"src/apis/auth/client/get-profile.ts",
|
|
1045
|
+
"src/apis/auth/hooks/index.ts",
|
|
1046
|
+
"src/apis/auth/hooks/mutations/index.ts",
|
|
1047
|
+
"src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
1048
|
+
"src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
1049
|
+
"src/apis/query-keys/auth.ts",
|
|
1050
|
+
"src/store/auth-state.ts",
|
|
1051
|
+
"src/lib/auth/token-cookie.ts",
|
|
1052
|
+
"src/@types/user.ts",
|
|
1053
|
+
"src/components/common/controllers/input-field-controller.tsx",
|
|
1054
|
+
"src/components/ui/composed/button/loading-button.tsx",
|
|
1055
|
+
"src/hooks/use-response-error.ts"
|
|
1056
|
+
],
|
|
1057
|
+
"i18nKeys": [
|
|
1058
|
+
"signup_page",
|
|
1059
|
+
"signup_title",
|
|
1060
|
+
"signup_intro",
|
|
1061
|
+
"signup_name_label",
|
|
1062
|
+
"signup_name_placeholder",
|
|
1063
|
+
"signup_email_label",
|
|
1064
|
+
"signup_email_placeholder",
|
|
1065
|
+
"signup_password_label",
|
|
1066
|
+
"signup_password_placeholder",
|
|
1067
|
+
"signup_confirm_password_label",
|
|
1068
|
+
"signup_confirm_password_placeholder",
|
|
1069
|
+
"signup_submitting",
|
|
1070
|
+
"signup_submit",
|
|
1071
|
+
"signup_success",
|
|
1072
|
+
"validation_name_required",
|
|
1073
|
+
"validation_email_required",
|
|
1074
|
+
"validation_email_invalid",
|
|
1075
|
+
"validation_password_required",
|
|
1076
|
+
"validation_confirm_password_required",
|
|
1077
|
+
"validation_password_mismatch"
|
|
1078
|
+
]
|
|
1079
|
+
},
|
|
1080
|
+
"nextjs": {
|
|
1081
|
+
"routeScope": "public",
|
|
1082
|
+
"sourcePrefix": "cli/templates/nextjs/",
|
|
1083
|
+
"files": [
|
|
1084
|
+
"cli/templates/nextjs/src/app/(public)/(auth)/layout.tsx",
|
|
1085
|
+
"cli/templates/nextjs/src/app/(public)/(auth)/signup/page.tsx",
|
|
1086
|
+
"src/components/screens/signup/index.ts",
|
|
1087
|
+
"src/components/screens/signup/signup-screen.tsx",
|
|
1088
|
+
"src/components/screens/signup/signup-form.tsx",
|
|
1089
|
+
"src/components/screens/signup/signup-schema.ts",
|
|
1090
|
+
"src/components/layouts/index.ts",
|
|
1091
|
+
"src/components/layouts/auth-layout.tsx",
|
|
1092
|
+
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-login.mutation.ts",
|
|
1093
|
+
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-register.mutation.ts",
|
|
1094
|
+
"src/apis/auth/client/register.ts",
|
|
1095
|
+
"src/apis/auth/client/login.ts",
|
|
1096
|
+
"src/apis/auth/client/index.ts",
|
|
1097
|
+
"src/apis/auth/client/get-profile.ts",
|
|
1098
|
+
"src/apis/auth/hooks/index.ts",
|
|
1099
|
+
"src/apis/auth/hooks/mutations/index.ts",
|
|
1100
|
+
"src/apis/query-keys/auth.ts",
|
|
1101
|
+
"src/store/auth-state.ts",
|
|
1102
|
+
"src/lib/auth/token-cookie.ts",
|
|
1103
|
+
"src/lib/query-client.ts",
|
|
1104
|
+
"src/@types/user.ts",
|
|
1105
|
+
"src/components/common/controllers/input-field-controller.tsx",
|
|
1106
|
+
"src/components/ui/composed/button/loading-button.tsx",
|
|
1107
|
+
"src/hooks/use-response-error.ts"
|
|
1108
|
+
],
|
|
1109
|
+
"i18nKeys": [
|
|
1110
|
+
"signup_page",
|
|
1111
|
+
"signup_title",
|
|
1112
|
+
"signup_intro",
|
|
1113
|
+
"signup_name_label",
|
|
1114
|
+
"signup_name_placeholder",
|
|
1115
|
+
"signup_email_label",
|
|
1116
|
+
"signup_email_placeholder",
|
|
1117
|
+
"signup_password_label",
|
|
1118
|
+
"signup_password_placeholder",
|
|
1119
|
+
"signup_confirm_password_label",
|
|
1120
|
+
"signup_confirm_password_placeholder",
|
|
1121
|
+
"signup_submitting",
|
|
1122
|
+
"signup_submit",
|
|
1123
|
+
"signup_success",
|
|
1124
|
+
"validation_name_required",
|
|
1125
|
+
"validation_email_required",
|
|
1126
|
+
"validation_email_invalid",
|
|
1127
|
+
"validation_password_required",
|
|
1128
|
+
"validation_confirm_password_required",
|
|
1129
|
+
"validation_password_mismatch"
|
|
1130
|
+
]
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
"name": "dashboard",
|
|
1136
|
+
"description": "Protected landing screen behind the auth guard, with the current profile fetched via React Query into the auth store.",
|
|
1137
|
+
"frameworks": {
|
|
1138
|
+
"tanstack": {
|
|
1139
|
+
"routeScope": "protected",
|
|
1140
|
+
"files": [
|
|
1141
|
+
"src/routes/(protected-pages)/route.tsx",
|
|
1142
|
+
"src/routes/(protected-pages)/dashboard.tsx",
|
|
1143
|
+
"src/components/screens/dashboard/index.ts",
|
|
1144
|
+
"src/components/screens/dashboard/dashboard-screen.tsx",
|
|
1145
|
+
"src/components/layouts/index.ts",
|
|
1146
|
+
"src/components/layouts/dashboard-layout.tsx",
|
|
1147
|
+
"src/components/layouts/dashboard-sidebar.tsx",
|
|
1148
|
+
"src/apis/auth/client/get-profile.ts",
|
|
1149
|
+
"src/apis/auth/client/login.ts",
|
|
1150
|
+
"src/apis/auth/client/register.ts",
|
|
1151
|
+
"src/apis/auth/client/index.ts",
|
|
1152
|
+
"src/apis/query-keys/auth.ts",
|
|
1153
|
+
"src/store/auth-state.ts",
|
|
1154
|
+
"src/lib/auth/token-cookie.ts",
|
|
1155
|
+
"src/@types/user.ts"
|
|
1156
|
+
],
|
|
1157
|
+
"i18nKeys": [
|
|
1158
|
+
"dashboard_page",
|
|
1159
|
+
"dashboard_title",
|
|
1160
|
+
"dashboard_intro",
|
|
1161
|
+
"dashboard_signed_in_as",
|
|
1162
|
+
"dashboard_logout"
|
|
1163
|
+
]
|
|
1164
|
+
},
|
|
1165
|
+
"nextjs": {
|
|
1166
|
+
"routeScope": "protected",
|
|
1167
|
+
"sourcePrefix": "cli/templates/nextjs/",
|
|
1168
|
+
"files": [
|
|
1169
|
+
"cli/templates/nextjs/src/app/(protected)/layout.tsx",
|
|
1170
|
+
"cli/templates/nextjs/src/app/(protected)/dashboard/page.tsx",
|
|
1171
|
+
"cli/templates/nextjs/src/components/layouts/dashboard-sidebar.tsx",
|
|
1172
|
+
"src/components/screens/dashboard/index.ts",
|
|
1173
|
+
"src/components/screens/dashboard/dashboard-screen.tsx",
|
|
1174
|
+
"src/components/layouts/index.ts",
|
|
1175
|
+
"src/components/layouts/dashboard-layout.tsx",
|
|
1176
|
+
"src/apis/auth/client/get-profile.ts",
|
|
1177
|
+
"src/apis/auth/client/login.ts",
|
|
1178
|
+
"src/apis/auth/client/register.ts",
|
|
1179
|
+
"src/apis/auth/client/index.ts",
|
|
1180
|
+
"src/apis/query-keys/auth.ts",
|
|
1181
|
+
"src/store/auth-state.ts",
|
|
1182
|
+
"src/lib/auth/token-cookie.ts",
|
|
1183
|
+
"src/lib/query-client.ts",
|
|
1184
|
+
"src/@types/user.ts"
|
|
1185
|
+
],
|
|
1186
|
+
"i18nKeys": [
|
|
1187
|
+
"dashboard_page",
|
|
1188
|
+
"dashboard_title",
|
|
1189
|
+
"dashboard_intro",
|
|
1190
|
+
"dashboard_signed_in_as",
|
|
1191
|
+
"dashboard_logout"
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
"name": "about",
|
|
1198
|
+
"description": "Static public \"About\" page.",
|
|
1199
|
+
"frameworks": {
|
|
1200
|
+
"tanstack": {
|
|
1201
|
+
"routeScope": "public",
|
|
1202
|
+
"files": [
|
|
1203
|
+
"src/routes/(public-pages)/about.tsx",
|
|
1204
|
+
"src/components/screens/about/index.ts",
|
|
1205
|
+
"src/components/screens/about/about-screen.tsx"
|
|
1206
|
+
],
|
|
1207
|
+
"i18nKeys": [
|
|
1208
|
+
"about_page",
|
|
1209
|
+
"about_title",
|
|
1210
|
+
"about_intro"
|
|
1211
|
+
]
|
|
1212
|
+
},
|
|
1213
|
+
"nextjs": {
|
|
1214
|
+
"routeScope": "public",
|
|
1215
|
+
"sourcePrefix": "cli/templates/nextjs/",
|
|
1216
|
+
"files": [
|
|
1217
|
+
"cli/templates/nextjs/src/app/(public)/about/page.tsx",
|
|
1218
|
+
"src/components/screens/about/index.ts",
|
|
1219
|
+
"src/components/screens/about/about-screen.tsx"
|
|
1220
|
+
],
|
|
1221
|
+
"i18nKeys": [
|
|
1222
|
+
"about_page",
|
|
1223
|
+
"about_title",
|
|
1224
|
+
"about_intro"
|
|
1225
|
+
]
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
"name": "contact",
|
|
1231
|
+
"description": "Static public \"Contact\" page.",
|
|
1232
|
+
"frameworks": {
|
|
1233
|
+
"tanstack": {
|
|
1234
|
+
"routeScope": "public",
|
|
1235
|
+
"files": [
|
|
1236
|
+
"src/routes/(public-pages)/contact.tsx",
|
|
1237
|
+
"src/components/screens/contact/index.ts",
|
|
1238
|
+
"src/components/screens/contact/contact-screen.tsx"
|
|
1239
|
+
],
|
|
1240
|
+
"i18nKeys": [
|
|
1241
|
+
"contact_page",
|
|
1242
|
+
"contact_title",
|
|
1243
|
+
"contact_intro"
|
|
1244
|
+
]
|
|
1245
|
+
},
|
|
1246
|
+
"nextjs": {
|
|
1247
|
+
"routeScope": "public",
|
|
1248
|
+
"sourcePrefix": "cli/templates/nextjs/",
|
|
1249
|
+
"files": [
|
|
1250
|
+
"cli/templates/nextjs/src/app/(public)/contact/page.tsx",
|
|
1251
|
+
"src/components/screens/contact/index.ts",
|
|
1252
|
+
"src/components/screens/contact/contact-screen.tsx"
|
|
1253
|
+
],
|
|
1254
|
+
"i18nKeys": [
|
|
1255
|
+
"contact_page",
|
|
1256
|
+
"contact_title",
|
|
1257
|
+
"contact_intro"
|
|
1258
|
+
]
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
],
|
|
1263
|
+
"files": {
|
|
1264
|
+
"src/components/ui/accordion.tsx": "import { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { ChevronDownIcon, ChevronUpIcon } from \"lucide-react\"\r\n\r\nfunction Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {\r\n return (\r\n <AccordionPrimitive.Root\r\n data-slot=\"accordion\"\r\n className={cn(\"flex w-full flex-col\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {\r\n return (\r\n <AccordionPrimitive.Item\r\n data-slot=\"accordion-item\"\r\n className={cn(\"not-last:border-b\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AccordionTrigger({\r\n className,\r\n children,\r\n ...props\r\n}: AccordionPrimitive.Trigger.Props) {\r\n return (\r\n <AccordionPrimitive.Header className=\"flex\">\r\n <AccordionPrimitive.Trigger\r\n data-slot=\"accordion-trigger\"\r\n className={cn(\r\n \"group/accordion-trigger relative flex flex-1 items-start justify-between rounded-md border border-transparent py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <ChevronDownIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\" />\r\n <ChevronUpIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\" />\r\n </AccordionPrimitive.Trigger>\r\n </AccordionPrimitive.Header>\r\n )\r\n}\r\n\r\nfunction AccordionContent({\r\n className,\r\n children,\r\n ...props\r\n}: AccordionPrimitive.Panel.Props) {\r\n return (\r\n <AccordionPrimitive.Panel\r\n data-slot=\"accordion-content\"\r\n className=\"overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up\"\r\n {...props}\r\n >\r\n <div\r\n className={cn(\r\n \"h-(--accordion-panel-height) pt-0 pb-4 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\r\n className\r\n )}\r\n >\r\n {children}\r\n </div>\r\n </AccordionPrimitive.Panel>\r\n )\r\n}\r\n\r\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\r\n",
|
|
1265
|
+
"src/components/ui/alert.tsx": "import * as React from \"react\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nconst alertVariants = cva(\r\n \"group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-start text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pe-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4\",\r\n {\r\n variants: {\r\n variant: {\r\n default: \"bg-card text-card-foreground\",\r\n destructive:\r\n \"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"default\",\r\n },\r\n }\r\n)\r\n\r\nfunction Alert({\r\n className,\r\n variant,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & VariantProps<typeof alertVariants>) {\r\n return (\r\n <div\r\n data-slot=\"alert\"\r\n role=\"alert\"\r\n className={cn(alertVariants({ variant }), className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AlertTitle({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"alert-title\"\r\n className={cn(\r\n \"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AlertDescription({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"alert-description\"\r\n className={cn(\r\n \"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AlertAction({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"alert-action\"\r\n className={cn(\"absolute top-2.5 end-3\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Alert, AlertTitle, AlertDescription, AlertAction }\r\n",
|
|
1266
|
+
"src/components/ui/avatar.tsx": "import * as React from \"react\"\r\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Avatar({\r\n className,\r\n size = \"default\",\r\n ...props\r\n}: AvatarPrimitive.Root.Props & {\r\n size?: \"default\" | \"sm\" | \"lg\"\r\n}) {\r\n return (\r\n <AvatarPrimitive.Root\r\n data-slot=\"avatar\"\r\n data-size={size}\r\n className={cn(\r\n \"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\r\n return (\r\n <AvatarPrimitive.Image\r\n data-slot=\"avatar-image\"\r\n className={cn(\r\n \"aspect-square size-full rounded-full object-cover\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AvatarFallback({\r\n className,\r\n ...props\r\n}: AvatarPrimitive.Fallback.Props) {\r\n return (\r\n <AvatarPrimitive.Fallback\r\n data-slot=\"avatar-fallback\"\r\n className={cn(\r\n \"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<\"span\">) {\r\n return (\r\n <span\r\n data-slot=\"avatar-badge\"\r\n className={cn(\r\n \"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none\",\r\n \"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden\",\r\n \"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2\",\r\n \"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"avatar-group\"\r\n className={cn(\r\n \"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction AvatarGroupCount({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"avatar-group-count\"\r\n className={cn(\r\n \"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Avatar,\r\n AvatarImage,\r\n AvatarFallback,\r\n AvatarGroup,\r\n AvatarGroupCount,\r\n AvatarBadge,\r\n}\r\n",
|
|
1267
|
+
"src/components/ui/badge.tsx": "import { mergeProps } from \"@base-ui/react/merge-props\"\r\nimport { useRender } from \"@base-ui/react/use-render\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nconst badgeVariants = cva(\r\n \"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!\",\r\n {\r\n variants: {\r\n variant: {\r\n default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\r\n secondary:\r\n \"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80\",\r\n destructive:\r\n \"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20\",\r\n outline:\r\n \"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground\",\r\n ghost:\r\n \"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50\",\r\n link: \"text-primary underline-offset-4 hover:underline\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"default\",\r\n },\r\n }\r\n)\r\n\r\nfunction Badge({\r\n className,\r\n variant = \"default\",\r\n render,\r\n ...props\r\n}: useRender.ComponentProps<\"span\"> & VariantProps<typeof badgeVariants>) {\r\n return useRender({\r\n defaultTagName: \"span\",\r\n props: mergeProps<\"span\">(\r\n {\r\n className: cn(badgeVariants({ variant }), className),\r\n },\r\n props\r\n ),\r\n render,\r\n state: {\r\n slot: \"badge\",\r\n variant,\r\n },\r\n })\r\n}\r\n\r\nexport { Badge, badgeVariants }\r\n",
|
|
1268
|
+
"src/components/ui/button.tsx": "import { Button as ButtonPrimitive } from \"@base-ui/react/button\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nconst buttonVariants = cva(\r\n \"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n {\r\n variants: {\r\n variant: {\r\n default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\r\n outline:\r\n \"border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\r\n secondary:\r\n \"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\r\n ghost:\r\n \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\r\n destructive:\r\n \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\r\n link: \"text-primary underline-offset-4 hover:underline\",\r\n },\r\n size: {\r\n default:\r\n \"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\r\n xs: \"h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\r\n sm: \"h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5\",\r\n lg: \"h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\r\n icon: \"size-9\",\r\n \"icon-xs\":\r\n \"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3\",\r\n \"icon-sm\":\r\n \"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md\",\r\n \"icon-lg\": \"size-10\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"default\",\r\n size: \"default\",\r\n },\r\n }\r\n)\r\n\r\nfunction Button({\r\n className,\r\n variant = \"default\",\r\n size = \"default\",\r\n ...props\r\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\r\n return (\r\n <ButtonPrimitive\r\n data-slot=\"button\"\r\n className={cn(buttonVariants({ variant, size, className }))}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Button, buttonVariants }\r\n",
|
|
1269
|
+
"src/components/ui/calendar.tsx": "import * as React from \"react\"\r\nimport {\r\n DayPicker,\r\n getDefaultClassNames,\r\n type DayButton,\r\n type Locale,\r\n} from \"react-day-picker\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button, buttonVariants } from \"#/components/ui/button.tsx\"\r\nimport { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from \"lucide-react\"\r\n\r\nfunction Calendar({\r\n className,\r\n classNames,\r\n showOutsideDays = true,\r\n captionLayout = \"label\",\r\n buttonVariant = \"ghost\",\r\n locale,\r\n formatters,\r\n components,\r\n ...props\r\n}: React.ComponentProps<typeof DayPicker> & {\r\n buttonVariant?: React.ComponentProps<typeof Button>[\"variant\"]\r\n}) {\r\n const defaultClassNames = getDefaultClassNames()\r\n\r\n return (\r\n <DayPicker\r\n showOutsideDays={showOutsideDays}\r\n className={cn(\r\n \"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent\",\r\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\r\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\r\n className\r\n )}\r\n captionLayout={captionLayout}\r\n locale={locale}\r\n formatters={{\r\n formatMonthDropdown: (date) =>\r\n date.toLocaleString(locale?.code, { month: \"short\" }),\r\n ...formatters,\r\n }}\r\n classNames={{\r\n root: cn(\"w-fit\", defaultClassNames.root),\r\n months: cn(\r\n \"relative flex flex-col gap-4 md:flex-row\",\r\n defaultClassNames.months\r\n ),\r\n month: cn(\"flex w-full flex-col gap-4\", defaultClassNames.month),\r\n nav: cn(\r\n \"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1\",\r\n defaultClassNames.nav\r\n ),\r\n button_previous: cn(\r\n buttonVariants({ variant: buttonVariant }),\r\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\r\n defaultClassNames.button_previous\r\n ),\r\n button_next: cn(\r\n buttonVariants({ variant: buttonVariant }),\r\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\r\n defaultClassNames.button_next\r\n ),\r\n month_caption: cn(\r\n \"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)\",\r\n defaultClassNames.month_caption\r\n ),\r\n dropdowns: cn(\r\n \"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium\",\r\n defaultClassNames.dropdowns\r\n ),\r\n dropdown_root: cn(\r\n \"relative rounded-(--cell-radius)\",\r\n defaultClassNames.dropdown_root\r\n ),\r\n dropdown: cn(\r\n \"absolute inset-0 bg-popover opacity-0\",\r\n defaultClassNames.dropdown\r\n ),\r\n caption_label: cn(\r\n \"font-medium select-none\",\r\n captionLayout === \"label\"\r\n ? \"text-sm\"\r\n : \"flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground\",\r\n defaultClassNames.caption_label\r\n ),\r\n table: \"w-full border-collapse\",\r\n weekdays: cn(\"flex\", defaultClassNames.weekdays),\r\n weekday: cn(\r\n \"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none\",\r\n defaultClassNames.weekday\r\n ),\r\n week: cn(\"mt-2 flex w-full\", defaultClassNames.week),\r\n week_number_header: cn(\r\n \"w-(--cell-size) select-none\",\r\n defaultClassNames.week_number_header\r\n ),\r\n week_number: cn(\r\n \"text-[0.8rem] text-muted-foreground select-none\",\r\n defaultClassNames.week_number\r\n ),\r\n day: cn(\r\n \"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)\",\r\n props.showWeekNumber\r\n ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)\"\r\n : \"[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)\",\r\n defaultClassNames.day\r\n ),\r\n range_start: cn(\r\n \"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted\",\r\n defaultClassNames.range_start\r\n ),\r\n range_middle: cn(\"rounded-none\", defaultClassNames.range_middle),\r\n range_end: cn(\r\n \"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted\",\r\n defaultClassNames.range_end\r\n ),\r\n today: cn(\r\n \"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none\",\r\n defaultClassNames.today\r\n ),\r\n outside: cn(\r\n \"text-muted-foreground aria-selected:text-muted-foreground\",\r\n defaultClassNames.outside\r\n ),\r\n disabled: cn(\r\n \"text-muted-foreground opacity-50\",\r\n defaultClassNames.disabled\r\n ),\r\n hidden: cn(\"invisible\", defaultClassNames.hidden),\r\n ...classNames,\r\n }}\r\n components={{\r\n Root: ({ className, rootRef, ...props }) => {\r\n return (\r\n <div\r\n data-slot=\"calendar\"\r\n ref={rootRef}\r\n className={cn(className)}\r\n {...props}\r\n />\r\n )\r\n },\r\n Chevron: ({ className, orientation, ...props }) => {\r\n if (orientation === \"left\") {\r\n return (\r\n <ChevronLeftIcon className={cn(\"size-4\", className)} {...props} />\r\n )\r\n }\r\n\r\n if (orientation === \"right\") {\r\n return (\r\n <ChevronRightIcon className={cn(\"size-4\", className)} {...props} />\r\n )\r\n }\r\n\r\n return (\r\n <ChevronDownIcon className={cn(\"size-4\", className)} {...props} />\r\n )\r\n },\r\n DayButton: ({ ...props }) => (\r\n <CalendarDayButton locale={locale} {...props} />\r\n ),\r\n WeekNumber: ({ children, ...props }) => {\r\n return (\r\n <td {...props}>\r\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">\r\n {children}\r\n </div>\r\n </td>\r\n )\r\n },\r\n ...components,\r\n }}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CalendarDayButton({\r\n className,\r\n day,\r\n modifiers,\r\n locale,\r\n ...props\r\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {\r\n const defaultClassNames = getDefaultClassNames()\r\n\r\n const ref = React.useRef<HTMLButtonElement>(null)\r\n React.useEffect(() => {\r\n if (modifiers.focused) ref.current?.focus()\r\n }, [modifiers.focused])\r\n\r\n return (\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n data-day={day.date.toLocaleDateString(locale?.code)}\r\n data-selected-single={\r\n modifiers.selected &&\r\n !modifiers.range_start &&\r\n !modifiers.range_end &&\r\n !modifiers.range_middle\r\n }\r\n data-range-start={modifiers.range_start}\r\n data-range-end={modifiers.range_end}\r\n data-range-middle={modifiers.range_middle}\r\n className={cn(\r\n \"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70\",\r\n defaultClassNames.day,\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Calendar, CalendarDayButton }\r\n",
|
|
1270
|
+
"src/components/ui/card.tsx": "import * as React from \"react\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Card({\r\n className,\r\n size = \"default\",\r\n ...props\r\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"sm\" }) {\r\n return (\r\n <div\r\n data-slot=\"card\"\r\n data-size={size}\r\n className={cn(\r\n \"group/card flex flex-col gap-6 overflow-hidden rounded-xl bg-card py-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"card-header\"\r\n className={cn(\r\n \"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"card-title\"\r\n className={cn(\r\n \"font-heading text-base leading-normal font-medium group-data-[size=sm]/card:text-sm\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"card-description\"\r\n className={cn(\"text-sm text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"card-action\"\r\n className={cn(\r\n \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"card-content\"\r\n className={cn(\"px-6 group-data-[size=sm]/card:px-4\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"card-footer\"\r\n className={cn(\r\n \"flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Card,\r\n CardHeader,\r\n CardFooter,\r\n CardTitle,\r\n CardAction,\r\n CardDescription,\r\n CardContent,\r\n}\r\n",
|
|
1271
|
+
"src/components/ui/carousel.tsx": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport useEmblaCarousel, {\r\n type UseEmblaCarouselType,\r\n} from \"embla-carousel-react\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button } from \"#/components/ui/button.tsx\"\r\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\r\n\r\ntype CarouselApi = UseEmblaCarouselType[1]\r\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\r\ntype CarouselOptions = UseCarouselParameters[0]\r\ntype CarouselPlugin = UseCarouselParameters[1]\r\n\r\ntype CarouselProps = {\r\n opts?: CarouselOptions\r\n plugins?: CarouselPlugin\r\n orientation?: \"horizontal\" | \"vertical\"\r\n setApi?: (api: CarouselApi) => void\r\n}\r\n\r\ntype CarouselContextProps = {\r\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\r\n api: ReturnType<typeof useEmblaCarousel>[1]\r\n scrollPrev: () => void\r\n scrollNext: () => void\r\n canScrollPrev: boolean\r\n canScrollNext: boolean\r\n} & CarouselProps\r\n\r\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\r\n\r\nfunction useCarousel() {\r\n const context = React.useContext(CarouselContext)\r\n\r\n if (!context) {\r\n throw new Error(\"useCarousel must be used within a <Carousel />\")\r\n }\r\n\r\n return context\r\n}\r\n\r\nfunction Carousel({\r\n orientation = \"horizontal\",\r\n opts,\r\n setApi,\r\n plugins,\r\n className,\r\n children,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & CarouselProps) {\r\n const [carouselRef, api] = useEmblaCarousel(\r\n {\r\n ...opts,\r\n axis: orientation === \"horizontal\" ? \"x\" : \"y\",\r\n },\r\n plugins\r\n )\r\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\r\n const [canScrollNext, setCanScrollNext] = React.useState(false)\r\n\r\n const onSelect = React.useCallback((api: CarouselApi) => {\r\n if (!api) return\r\n setCanScrollPrev(api.canScrollPrev())\r\n setCanScrollNext(api.canScrollNext())\r\n }, [])\r\n\r\n const scrollPrev = React.useCallback(() => {\r\n api?.scrollPrev()\r\n }, [api])\r\n\r\n const scrollNext = React.useCallback(() => {\r\n api?.scrollNext()\r\n }, [api])\r\n\r\n const handleKeyDown = React.useCallback(\r\n (event: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (event.key === \"ArrowLeft\") {\r\n event.preventDefault()\r\n scrollPrev()\r\n } else if (event.key === \"ArrowRight\") {\r\n event.preventDefault()\r\n scrollNext()\r\n }\r\n },\r\n [scrollPrev, scrollNext]\r\n )\r\n\r\n React.useEffect(() => {\r\n if (!api || !setApi) return\r\n setApi(api)\r\n }, [api, setApi])\r\n\r\n React.useEffect(() => {\r\n if (!api) return\r\n onSelect(api)\r\n api.on(\"reInit\", onSelect)\r\n api.on(\"select\", onSelect)\r\n\r\n return () => {\r\n api?.off(\"select\", onSelect)\r\n }\r\n }, [api, onSelect])\r\n\r\n return (\r\n <CarouselContext.Provider\r\n value={{\r\n carouselRef,\r\n api: api,\r\n opts,\r\n orientation:\r\n orientation || (opts?.axis === \"y\" ? \"vertical\" : \"horizontal\"),\r\n scrollPrev,\r\n scrollNext,\r\n canScrollPrev,\r\n canScrollNext,\r\n }}\r\n >\r\n <div\r\n onKeyDownCapture={handleKeyDown}\r\n className={cn(\"relative\", className)}\r\n role=\"region\"\r\n aria-roledescription=\"carousel\"\r\n data-slot=\"carousel\"\r\n {...props}\r\n >\r\n {children}\r\n </div>\r\n </CarouselContext.Provider>\r\n )\r\n}\r\n\r\nfunction CarouselContent({ className, ...props }: React.ComponentProps<\"div\">) {\r\n const { carouselRef, orientation } = useCarousel()\r\n\r\n return (\r\n <div\r\n ref={carouselRef}\r\n className=\"overflow-hidden\"\r\n data-slot=\"carousel-content\"\r\n >\r\n <div\r\n className={cn(\r\n \"flex\",\r\n orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nfunction CarouselItem({ className, ...props }: React.ComponentProps<\"div\">) {\r\n const { orientation } = useCarousel()\r\n\r\n return (\r\n <div\r\n role=\"group\"\r\n aria-roledescription=\"slide\"\r\n data-slot=\"carousel-item\"\r\n className={cn(\r\n \"min-w-0 shrink-0 grow-0 basis-full\",\r\n orientation === \"horizontal\" ? \"pl-4\" : \"pt-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CarouselPrevious({\r\n className,\r\n variant = \"outline\",\r\n size = \"icon-sm\",\r\n ...props\r\n}: React.ComponentProps<typeof Button>) {\r\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\r\n\r\n return (\r\n <Button\r\n data-slot=\"carousel-previous\"\r\n variant={variant}\r\n size={size}\r\n className={cn(\r\n \"absolute touch-manipulation rounded-full\",\r\n orientation === \"horizontal\"\r\n ? \"top-1/2 -left-12 -translate-y-1/2\"\r\n : \"-top-12 left-1/2 -translate-x-1/2 rotate-90\",\r\n className\r\n )}\r\n disabled={!canScrollPrev}\r\n onClick={scrollPrev}\r\n {...props}\r\n >\r\n <ChevronLeftIcon />\r\n <span className=\"sr-only\">Previous slide</span>\r\n </Button>\r\n )\r\n}\r\n\r\nfunction CarouselNext({\r\n className,\r\n variant = \"outline\",\r\n size = \"icon-sm\",\r\n ...props\r\n}: React.ComponentProps<typeof Button>) {\r\n const { orientation, scrollNext, canScrollNext } = useCarousel()\r\n\r\n return (\r\n <Button\r\n data-slot=\"carousel-next\"\r\n variant={variant}\r\n size={size}\r\n className={cn(\r\n \"absolute touch-manipulation rounded-full\",\r\n orientation === \"horizontal\"\r\n ? \"top-1/2 -right-12 -translate-y-1/2\"\r\n : \"-bottom-12 left-1/2 -translate-x-1/2 rotate-90\",\r\n className\r\n )}\r\n disabled={!canScrollNext}\r\n onClick={scrollNext}\r\n {...props}\r\n >\r\n <ChevronRightIcon />\r\n <span className=\"sr-only\">Next slide</span>\r\n </Button>\r\n )\r\n}\r\n\r\nexport {\r\n type CarouselApi,\r\n Carousel,\r\n CarouselContent,\r\n CarouselItem,\r\n CarouselPrevious,\r\n CarouselNext,\r\n useCarousel,\r\n}\r\n",
|
|
1272
|
+
"src/components/ui/checkbox.tsx": "\"use client\"\r\n\r\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { CheckIcon } from \"lucide-react\"\r\n\r\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\r\n return (\r\n <CheckboxPrimitive.Root\r\n data-slot=\"checkbox\"\r\n className={cn(\r\n \"peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <CheckboxPrimitive.Indicator\r\n data-slot=\"checkbox-indicator\"\r\n className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\r\n >\r\n <CheckIcon\r\n />\r\n </CheckboxPrimitive.Indicator>\r\n </CheckboxPrimitive.Root>\r\n )\r\n}\r\n\r\nexport { Checkbox }\r\n",
|
|
1273
|
+
"src/components/ui/combobox.tsx": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { Combobox as ComboboxPrimitive } from \"@base-ui/react\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button } from \"#/components/ui/button.tsx\"\r\nimport {\r\n InputGroup,\r\n InputGroupAddon,\r\n InputGroupButton,\r\n InputGroupInput,\r\n} from \"#/components/ui/input-group.tsx\"\r\nimport { ChevronDownIcon, XIcon, CheckIcon } from \"lucide-react\"\r\n\r\nconst Combobox = ComboboxPrimitive.Root\r\n\r\nfunction ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {\r\n return <ComboboxPrimitive.Value data-slot=\"combobox-value\" {...props} />\r\n}\r\n\r\nfunction ComboboxTrigger({\r\n className,\r\n children,\r\n ...props\r\n}: ComboboxPrimitive.Trigger.Props) {\r\n return (\r\n <ComboboxPrimitive.Trigger\r\n data-slot=\"combobox-trigger\"\r\n className={cn(\"[&_svg:not([class*='size-'])]:size-4\", className)}\r\n {...props}\r\n >\r\n {children}\r\n <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\r\n </ComboboxPrimitive.Trigger>\r\n )\r\n}\r\n\r\nfunction ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {\r\n return (\r\n <ComboboxPrimitive.Clear\r\n data-slot=\"combobox-clear\"\r\n render={<InputGroupButton variant=\"ghost\" size=\"icon-xs\" />}\r\n className={cn(className)}\r\n {...props}\r\n >\r\n <XIcon className=\"pointer-events-none\" />\r\n </ComboboxPrimitive.Clear>\r\n )\r\n}\r\n\r\nfunction ComboboxInput({\r\n className,\r\n children,\r\n disabled = false,\r\n showTrigger = true,\r\n showClear = false,\r\n ...props\r\n}: ComboboxPrimitive.Input.Props & {\r\n showTrigger?: boolean\r\n showClear?: boolean\r\n}) {\r\n return (\r\n <InputGroup className={cn(\"w-auto\", className)}>\r\n <ComboboxPrimitive.Input\r\n render={<InputGroupInput disabled={disabled} />}\r\n {...props}\r\n />\r\n <InputGroupAddon align=\"inline-end\">\r\n {showTrigger && (\r\n <InputGroupButton\r\n size=\"icon-xs\"\r\n variant=\"ghost\"\r\n render={<ComboboxTrigger />}\r\n data-slot=\"input-group-button\"\r\n className=\"group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent\"\r\n disabled={disabled}\r\n />\r\n )}\r\n {showClear && <ComboboxClear disabled={disabled} />}\r\n </InputGroupAddon>\r\n {children}\r\n </InputGroup>\r\n )\r\n}\r\n\r\nfunction ComboboxContent({\r\n className,\r\n side = \"bottom\",\r\n sideOffset = 6,\r\n align = \"start\",\r\n alignOffset = 0,\r\n anchor,\r\n ...props\r\n}: ComboboxPrimitive.Popup.Props &\r\n Pick<\r\n ComboboxPrimitive.Positioner.Props,\r\n \"side\" | \"align\" | \"sideOffset\" | \"alignOffset\" | \"anchor\"\r\n >) {\r\n return (\r\n <ComboboxPrimitive.Portal>\r\n <ComboboxPrimitive.Positioner\r\n side={side}\r\n sideOffset={sideOffset}\r\n align={align}\r\n alignOffset={alignOffset}\r\n anchor={anchor}\r\n className=\"isolate z-50\"\r\n >\r\n <ComboboxPrimitive.Popup\r\n data-slot=\"combobox-content\"\r\n data-chips={!!anchor}\r\n className={cn(\"group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\", className )}\r\n {...props}\r\n />\r\n </ComboboxPrimitive.Positioner>\r\n </ComboboxPrimitive.Portal>\r\n )\r\n}\r\n\r\nfunction ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {\r\n return (\r\n <ComboboxPrimitive.List\r\n data-slot=\"combobox-list\"\r\n className={cn(\r\n \"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction ComboboxItem({\r\n className,\r\n children,\r\n ...props\r\n}: ComboboxPrimitive.Item.Props) {\r\n return (\r\n <ComboboxPrimitive.Item\r\n data-slot=\"combobox-item\"\r\n className={cn(\r\n \"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <ComboboxPrimitive.ItemIndicator\r\n render={\r\n <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\r\n }\r\n >\r\n <CheckIcon className=\"pointer-events-none\" />\r\n </ComboboxPrimitive.ItemIndicator>\r\n </ComboboxPrimitive.Item>\r\n )\r\n}\r\n\r\nfunction ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {\r\n return (\r\n <ComboboxPrimitive.Group\r\n data-slot=\"combobox-group\"\r\n className={cn(className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction ComboboxLabel({\r\n className,\r\n ...props\r\n}: ComboboxPrimitive.GroupLabel.Props) {\r\n return (\r\n <ComboboxPrimitive.GroupLabel\r\n data-slot=\"combobox-label\"\r\n className={cn(\"px-2 py-1.5 text-xs text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {\r\n return (\r\n <ComboboxPrimitive.Collection data-slot=\"combobox-collection\" {...props} />\r\n )\r\n}\r\n\r\nfunction ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {\r\n return (\r\n <ComboboxPrimitive.Empty\r\n data-slot=\"combobox-empty\"\r\n className={cn(\r\n \"hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction ComboboxSeparator({\r\n className,\r\n ...props\r\n}: ComboboxPrimitive.Separator.Props) {\r\n return (\r\n <ComboboxPrimitive.Separator\r\n data-slot=\"combobox-separator\"\r\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction ComboboxChips({\r\n className,\r\n ...props\r\n}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &\r\n ComboboxPrimitive.Chips.Props) {\r\n return (\r\n <ComboboxPrimitive.Chips\r\n data-slot=\"combobox-chips\"\r\n className={cn(\r\n \"flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction ComboboxChip({\r\n className,\r\n children,\r\n showRemove = true,\r\n ...props\r\n}: ComboboxPrimitive.Chip.Props & {\r\n showRemove?: boolean\r\n}) {\r\n return (\r\n <ComboboxPrimitive.Chip\r\n data-slot=\"combobox-chip\"\r\n className={cn(\r\n \"flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n {showRemove && (\r\n <ComboboxPrimitive.ChipRemove\r\n render={<Button variant=\"ghost\" size=\"icon-xs\" />}\r\n className=\"-ml-1 opacity-50 hover:opacity-100\"\r\n data-slot=\"combobox-chip-remove\"\r\n >\r\n <XIcon className=\"pointer-events-none\" />\r\n </ComboboxPrimitive.ChipRemove>\r\n )}\r\n </ComboboxPrimitive.Chip>\r\n )\r\n}\r\n\r\nfunction ComboboxChipsInput({\r\n className,\r\n ...props\r\n}: ComboboxPrimitive.Input.Props) {\r\n return (\r\n <ComboboxPrimitive.Input\r\n data-slot=\"combobox-chip-input\"\r\n className={cn(\"min-w-16 flex-1 outline-none\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction useComboboxAnchor() {\r\n return React.useRef<HTMLDivElement | null>(null)\r\n}\r\n\r\nexport {\r\n Combobox,\r\n ComboboxInput,\r\n ComboboxContent,\r\n ComboboxList,\r\n ComboboxItem,\r\n ComboboxGroup,\r\n ComboboxLabel,\r\n ComboboxCollection,\r\n ComboboxEmpty,\r\n ComboboxSeparator,\r\n ComboboxChips,\r\n ComboboxChip,\r\n ComboboxChipsInput,\r\n ComboboxTrigger,\r\n ComboboxValue,\r\n useComboboxAnchor,\r\n}\r\n",
|
|
1274
|
+
"src/components/ui/command.tsx": "import * as React from \"react\"\r\nimport { Command as CommandPrimitive } from \"cmdk\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport {\r\n Dialog,\r\n DialogContent,\r\n DialogDescription,\r\n DialogHeader,\r\n DialogTitle,\r\n} from \"#/components/ui/dialog.tsx\"\r\nimport {\r\n InputGroup,\r\n InputGroupAddon,\r\n} from \"#/components/ui/input-group.tsx\"\r\nimport { SearchIcon, CheckIcon } from \"lucide-react\"\r\n\r\nfunction Command({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof CommandPrimitive>) {\r\n return (\r\n <CommandPrimitive\r\n data-slot=\"command\"\r\n className={cn(\r\n \"flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CommandDialog({\r\n title = \"Command Palette\",\r\n description = \"Search for a command to run...\",\r\n children,\r\n className,\r\n showCloseButton = false,\r\n ...props\r\n}: Omit<React.ComponentProps<typeof Dialog>, \"children\"> & {\r\n title?: string\r\n description?: string\r\n className?: string\r\n showCloseButton?: boolean\r\n children: React.ReactNode\r\n}) {\r\n return (\r\n <Dialog {...props}>\r\n <DialogHeader className=\"sr-only\">\r\n <DialogTitle>{title}</DialogTitle>\r\n <DialogDescription>{description}</DialogDescription>\r\n </DialogHeader>\r\n <DialogContent\r\n className={cn(\r\n \"top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0\",\r\n className\r\n )}\r\n showCloseButton={showCloseButton}\r\n >\r\n {children}\r\n </DialogContent>\r\n </Dialog>\r\n )\r\n}\r\n\r\nfunction CommandInput({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\r\n return (\r\n <div data-slot=\"command-input-wrapper\" className=\"p-1 pb-0\">\r\n <InputGroup className=\"h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\r\n <CommandPrimitive.Input\r\n data-slot=\"command-input\"\r\n className={cn(\r\n \"w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n <InputGroupAddon>\r\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\r\n </InputGroupAddon>\r\n </InputGroup>\r\n </div>\r\n )\r\n}\r\n\r\nfunction CommandList({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\r\n return (\r\n <CommandPrimitive.List\r\n data-slot=\"command-list\"\r\n className={cn(\r\n \"no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CommandEmpty({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\r\n return (\r\n <CommandPrimitive.Empty\r\n data-slot=\"command-empty\"\r\n className={cn(\"py-6 text-center text-sm\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CommandGroup({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\r\n return (\r\n <CommandPrimitive.Group\r\n data-slot=\"command-group\"\r\n className={cn(\r\n \"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CommandSeparator({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\r\n return (\r\n <CommandPrimitive.Separator\r\n data-slot=\"command-separator\"\r\n className={cn(\"-mx-1 h-px w-auto bg-border\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CommandItem({\r\n className,\r\n children,\r\n ...props\r\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\r\n return (\r\n <CommandPrimitive.Item\r\n data-slot=\"command-item\"\r\n className={cn(\r\n \"group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:**:[svg]:text-foreground\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\r\n </CommandPrimitive.Item>\r\n )\r\n}\r\n\r\nfunction CommandShortcut({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"span\">) {\r\n return (\r\n <span\r\n data-slot=\"command-shortcut\"\r\n className={cn(\r\n \"ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Command,\r\n CommandDialog,\r\n CommandInput,\r\n CommandList,\r\n CommandEmpty,\r\n CommandGroup,\r\n CommandItem,\r\n CommandShortcut,\r\n CommandSeparator,\r\n}\r\n",
|
|
1275
|
+
"src/components/ui/composed/accordion/faq-accordion.tsx": "import * as React from 'react';\r\nimport {\r\n Accordion,\r\n AccordionContent,\r\n AccordionItem,\r\n AccordionTrigger,\r\n} from '@/components/ui/accordion';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype FaqItem = {\r\n id?: string;\r\n question: string;\r\n answer: React.ReactNode;\r\n};\r\n\r\ntype FaqAccordionProps = {\r\n items: FaqItem[];\r\n defaultValue?: string;\r\n className?: string;\r\n};\r\n\r\nexport function FaqAccordion({\r\n items,\r\n defaultValue,\r\n className,\r\n}: FaqAccordionProps) {\r\n return (\r\n <Accordion\r\n type='single'\r\n collapsible\r\n defaultValue={\r\n defaultValue ??\r\n (items.length > 0 ? (items[0].id ?? 'faq-0') : undefined)\r\n }\r\n className={cn('w-full', className)}\r\n >\r\n {items.map((item, index) => {\r\n const value = item.id ?? `faq-${index}`;\r\n return (\r\n <AccordionItem key={value} value={value}>\r\n <AccordionTrigger>{item.question}</AccordionTrigger>\r\n <AccordionContent>{item.answer}</AccordionContent>\r\n </AccordionItem>\r\n );\r\n })}\r\n </Accordion>\r\n );\r\n}\r\n",
|
|
1276
|
+
"src/components/ui/composed/alert/error-alert.tsx": "import type { ReactNode } from 'react';\r\nimport { CircleAlertIcon } from 'lucide-react';\r\n\r\nimport {\r\n Alert,\r\n AlertAction,\r\n AlertDescription,\r\n AlertTitle,\r\n} from '@/components/ui/alert';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype ErrorAlertProps = {\r\n title: string;\r\n description?: string;\r\n action?: ReactNode;\r\n className?: string;\r\n};\r\n\r\nexport function ErrorAlert({\r\n title,\r\n description,\r\n action,\r\n className,\r\n}: ErrorAlertProps) {\r\n return (\r\n <Alert variant='destructive' className={cn('w-full', className)}>\r\n <CircleAlertIcon />\r\n <AlertTitle>{title}</AlertTitle>\r\n {description ? <AlertDescription>{description}</AlertDescription> : null}\r\n {action ? <AlertAction>{action}</AlertAction> : null}\r\n </Alert>\r\n );\r\n}\r\n",
|
|
1277
|
+
"src/components/ui/composed/avatar/user-avatar.tsx": "import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst sizeClasses = {\r\n sm: 'size-6 text-xs',\r\n md: 'size-8 text-sm',\r\n lg: 'size-12 text-base',\r\n xl: 'size-16 text-lg',\r\n} as const;\r\n\r\ntype UserAvatarProps = {\r\n src?: string;\r\n name: string;\r\n size?: keyof typeof sizeClasses;\r\n className?: string;\r\n};\r\n\r\nfunction getInitials(name: string): string {\r\n return name\r\n .trim()\r\n .split(/\\s+/)\r\n .map(n => n[0])\r\n .slice(0, 2)\r\n .join('')\r\n .toUpperCase();\r\n}\r\n\r\nexport function UserAvatar({\r\n src,\r\n name,\r\n size = 'md',\r\n className,\r\n}: UserAvatarProps) {\r\n return (\r\n <Avatar className={cn(sizeClasses[size], className)}>\r\n <AvatarImage src={src} alt={name} />\r\n <AvatarFallback>{getInitials(name)}</AvatarFallback>\r\n </Avatar>\r\n );\r\n}\r\n",
|
|
1278
|
+
"src/components/ui/composed/badge/status-badge.tsx": "import * as React from 'react';\r\nimport { Badge } from '@/components/ui/badge';\r\nimport { cn } from '@/lib/utils';\r\n\r\nconst statusStyles = {\r\n success:\r\n 'border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-950 dark:text-green-300',\r\n warning:\r\n 'border-yellow-200 bg-yellow-50 text-yellow-700 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-300',\r\n error:\r\n 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-300',\r\n info: 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300',\r\n neutral: '',\r\n} as const;\r\n\r\ntype StatusBadgeProps = {\r\n status: keyof typeof statusStyles;\r\n label: string;\r\n icon?: React.ReactNode;\r\n className?: string;\r\n};\r\n\r\nexport function StatusBadge({\r\n status,\r\n label,\r\n icon,\r\n className,\r\n}: StatusBadgeProps) {\r\n return (\r\n <Badge variant='outline' className={cn(statusStyles[status], className)}>\r\n {icon}\r\n {label}\r\n </Badge>\r\n );\r\n}\r\n",
|
|
1279
|
+
"src/components/ui/composed/button/loading-button.tsx": "import type * as React from 'react';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Loader2Icon } from 'lucide-react';\r\n\r\ntype LoadingButtonProps = React.ComponentProps<typeof Button> & {\r\n loading?: boolean;\r\n loadingText?: string;\r\n};\r\n\r\nexport function LoadingButton({\r\n loading = false,\r\n loadingText,\r\n children,\r\n disabled,\r\n ...props\r\n}: LoadingButtonProps) {\r\n return (\r\n <Button disabled={disabled || loading} {...props}>\r\n {loading && <Loader2Icon className='animate-spin' />}\r\n {loading && loadingText !== undefined ? loadingText : children}\r\n </Button>\r\n );\r\n}\r\n",
|
|
1280
|
+
"src/components/ui/composed/button/theme-toggle.tsx": "import { MoonIcon, SunIcon } from 'lucide-react';\r\nimport { useTheme } from 'next-themes';\r\n\r\nimport { Button } from '@/components/ui/button';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nexport function ThemeToggle() {\r\n const { setTheme } = useTheme();\r\n\r\n return (\r\n <Button\r\n type='button'\r\n variant='ghost'\r\n size='icon'\r\n aria-label={m.ui_toggle_theme()}\r\n onClick={() => {\r\n const isDark = document.documentElement.classList.contains('dark');\r\n setTheme(isDark ? 'light' : 'dark');\r\n }}\r\n >\r\n <SunIcon className='dark:hidden' />\r\n <MoonIcon className='hidden dark:block' />\r\n </Button>\r\n );\r\n}\r\n",
|
|
1281
|
+
"src/components/ui/composed/card/stats-card.tsx": "import * as React from 'react';\r\nimport {\r\n Card,\r\n CardContent,\r\n CardDescription,\r\n CardHeader,\r\n CardTitle,\r\n} from '@/components/ui/card';\r\nimport { TrendingDownIcon, TrendingUpIcon } from 'lucide-react';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype Trend = {\r\n value: number;\r\n direction: 'up' | 'down';\r\n label?: string;\r\n};\r\n\r\ntype StatsCardProps = {\r\n title: string;\r\n value: string | number;\r\n description?: string;\r\n icon?: React.ReactNode;\r\n trend?: Trend;\r\n className?: string;\r\n};\r\n\r\nexport function StatsCard({\r\n title,\r\n value,\r\n description,\r\n icon,\r\n trend,\r\n className,\r\n}: StatsCardProps) {\r\n return (\r\n <Card className={className}>\r\n <CardHeader>\r\n <div className='flex items-start justify-between gap-4'>\r\n <CardTitle className='text-sm font-medium text-muted-foreground'>\r\n {title}\r\n </CardTitle>\r\n {icon && <span className='text-muted-foreground'>{icon}</span>}\r\n </div>\r\n <div className='text-2xl font-bold'>{value}</div>\r\n {trend && (\r\n <div\r\n className={cn(\r\n 'flex items-center gap-1 text-xs',\r\n trend.direction === 'up'\r\n ? 'text-green-600 dark:text-green-400'\r\n : 'text-red-600 dark:text-red-400'\r\n )}\r\n >\r\n {trend.direction === 'up' ? (\r\n <TrendingUpIcon className='size-3.5' />\r\n ) : (\r\n <TrendingDownIcon className='size-3.5' />\r\n )}\r\n <span>{trend.value}%</span>\r\n {trend.label && (\r\n <span className='text-muted-foreground'>{trend.label}</span>\r\n )}\r\n </div>\r\n )}\r\n </CardHeader>\r\n {description && (\r\n <CardContent>\r\n <CardDescription>{description}</CardDescription>\r\n </CardContent>\r\n )}\r\n </Card>\r\n );\r\n}\r\n",
|
|
1282
|
+
"src/components/ui/composed/carousel/media-carousel.tsx": "import * as React from 'react';\r\nimport {\r\n Carousel,\r\n CarouselContent,\r\n CarouselItem,\r\n CarouselNext,\r\n CarouselPrevious,\r\n} from '@/components/ui/carousel';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype MediaCarouselProps = {\r\n items: React.ReactNode[];\r\n itemClassName?: string;\r\n orientation?: 'horizontal' | 'vertical';\r\n loop?: boolean;\r\n className?: string;\r\n};\r\n\r\nexport function MediaCarousel({\r\n items,\r\n itemClassName,\r\n orientation = 'horizontal',\r\n loop = false,\r\n className,\r\n}: MediaCarouselProps) {\r\n return (\r\n <Carousel\r\n orientation={orientation}\r\n opts={{ loop }}\r\n className={cn('w-full', className)}\r\n >\r\n <CarouselContent>\r\n {items.map((item, index) => (\r\n <CarouselItem key={index} className={itemClassName}>\r\n {item}\r\n </CarouselItem>\r\n ))}\r\n </CarouselContent>\r\n <CarouselPrevious />\r\n <CarouselNext />\r\n </Carousel>\r\n );\r\n}\r\n",
|
|
1283
|
+
"src/components/ui/composed/checkbox/checkbox-field.tsx": "import * as React from 'react';\r\n\r\nimport { Checkbox } from '@/components/ui/checkbox';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype CheckboxFieldProps = {\r\n label: string;\r\n description?: string;\r\n error?: string;\r\n required?: boolean;\r\n id?: string;\r\n checked?: boolean | 'indeterminate';\r\n defaultChecked?: boolean;\r\n onCheckedChange?: (checked: boolean) => void;\r\n disabled?: boolean;\r\n name?: string;\r\n className?: string;\r\n};\r\n\r\nexport function CheckboxField({\r\n label,\r\n description,\r\n error,\r\n required,\r\n id: externalId,\r\n className,\r\n onCheckedChange,\r\n ...checkboxProps\r\n}: CheckboxFieldProps) {\r\n const generatedId = React.useId();\r\n const id = externalId ?? generatedId;\r\n\r\n return (\r\n <div className={cn('flex items-start gap-2', className)}>\r\n <Checkbox\r\n id={id}\r\n onCheckedChange={v => onCheckedChange?.(v === true)}\r\n aria-invalid={!!error}\r\n {...checkboxProps}\r\n />\r\n <div className='flex flex-col gap-0.5'>\r\n <Label\r\n htmlFor={id}\r\n className={cn(checkboxProps.disabled && 'opacity-50')}\r\n >\r\n {label}\r\n {required && <span className='text-destructive ms-0.5'>*</span>}\r\n </Label>\r\n {description && (\r\n <p className='text-muted-foreground text-xs'>{description}</p>\r\n )}\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n",
|
|
1284
|
+
"src/components/ui/composed/checkbox/checkbox-group.tsx": "import * as React from 'react';\r\nimport { Checkbox } from '@/components/ui/checkbox';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype CheckboxOption = {\r\n label: string;\r\n value: string;\r\n description?: string;\r\n disabled?: boolean;\r\n};\r\n\r\ntype CheckboxGroupProps = {\r\n options: CheckboxOption[];\r\n value: string[];\r\n onValueChange: (value: string[]) => void;\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n orientation?: 'vertical' | 'horizontal';\r\n className?: string;\r\n};\r\n\r\nexport function CheckboxGroup({\r\n options,\r\n value,\r\n onValueChange,\r\n label,\r\n description,\r\n error,\r\n orientation = 'vertical',\r\n className,\r\n}: CheckboxGroupProps) {\r\n const groupId = React.useId();\r\n\r\n const toggle = (optionValue: string) => {\r\n if (value.includes(optionValue)) {\r\n onValueChange(value.filter(v => v !== optionValue));\r\n } else {\r\n onValueChange([...value, optionValue]);\r\n }\r\n };\r\n\r\n return (\r\n <fieldset className={cn('flex flex-col gap-2', className)}>\r\n {label && (\r\n <legend className='mb-1 text-sm font-medium leading-none'>\r\n {label}\r\n </legend>\r\n )}\r\n {description && (\r\n <p className='text-muted-foreground text-sm'>{description}</p>\r\n )}\r\n <div\r\n className={cn(\r\n 'flex gap-3',\r\n orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap'\r\n )}\r\n >\r\n {options.map(option => {\r\n const id = `${groupId}-${option.value}`;\r\n return (\r\n <div key={option.value} className='flex items-start gap-2'>\r\n <Checkbox\r\n id={id}\r\n checked={value.includes(option.value)}\r\n onCheckedChange={() => toggle(option.value)}\r\n disabled={option.disabled}\r\n className='mt-0.5'\r\n />\r\n <div className='flex flex-col gap-0.5'>\r\n <Label\r\n htmlFor={id}\r\n className={cn(option.disabled && 'opacity-50')}\r\n >\r\n {option.label}\r\n </Label>\r\n {option.description && (\r\n <span className='text-muted-foreground text-xs'>\r\n {option.description}\r\n </span>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </fieldset>\r\n );\r\n}\r\n",
|
|
1285
|
+
"src/components/ui/composed/combobox/searchable-select.tsx": "import * as React from 'react';\r\n\r\nimport { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';\r\n\r\nimport { Button } from '@/components/ui/button';\r\nimport {\r\n Command,\r\n CommandEmpty,\r\n CommandGroup,\r\n CommandInput,\r\n CommandItem,\r\n CommandList,\r\n} from '@/components/ui/command';\r\nimport { Label } from '@/components/ui/label';\r\nimport {\r\n Popover,\r\n PopoverContent,\r\n PopoverTrigger,\r\n} from '@/components/ui/popover';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype ComboboxOption = {\r\n value: string;\r\n label: string;\r\n disabled?: boolean;\r\n};\r\n\r\ntype SearchableSelectProps = {\r\n options: ComboboxOption[];\r\n value?: string;\r\n onValueChange?: (value: string) => void;\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n required?: boolean;\r\n placeholder?: string;\r\n searchPlaceholder?: string;\r\n emptyMessage?: string;\r\n disabled?: boolean;\r\n className?: string;\r\n};\r\n\r\nexport function SearchableSelect({\r\n options,\r\n value,\r\n onValueChange,\r\n label,\r\n description,\r\n error,\r\n required,\r\n placeholder = m.ui_select_placeholder(),\r\n searchPlaceholder = m.ui_search_placeholder(),\r\n emptyMessage = m.ui_no_results(),\r\n disabled,\r\n className,\r\n}: SearchableSelectProps) {\r\n const id = React.useId();\r\n const [open, setOpen] = React.useState(false);\r\n\r\n const selected = options.find(o => o.value === value);\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', className)}>\r\n {label && (\r\n <Label htmlFor={id}>\r\n {label}\r\n {required && <span className='text-destructive ms-0.5'>*</span>}\r\n </Label>\r\n )}\r\n {description && (\r\n <p className='text-muted-foreground text-sm'>{description}</p>\r\n )}\r\n <Popover open={open} onOpenChange={setOpen}>\r\n <PopoverTrigger asChild>\r\n <Button\r\n id={id}\r\n variant='outline'\r\n role='combobox'\r\n aria-expanded={open}\r\n aria-invalid={!!error}\r\n disabled={disabled}\r\n className='w-full justify-between font-normal'\r\n >\r\n {selected ? (\r\n selected.label\r\n ) : (\r\n <span className='text-muted-foreground'>{placeholder}</span>\r\n )}\r\n <ChevronsUpDownIcon className='ms-2 size-4 shrink-0 opacity-50' />\r\n </Button>\r\n </PopoverTrigger>\r\n <PopoverContent\r\n className='w-[--radix-popover-trigger-width] p-0'\r\n align='start'\r\n >\r\n <Command>\r\n <CommandInput placeholder={searchPlaceholder} />\r\n <CommandList>\r\n <CommandEmpty>{emptyMessage}</CommandEmpty>\r\n <CommandGroup>\r\n {options.map(option => (\r\n <CommandItem\r\n key={option.value}\r\n value={option.value}\r\n disabled={option.disabled}\r\n onSelect={current => {\r\n onValueChange?.(current === value ? '' : current);\r\n setOpen(false);\r\n }}\r\n >\r\n <CheckIcon\r\n className={cn(\r\n 'me-2 size-4',\r\n value === option.value ? 'opacity-100' : 'opacity-0'\r\n )}\r\n />\r\n {option.label}\r\n </CommandItem>\r\n ))}\r\n </CommandGroup>\r\n </CommandList>\r\n </Command>\r\n </PopoverContent>\r\n </Popover>\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </div>\r\n );\r\n}\r\n",
|
|
1286
|
+
"src/components/ui/composed/date-picker/date-picker-field.tsx": "'use client';\r\n\r\nimport * as React from 'react';\r\nimport type { Matcher } from 'react-day-picker';\r\n\r\nimport { CalendarIcon } from 'lucide-react';\r\n\r\nimport { Button } from '@/components/ui/button';\r\nimport { Calendar } from '@/components/ui/calendar';\r\nimport { Label } from '@/components/ui/label';\r\nimport {\r\n Popover,\r\n PopoverContent,\r\n PopoverTrigger,\r\n} from '@/components/ui/popover';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype DatePickerFieldProps = {\r\n value?: Date;\r\n onValueChange?: (date: Date | undefined) => void;\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n required?: boolean;\r\n placeholder?: string;\r\n disabled?: Matcher | Matcher[];\r\n id?: string;\r\n className?: string;\r\n};\r\n\r\nfunction formatDate(date: Date): string {\r\n return date.toLocaleDateString(undefined, {\r\n year: 'numeric',\r\n month: 'long',\r\n day: 'numeric',\r\n });\r\n}\r\n\r\nexport function DatePickerField({\r\n value,\r\n onValueChange,\r\n label,\r\n description,\r\n error,\r\n required,\r\n placeholder = m.ui_pick_date(),\r\n disabled,\r\n id: externalId,\r\n className,\r\n}: DatePickerFieldProps) {\r\n const generatedId = React.useId();\r\n const id = externalId ?? generatedId;\r\n const [open, setOpen] = React.useState(false);\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', className)}>\r\n {label && (\r\n <Label htmlFor={id}>\r\n {label}\r\n {required && <span className='text-destructive ms-0.5'>*</span>}\r\n </Label>\r\n )}\r\n {description && (\r\n <p className='text-muted-foreground text-sm'>{description}</p>\r\n )}\r\n <Popover open={open} onOpenChange={setOpen}>\r\n <PopoverTrigger asChild>\r\n <Button\r\n id={id}\r\n variant='outline'\r\n aria-invalid={!!error}\r\n className='w-full justify-start text-start font-normal'\r\n >\r\n <CalendarIcon className='me-2 size-4' />\r\n {value ? (\r\n formatDate(value)\r\n ) : (\r\n <span className='text-muted-foreground'>{placeholder}</span>\r\n )}\r\n </Button>\r\n </PopoverTrigger>\r\n <PopoverContent className='w-auto p-0' align='start'>\r\n <Calendar\r\n mode='single'\r\n selected={value}\r\n onSelect={date => {\r\n onValueChange?.(date);\r\n setOpen(false);\r\n }}\r\n disabled={disabled}\r\n autoFocus\r\n />\r\n </PopoverContent>\r\n </Popover>\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </div>\r\n );\r\n}\r\n",
|
|
1287
|
+
"src/components/ui/composed/dialog/confirmation-dialog.tsx": "import {\r\n Dialog,\r\n DialogContent,\r\n DialogDescription,\r\n DialogFooter,\r\n DialogHeader,\r\n DialogTitle,\r\n} from '@/components/ui/dialog';\r\nimport { Button } from '@/components/ui/button';\r\nimport { Loader2Icon } from 'lucide-react';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype ConfirmationDialogProps = {\r\n open: boolean;\r\n onOpenChange: (open: boolean) => void;\r\n title: string;\r\n description?: string;\r\n confirmLabel?: string;\r\n cancelLabel?: string;\r\n onConfirm: () => void;\r\n onCancel?: () => void;\r\n variant?: 'default' | 'destructive';\r\n loading?: boolean;\r\n};\r\n\r\nexport function ConfirmationDialog({\r\n open,\r\n onOpenChange,\r\n title,\r\n description,\r\n confirmLabel = m.ui_confirm(),\r\n cancelLabel = m.ui_cancel(),\r\n onConfirm,\r\n onCancel,\r\n variant = 'default',\r\n loading = false,\r\n}: ConfirmationDialogProps) {\r\n const handleCancel = () => {\r\n onCancel?.();\r\n onOpenChange(false);\r\n };\r\n\r\n return (\r\n <Dialog open={open} onOpenChange={onOpenChange}>\r\n <DialogContent>\r\n <DialogHeader>\r\n <DialogTitle>{title}</DialogTitle>\r\n {description && <DialogDescription>{description}</DialogDescription>}\r\n </DialogHeader>\r\n <DialogFooter>\r\n <Button variant='outline' onClick={handleCancel} disabled={loading}>\r\n {cancelLabel}\r\n </Button>\r\n <Button variant={variant} onClick={onConfirm} disabled={loading}>\r\n {loading && <Loader2Icon className='animate-spin' />}\r\n {confirmLabel}\r\n </Button>\r\n </DialogFooter>\r\n </DialogContent>\r\n </Dialog>\r\n );\r\n}\r\n",
|
|
1288
|
+
"src/components/ui/composed/drawer/action-drawer.tsx": "import * as React from 'react';\r\nimport {\r\n Drawer,\r\n DrawerContent,\r\n DrawerDescription,\r\n DrawerFooter,\r\n DrawerHeader,\r\n DrawerTitle,\r\n DrawerTrigger,\r\n} from '@/components/ui/drawer';\r\n\r\ntype ActionDrawerProps = {\r\n open?: boolean;\r\n onOpenChange?: (open: boolean) => void;\r\n trigger?: React.ReactNode;\r\n title: string;\r\n description?: string;\r\n children: React.ReactNode;\r\n footer?: React.ReactNode;\r\n direction?: 'bottom' | 'left' | 'right' | 'top';\r\n};\r\n\r\nexport function ActionDrawer({\r\n open,\r\n onOpenChange,\r\n trigger,\r\n title,\r\n description,\r\n children,\r\n footer,\r\n direction = 'bottom',\r\n}: ActionDrawerProps) {\r\n return (\r\n <Drawer open={open} onOpenChange={onOpenChange} direction={direction}>\r\n {trigger && <DrawerTrigger asChild>{trigger}</DrawerTrigger>}\r\n <DrawerContent>\r\n <DrawerHeader>\r\n <DrawerTitle>{title}</DrawerTitle>\r\n {description && <DrawerDescription>{description}</DrawerDescription>}\r\n </DrawerHeader>\r\n <div className='px-4 pb-2'>{children}</div>\r\n {footer && <DrawerFooter>{footer}</DrawerFooter>}\r\n </DrawerContent>\r\n </Drawer>\r\n );\r\n}\r\n",
|
|
1289
|
+
"src/components/ui/composed/dropdown-menu/actions-dropdown.tsx": "import * as React from 'react';\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuLabel,\r\n DropdownMenuSeparator,\r\n DropdownMenuTrigger,\r\n} from '@/components/ui/dropdown-menu';\r\nimport { Button } from '@/components/ui/button';\r\nimport { MoreHorizontalIcon } from 'lucide-react';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype ActionItem = {\r\n label: string;\r\n icon?: React.ReactNode;\r\n onClick: () => void;\r\n disabled?: boolean;\r\n variant?: 'default' | 'destructive';\r\n};\r\n\r\ntype ActionGroup = {\r\n label?: string;\r\n items: ActionItem[];\r\n};\r\n\r\ntype ActionsDropdownProps = {\r\n groups: ActionGroup[];\r\n trigger?: React.ReactNode;\r\n triggerLabel?: string;\r\n align?: 'start' | 'center' | 'end';\r\n};\r\n\r\nexport function ActionsDropdown({\r\n groups,\r\n trigger,\r\n triggerLabel,\r\n align = 'end',\r\n}: ActionsDropdownProps) {\r\n return (\r\n <DropdownMenu>\r\n <DropdownMenuTrigger asChild>\r\n {trigger ?? (\r\n <Button\r\n variant='ghost'\r\n size='icon'\r\n aria-label={triggerLabel ?? m.ui_open_actions()}\r\n >\r\n <MoreHorizontalIcon />\r\n </Button>\r\n )}\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent align={align}>\r\n {groups.map((group, groupIndex) => (\r\n <React.Fragment key={groupIndex}>\r\n {groupIndex > 0 && <DropdownMenuSeparator />}\r\n {group.label && (\r\n <DropdownMenuLabel>{group.label}</DropdownMenuLabel>\r\n )}\r\n {group.items.map((item, itemIndex) => (\r\n <DropdownMenuItem\r\n key={itemIndex}\r\n onClick={item.onClick}\r\n disabled={item.disabled}\r\n variant={item.variant}\r\n >\r\n {item.icon}\r\n {item.label}\r\n </DropdownMenuItem>\r\n ))}\r\n </React.Fragment>\r\n ))}\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n );\r\n}\r\n",
|
|
1290
|
+
"src/components/ui/composed/dropdown-menu/locale-switcher.tsx": "import { LanguagesIcon } from 'lucide-react';\r\n\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n} from '@/components/ui/dropdown-menu';\r\nimport { Button } from '@/components/ui/button';\r\n\r\nimport { m } from '#/paraglide/messages';\r\nimport {\r\n locales,\r\n getLocale,\r\n setLocale,\r\n type Locale,\r\n} from '#/paraglide/runtime';\r\n\r\nconst localeLabels: Record<Locale, () => string> = {\r\n en: m.ui_language_en,\r\n ar: m.ui_language_ar,\r\n};\r\n\r\nexport function LocaleSwitcher() {\r\n const activeLocale = getLocale();\r\n\r\n return (\r\n <DropdownMenu>\r\n <DropdownMenuTrigger>\r\n <Button\r\n type='button'\r\n variant='ghost'\r\n size='icon'\r\n aria-label={m.ui_switch_language()}\r\n >\r\n <LanguagesIcon />\r\n </Button>\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent align='end'>\r\n {locales.map(locale => (\r\n <DropdownMenuItem\r\n key={locale}\r\n disabled={locale === activeLocale}\r\n onClick={() => setLocale(locale)}\r\n >\r\n {localeLabels[locale]()}\r\n </DropdownMenuItem>\r\n ))}\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n );\r\n}\r\n",
|
|
1291
|
+
"src/components/ui/composed/empty/empty-state.tsx": "import type { ReactNode } from 'react';\r\n\r\nimport {\r\n Empty,\r\n EmptyContent,\r\n EmptyDescription,\r\n EmptyHeader,\r\n EmptyMedia,\r\n EmptyTitle,\r\n} from '@/components/ui/empty';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype EmptyStateProps = {\r\n title: string;\r\n description?: string;\r\n icon?: ReactNode;\r\n action?: ReactNode;\r\n className?: string;\r\n};\r\n\r\nexport function EmptyState({\r\n title,\r\n description,\r\n icon,\r\n action,\r\n className,\r\n}: EmptyStateProps) {\r\n return (\r\n <Empty className={cn('border', className)}>\r\n <EmptyHeader>\r\n {icon ? <EmptyMedia variant='icon'>{icon}</EmptyMedia> : null}\r\n <EmptyTitle>{title}</EmptyTitle>\r\n {description ? (\r\n <EmptyDescription>{description}</EmptyDescription>\r\n ) : null}\r\n </EmptyHeader>\r\n {action ? <EmptyContent>{action}</EmptyContent> : null}\r\n </Empty>\r\n );\r\n}\r\n",
|
|
1292
|
+
"src/components/ui/composed/input/labeled-input.tsx": "import * as React from 'react';\r\nimport { Input, type InputProps } from '@/components/ui/input';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\n// Omit `error` from InputProps (which is boolean) so we can redefine it as\r\n// a string error message without a conflicting type intersection.\r\ntype LabeledInputProps = Omit<InputProps, 'error'> & {\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n required?: boolean;\r\n};\r\n\r\nexport function LabeledInput({\r\n label,\r\n description,\r\n error,\r\n required,\r\n id: externalId,\r\n className,\r\n ...props\r\n}: LabeledInputProps) {\r\n const generatedId = React.useId();\r\n const id = externalId ?? generatedId;\r\n const descId = description ? `${id}-desc` : undefined;\r\n const errId = error ? `${id}-err` : undefined;\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', className)}>\r\n {label && (\r\n <Label htmlFor={id}>\r\n {label}\r\n {required && <span className='text-destructive ms-0.5'>*</span>}\r\n </Label>\r\n )}\r\n {description && (\r\n <p id={descId} className='text-muted-foreground text-sm'>\r\n {description}\r\n </p>\r\n )}\r\n <Input\r\n id={id}\r\n aria-describedby={\r\n [descId, errId].filter(Boolean).join(' ') || undefined\r\n }\r\n aria-invalid={!!error}\r\n error={!!error}\r\n {...props}\r\n />\r\n {error && (\r\n <p id={errId} role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </div>\r\n );\r\n}\r\n",
|
|
1293
|
+
"src/components/ui/composed/input-group/search-input.tsx": "import { SearchIcon, XIcon } from 'lucide-react';\r\nimport {\r\n InputGroup,\r\n InputGroupAddon,\r\n InputGroupButton,\r\n InputGroupInput,\r\n} from '@/components/ui/input-group';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype SearchInputProps = {\r\n value: string;\r\n onValueChange: (value: string) => void;\r\n placeholder?: string;\r\n clearable?: boolean;\r\n disabled?: boolean;\r\n className?: string;\r\n};\r\n\r\nexport function SearchInput({\r\n value,\r\n onValueChange,\r\n placeholder = m.ui_search_placeholder(),\r\n clearable = true,\r\n disabled,\r\n className,\r\n}: SearchInputProps) {\r\n return (\r\n <InputGroup className={cn('w-full', className)}>\r\n <InputGroupAddon align='inline-start'>\r\n <SearchIcon className='size-4' />\r\n </InputGroupAddon>\r\n <InputGroupInput\r\n value={value}\r\n onChange={e => onValueChange(e.target.value)}\r\n placeholder={placeholder}\r\n disabled={disabled}\r\n />\r\n {clearable && value && !disabled && (\r\n <InputGroupAddon align='inline-end'>\r\n <InputGroupButton\r\n onClick={() => onValueChange('')}\r\n aria-label={m.ui_clear_search()}\r\n >\r\n <XIcon />\r\n </InputGroupButton>\r\n </InputGroupAddon>\r\n )}\r\n </InputGroup>\r\n );\r\n}\r\n",
|
|
1294
|
+
"src/components/ui/composed/input-otp/otp-field.tsx": "import * as React from 'react';\r\nimport {\r\n InputOTP,\r\n InputOTPGroup,\r\n InputOTPSeparator,\r\n InputOTPSlot,\r\n} from '@/components/ui/input-otp';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype OtpFieldProps = {\r\n length?: 4 | 6;\r\n value: string;\r\n onChange: (value: string) => void;\r\n label?: string;\r\n error?: string;\r\n disabled?: boolean;\r\n className?: string;\r\n};\r\n\r\nexport function OtpField({\r\n length = 6,\r\n value,\r\n onChange,\r\n label,\r\n error,\r\n disabled,\r\n className,\r\n}: OtpFieldProps) {\r\n const id = React.useId();\r\n\r\n return (\r\n <div className={cn('flex flex-col items-start gap-2', className)}>\r\n {label && (\r\n <Label htmlFor={id} className='text-sm font-medium'>\r\n {label}\r\n </Label>\r\n )}\r\n <InputOTP\r\n id={id}\r\n maxLength={length}\r\n value={value}\r\n onChange={onChange}\r\n disabled={disabled}\r\n aria-invalid={!!error}\r\n >\r\n {length === 6 ? (\r\n <>\r\n <InputOTPGroup>\r\n <InputOTPSlot index={0} />\r\n <InputOTPSlot index={1} />\r\n <InputOTPSlot index={2} />\r\n </InputOTPGroup>\r\n <InputOTPSeparator />\r\n <InputOTPGroup>\r\n <InputOTPSlot index={3} />\r\n <InputOTPSlot index={4} />\r\n <InputOTPSlot index={5} />\r\n </InputOTPGroup>\r\n </>\r\n ) : (\r\n <InputOTPGroup>\r\n {Array.from({ length }).map((_, i) => (\r\n <InputOTPSlot key={i} index={i} />\r\n ))}\r\n </InputOTPGroup>\r\n )}\r\n </InputOTP>\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </div>\r\n );\r\n}\r\n",
|
|
1295
|
+
"src/components/ui/composed/navigation-menu/site-nav.tsx": "import * as React from 'react';\r\nimport {\r\n NavigationMenu,\r\n NavigationMenuContent,\r\n NavigationMenuItem,\r\n NavigationMenuLink,\r\n NavigationMenuList,\r\n NavigationMenuTrigger,\r\n} from '@/components/ui/navigation-menu';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype NavChild = {\r\n label: string;\r\n href: string;\r\n description?: string;\r\n};\r\n\r\ntype NavItem = {\r\n label: string;\r\n href?: string;\r\n children?: NavChild[];\r\n};\r\n\r\ntype SiteNavProps = {\r\n items: NavItem[];\r\n className?: string;\r\n};\r\n\r\nexport function SiteNav({ items, className }: SiteNavProps) {\r\n return (\r\n <NavigationMenu className={className}>\r\n <NavigationMenuList>\r\n {items.map(item =>\r\n item.children?.length ? (\r\n <NavigationMenuItem key={item.label}>\r\n <NavigationMenuTrigger>{item.label}</NavigationMenuTrigger>\r\n <NavigationMenuContent>\r\n <ul className='grid w-[280px] gap-1 p-2 md:w-[360px]'>\r\n {item.children.map(child => (\r\n <li key={child.href}>\r\n <NavigationMenuLink\r\n href={child.href}\r\n className='flex flex-col gap-0.5 p-2'\r\n >\r\n <span className='text-sm font-medium'>\r\n {child.label}\r\n </span>\r\n {child.description && (\r\n <span className='text-muted-foreground line-clamp-1 text-xs'>\r\n {child.description}\r\n </span>\r\n )}\r\n </NavigationMenuLink>\r\n </li>\r\n ))}\r\n </ul>\r\n </NavigationMenuContent>\r\n </NavigationMenuItem>\r\n ) : (\r\n <NavigationMenuItem key={item.label}>\r\n <NavigationMenuLink href={item.href ?? '#'}>\r\n {item.label}\r\n </NavigationMenuLink>\r\n </NavigationMenuItem>\r\n )\r\n )}\r\n </NavigationMenuList>\r\n </NavigationMenu>\r\n );\r\n}\r\n",
|
|
1296
|
+
"src/components/ui/composed/pagination/data-pagination.tsx": "import * as React from 'react';\r\nimport {\r\n Pagination,\r\n PaginationContent,\r\n PaginationEllipsis,\r\n PaginationItem,\r\n PaginationLink,\r\n PaginationNext,\r\n PaginationPrevious,\r\n} from '@/components/ui/pagination';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype DataPaginationProps = {\r\n currentPage: number;\r\n totalPages: number;\r\n onPageChange: (page: number) => void;\r\n siblingCount?: number;\r\n className?: string;\r\n};\r\n\r\nfunction buildPageRange(\r\n current: number,\r\n total: number,\r\n siblings: number\r\n): (number | 'ellipsis')[] {\r\n if (total <= 1) return [1];\r\n\r\n const delta = siblings + 1;\r\n const innerStart = Math.max(2, current - delta);\r\n const innerEnd = Math.min(total - 1, current + delta);\r\n\r\n const inner: number[] = [];\r\n for (let i = innerStart; i <= innerEnd; i++) inner.push(i);\r\n\r\n const result: (number | 'ellipsis')[] = [1];\r\n if (inner[0] > 2) result.push('ellipsis');\r\n result.push(...inner);\r\n if (inner[inner.length - 1] < total - 1) result.push('ellipsis');\r\n if (total > 1) result.push(total);\r\n\r\n return result;\r\n}\r\n\r\nexport function DataPagination({\r\n currentPage,\r\n totalPages,\r\n onPageChange,\r\n siblingCount = 1,\r\n className,\r\n}: DataPaginationProps) {\r\n if (totalPages <= 1) return null;\r\n\r\n const pages = buildPageRange(currentPage, totalPages, siblingCount);\r\n\r\n return (\r\n <Pagination className={cn(className)}>\r\n <PaginationContent>\r\n <PaginationItem>\r\n <PaginationPrevious\r\n href='#'\r\n text={m.ui_previous()}\r\n aria-label={m.ui_previous_page()}\r\n onClick={e => {\r\n e.preventDefault();\r\n if (currentPage > 1) onPageChange(currentPage - 1);\r\n }}\r\n disabled={currentPage <= 1}\r\n />\r\n </PaginationItem>\r\n\r\n {pages.map((page, index) =>\r\n page === 'ellipsis' ? (\r\n <PaginationItem key={`ellipsis-${index}`}>\r\n <PaginationEllipsis />\r\n </PaginationItem>\r\n ) : (\r\n <PaginationItem key={page}>\r\n <PaginationLink\r\n href='#'\r\n isActive={page === currentPage}\r\n onClick={e => {\r\n e.preventDefault();\r\n onPageChange(page);\r\n }}\r\n >\r\n {page}\r\n </PaginationLink>\r\n </PaginationItem>\r\n )\r\n )}\r\n\r\n <PaginationItem>\r\n <PaginationNext\r\n href='#'\r\n text={m.ui_next()}\r\n aria-label={m.ui_next_page()}\r\n onClick={e => {\r\n e.preventDefault();\r\n if (currentPage < totalPages) onPageChange(currentPage + 1);\r\n }}\r\n disabled={currentPage >= totalPages}\r\n />\r\n </PaginationItem>\r\n </PaginationContent>\r\n </Pagination>\r\n );\r\n}\r\n",
|
|
1297
|
+
"src/components/ui/composed/popover/info-popover.tsx": "import * as React from 'react';\r\nimport {\r\n Popover,\r\n PopoverContent,\r\n PopoverTrigger,\r\n} from '@/components/ui/popover';\r\nimport { Button } from '@/components/ui/button';\r\nimport { InfoIcon } from 'lucide-react';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype InfoPopoverProps = {\r\n content: React.ReactNode;\r\n trigger?: React.ReactNode;\r\n side?: 'top' | 'right' | 'bottom' | 'left';\r\n align?: 'start' | 'center' | 'end';\r\n className?: string;\r\n};\r\n\r\nexport function InfoPopover({\r\n content,\r\n trigger,\r\n side = 'top',\r\n align = 'center',\r\n className,\r\n}: InfoPopoverProps) {\r\n return (\r\n <Popover>\r\n <PopoverTrigger asChild>\r\n {trigger ?? (\r\n <Button\r\n variant='ghost'\r\n size='icon'\r\n className='size-6 rounded-full'\r\n aria-label={m.ui_more_information()}\r\n >\r\n <InfoIcon className='size-3.5' />\r\n </Button>\r\n )}\r\n </PopoverTrigger>\r\n <PopoverContent\r\n side={side}\r\n align={align}\r\n className={cn('text-sm', className)}\r\n >\r\n {content}\r\n </PopoverContent>\r\n </Popover>\r\n );\r\n}\r\n",
|
|
1298
|
+
"src/components/ui/composed/radio-group/options-radio-group.tsx": "import * as React from 'react';\r\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype RadioOption = {\r\n label: string;\r\n value: string;\r\n description?: string;\r\n disabled?: boolean;\r\n};\r\n\r\ntype OptionsRadioGroupProps = {\r\n options: RadioOption[];\r\n value: string;\r\n onValueChange: (value: string) => void;\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n orientation?: 'vertical' | 'horizontal';\r\n className?: string;\r\n};\r\n\r\nexport function OptionsRadioGroup({\r\n options,\r\n value,\r\n onValueChange,\r\n label,\r\n description,\r\n error,\r\n orientation = 'vertical',\r\n className,\r\n}: OptionsRadioGroupProps) {\r\n const groupId = React.useId();\r\n\r\n return (\r\n <fieldset className={cn('flex flex-col gap-2', className)}>\r\n {label && (\r\n <legend className='mb-1 text-sm font-medium leading-none'>\r\n {label}\r\n </legend>\r\n )}\r\n {description && (\r\n <p className='text-muted-foreground text-sm'>{description}</p>\r\n )}\r\n <RadioGroup\r\n value={value}\r\n onValueChange={onValueChange}\r\n className={cn(\r\n orientation === 'horizontal' &&\r\n 'flex flex-row flex-wrap gap-x-6 gap-y-2'\r\n )}\r\n >\r\n {options.map(option => {\r\n const id = `${groupId}-${option.value}`;\r\n return (\r\n <div key={option.value} className='flex items-start gap-2'>\r\n <RadioGroupItem\r\n id={id}\r\n value={option.value}\r\n disabled={option.disabled}\r\n className='mt-0.5'\r\n />\r\n <div className='flex flex-col gap-0.5'>\r\n <Label\r\n htmlFor={id}\r\n className={cn(option.disabled && 'opacity-50')}\r\n >\r\n {option.label}\r\n </Label>\r\n {option.description && (\r\n <span className='text-muted-foreground text-xs'>\r\n {option.description}\r\n </span>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </RadioGroup>\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </fieldset>\r\n );\r\n}\r\n",
|
|
1299
|
+
"src/components/ui/composed/select/labeled-select.tsx": "import * as React from 'react';\r\n\r\nimport { Label } from '@/components/ui/label';\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '@/components/ui/select';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype SelectOption = {\r\n label: string;\r\n value: string;\r\n disabled?: boolean;\r\n};\r\n\r\ntype LabeledSelectProps = {\r\n options: SelectOption[];\r\n value?: string;\r\n onValueChange?: (value: string) => void;\r\n placeholder?: string;\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n required?: boolean;\r\n disabled?: boolean;\r\n className?: string;\r\n id?: string;\r\n};\r\n\r\nexport function LabeledSelect({\r\n options,\r\n value,\r\n onValueChange,\r\n placeholder = m.ui_select_placeholder(),\r\n label,\r\n description,\r\n error,\r\n required,\r\n disabled,\r\n className,\r\n id: externalId,\r\n}: LabeledSelectProps) {\r\n const generatedId = React.useId();\r\n const id = externalId ?? generatedId;\r\n const descId = description ? `${id}-desc` : undefined;\r\n const errId = error ? `${id}-err` : undefined;\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', className)}>\r\n {label ? (\r\n <Label htmlFor={id}>\r\n {label}\r\n {required ? <span className='text-destructive ms-0.5'>*</span> : null}\r\n </Label>\r\n ) : null}\r\n {description ? (\r\n <p id={descId} className='text-muted-foreground text-sm'>\r\n {description}\r\n </p>\r\n ) : null}\r\n <Select\r\n value={value}\r\n onValueChange={next => {\r\n onValueChange?.(next ?? '');\r\n }}\r\n disabled={disabled}\r\n >\r\n <SelectTrigger\r\n id={id}\r\n className='w-full'\r\n aria-invalid={!!error}\r\n aria-describedby={\r\n [descId, errId].filter(Boolean).join(' ') || undefined\r\n }\r\n >\r\n <SelectValue placeholder={placeholder} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {options.map(option => (\r\n <SelectItem\r\n key={option.value}\r\n value={option.value}\r\n disabled={option.disabled}\r\n >\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n {error ? (\r\n <p id={errId} role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n",
|
|
1300
|
+
"src/components/ui/composed/select/select-groups.tsx": "import * as React from 'react';\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectGroup,\r\n SelectItem,\r\n SelectLabel,\r\n SelectSeparator,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '@/components/ui/select';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype SelectOption = {\r\n label: string;\r\n value: string;\r\n disabled?: boolean;\r\n};\r\n\r\ntype SelectOptionGroup = {\r\n label: string;\r\n options: SelectOption[];\r\n};\r\n\r\ntype SelectGroupsProps = {\r\n groups: SelectOptionGroup[];\r\n value?: string;\r\n onValueChange?: (value: string) => void;\r\n placeholder?: string;\r\n label?: string;\r\n error?: string;\r\n required?: boolean;\r\n disabled?: boolean;\r\n className?: string;\r\n};\r\n\r\nexport function SelectGroups({\r\n groups,\r\n value,\r\n onValueChange,\r\n placeholder = m.ui_select_placeholder(),\r\n label,\r\n error,\r\n required,\r\n disabled,\r\n className,\r\n}: SelectGroupsProps) {\r\n const id = React.useId();\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', className)}>\r\n {label && (\r\n <Label htmlFor={id}>\r\n {label}\r\n {required && <span className='text-destructive ms-0.5'>*</span>}\r\n </Label>\r\n )}\r\n <Select\r\n value={value}\r\n onValueChange={next => {\r\n onValueChange?.(next ?? '');\r\n }}\r\n disabled={disabled}\r\n >\r\n <SelectTrigger id={id} className='w-full' aria-invalid={!!error}>\r\n <SelectValue placeholder={placeholder} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {groups.map((group, index) => (\r\n <React.Fragment key={group.label}>\r\n {index > 0 && <SelectSeparator />}\r\n <SelectGroup>\r\n <SelectLabel>{group.label}</SelectLabel>\r\n {group.options.map(option => (\r\n <SelectItem\r\n key={option.value}\r\n value={option.value}\r\n disabled={option.disabled}\r\n >\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </SelectGroup>\r\n </React.Fragment>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n {error && (\r\n <p role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </div>\r\n );\r\n}\r\n",
|
|
1301
|
+
"src/components/ui/composed/sheet/filter-sheet.tsx": "import * as React from 'react';\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetDescription,\r\n SheetFooter,\r\n SheetHeader,\r\n SheetTitle,\r\n SheetTrigger,\r\n} from '@/components/ui/sheet';\r\n\r\ntype FilterSheetProps = {\r\n open?: boolean;\r\n onOpenChange?: (open: boolean) => void;\r\n trigger?: React.ReactNode;\r\n title: string;\r\n description?: string;\r\n children: React.ReactNode;\r\n footer?: React.ReactNode;\r\n side?: 'left' | 'right' | 'top' | 'bottom';\r\n};\r\n\r\nexport function FilterSheet({\r\n open,\r\n onOpenChange,\r\n trigger,\r\n title,\r\n description,\r\n children,\r\n footer,\r\n side = 'right',\r\n}: FilterSheetProps) {\r\n return (\r\n <Sheet open={open} onOpenChange={onOpenChange}>\r\n {trigger && <SheetTrigger asChild>{trigger}</SheetTrigger>}\r\n <SheetContent side={side} className='flex flex-col'>\r\n <SheetHeader>\r\n <SheetTitle>{title}</SheetTitle>\r\n {description && <SheetDescription>{description}</SheetDescription>}\r\n </SheetHeader>\r\n <div className='flex-1 overflow-y-auto px-4 py-4'>{children}</div>\r\n {footer && <SheetFooter className='px-4 pb-4'>{footer}</SheetFooter>}\r\n </SheetContent>\r\n </Sheet>\r\n );\r\n}\r\n",
|
|
1302
|
+
"src/components/ui/composed/skeleton/card-skeleton.tsx": "import { Skeleton } from '@/components/ui/skeleton';\r\nimport { Card, CardContent, CardHeader } from '@/components/ui/card';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype CardSkeletonProps = {\r\n lines?: number;\r\n showHeader?: boolean;\r\n showAvatar?: boolean;\r\n className?: string;\r\n};\r\n\r\nexport function CardSkeleton({\r\n lines = 3,\r\n showHeader = true,\r\n showAvatar = false,\r\n className,\r\n}: CardSkeletonProps) {\r\n return (\r\n <Card className={cn('overflow-hidden', className)}>\r\n {showHeader && (\r\n <CardHeader className='gap-3'>\r\n <div className='flex items-center gap-3'>\r\n {showAvatar && (\r\n <Skeleton className='size-10 shrink-0 rounded-full' />\r\n )}\r\n <div className='flex flex-1 flex-col gap-2'>\r\n <Skeleton className='h-4 w-1/3' />\r\n <Skeleton className='h-3 w-1/4' />\r\n </div>\r\n </div>\r\n </CardHeader>\r\n )}\r\n <CardContent className='flex flex-col gap-2'>\r\n {Array.from({ length: lines }).map((_, i) => (\r\n <Skeleton\r\n key={i}\r\n className={cn('h-3', i === lines - 1 ? 'w-2/3' : 'w-full')}\r\n />\r\n ))}\r\n </CardContent>\r\n </Card>\r\n );\r\n}\r\n",
|
|
1303
|
+
"src/components/ui/composed/table/simple-data-table.tsx": "import * as React from 'react';\r\nimport {\r\n Table,\r\n TableBody,\r\n TableCaption,\r\n TableCell,\r\n TableHead,\r\n TableHeader,\r\n TableRow,\r\n} from '@/components/ui/table';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype Column<TData> = {\r\n key: string;\r\n header: string;\r\n cell: (row: TData) => React.ReactNode;\r\n headerClassName?: string;\r\n cellClassName?: string;\r\n};\r\n\r\ntype SimpleDataTableProps<TData extends { id: string | number }> = {\r\n columns: Column<TData>[];\r\n data: TData[];\r\n caption?: string;\r\n emptyMessage?: string;\r\n className?: string;\r\n};\r\n\r\nexport function SimpleDataTable<TData extends { id: string | number }>({\r\n columns,\r\n data,\r\n caption,\r\n emptyMessage = m.ui_no_results(),\r\n className,\r\n}: SimpleDataTableProps<TData>) {\r\n return (\r\n <Table className={className}>\r\n {caption && <TableCaption>{caption}</TableCaption>}\r\n <TableHeader>\r\n <TableRow>\r\n {columns.map(col => (\r\n <TableHead key={col.key} className={cn(col.headerClassName)}>\r\n {col.header}\r\n </TableHead>\r\n ))}\r\n </TableRow>\r\n </TableHeader>\r\n <TableBody>\r\n {data.length === 0 ? (\r\n <TableRow>\r\n <TableCell\r\n colSpan={columns.length}\r\n className='h-24 text-center text-muted-foreground'\r\n >\r\n {emptyMessage}\r\n </TableCell>\r\n </TableRow>\r\n ) : (\r\n data.map(row => (\r\n <TableRow key={row.id}>\r\n {columns.map(col => (\r\n <TableCell key={col.key} className={cn(col.cellClassName)}>\r\n {col.cell(row)}\r\n </TableCell>\r\n ))}\r\n </TableRow>\r\n ))\r\n )}\r\n </TableBody>\r\n </Table>\r\n );\r\n}\r\n",
|
|
1304
|
+
"src/components/ui/composed/tabs/icon-tabs.tsx": "import * as React from 'react';\r\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype TabItem = {\r\n value: string;\r\n label: string;\r\n icon?: React.ReactNode;\r\n content: React.ReactNode;\r\n disabled?: boolean;\r\n};\r\n\r\ntype IconTabsProps = {\r\n items: TabItem[];\r\n defaultValue?: string;\r\n value?: string;\r\n onValueChange?: (value: string) => void;\r\n className?: string;\r\n listClassName?: string;\r\n};\r\n\r\nexport function IconTabs({\r\n items,\r\n defaultValue,\r\n value,\r\n onValueChange,\r\n className,\r\n listClassName,\r\n}: IconTabsProps) {\r\n return (\r\n <Tabs\r\n defaultValue={defaultValue ?? items[0]?.value}\r\n value={value}\r\n onValueChange={onValueChange}\r\n className={className}\r\n >\r\n <TabsList className={cn(listClassName)}>\r\n {items.map(item => (\r\n <TabsTrigger\r\n key={item.value}\r\n value={item.value}\r\n disabled={item.disabled}\r\n >\r\n {item.icon}\r\n {item.label}\r\n </TabsTrigger>\r\n ))}\r\n </TabsList>\r\n {items.map(item => (\r\n <TabsContent key={item.value} value={item.value}>\r\n {item.content}\r\n </TabsContent>\r\n ))}\r\n </Tabs>\r\n );\r\n}\r\n",
|
|
1305
|
+
"src/components/ui/composed/textarea/labeled-textarea.tsx": "import * as React from 'react';\r\nimport { Textarea } from '@/components/ui/textarea';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype LabeledTextareaProps = React.ComponentProps<typeof Textarea> & {\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n required?: boolean;\r\n maxLength?: number;\r\n showCharCount?: boolean;\r\n};\r\n\r\nexport function LabeledTextarea({\r\n label,\r\n description,\r\n error,\r\n required,\r\n maxLength,\r\n showCharCount = false,\r\n value,\r\n id: externalId,\r\n className,\r\n ...props\r\n}: LabeledTextareaProps) {\r\n const generatedId = React.useId();\r\n const id = externalId ?? generatedId;\r\n const descId = description ? `${id}-desc` : undefined;\r\n const errId = error ? `${id}-err` : undefined;\r\n const charCount = typeof value === 'string' ? value.length : 0;\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', className)}>\r\n <div className='flex items-baseline justify-between gap-2'>\r\n {label && (\r\n <Label htmlFor={id}>\r\n {label}\r\n {required && <span className='text-destructive ms-0.5'>*</span>}\r\n </Label>\r\n )}\r\n {showCharCount && maxLength && (\r\n <span className='text-muted-foreground text-xs tabular-nums'>\r\n {charCount}/{maxLength}\r\n </span>\r\n )}\r\n </div>\r\n {description && (\r\n <p id={descId} className='text-muted-foreground text-sm'>\r\n {description}\r\n </p>\r\n )}\r\n <Textarea\r\n id={id}\r\n value={value}\r\n maxLength={maxLength}\r\n aria-describedby={\r\n [descId, errId].filter(Boolean).join(' ') || undefined\r\n }\r\n aria-invalid={!!error}\r\n {...props}\r\n />\r\n {error && (\r\n <p id={errId} role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n )}\r\n </div>\r\n );\r\n}\r\n",
|
|
1306
|
+
"src/components/ui/composed/tooltip/icon-tooltip.tsx": "import * as React from 'react';\r\nimport {\r\n Tooltip,\r\n TooltipContent,\r\n TooltipTrigger,\r\n} from '@/components/ui/tooltip';\r\nimport { cn } from '@/lib/utils';\r\n\r\ntype IconTooltipProps = {\r\n content: React.ReactNode;\r\n children: React.ReactNode;\r\n side?: 'top' | 'right' | 'bottom' | 'left';\r\n align?: 'start' | 'center' | 'end';\r\n className?: string;\r\n};\r\n\r\nexport function IconTooltip({\r\n content,\r\n children,\r\n side = 'top',\r\n align = 'center',\r\n className,\r\n}: IconTooltipProps) {\r\n return (\r\n <Tooltip>\r\n <TooltipTrigger asChild>{children}</TooltipTrigger>\r\n <TooltipContent side={side} align={align} className={className}>\r\n {content}\r\n </TooltipContent>\r\n </Tooltip>\r\n );\r\n}\r\n",
|
|
1307
|
+
"src/components/ui/composed/upload/file-dropzone.tsx": "import * as React from 'react';\r\nimport { UploadIcon, XIcon } from 'lucide-react';\r\n\r\nimport { Button } from '@/components/ui/button';\r\nimport { Input } from '@/components/ui/input';\r\nimport { Label } from '@/components/ui/label';\r\nimport { cn } from '@/lib/utils';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype FileDropzoneProps = {\r\n value?: File[];\r\n onValueChange?: (files: File[]) => void;\r\n label?: string;\r\n description?: string;\r\n error?: string;\r\n required?: boolean;\r\n disabled?: boolean;\r\n multiple?: boolean;\r\n accept?: string;\r\n maxSize?: number;\r\n id?: string;\r\n className?: string;\r\n};\r\n\r\nconst formatBytes = (bytes: number) => {\r\n if (bytes < 1024) return `${bytes} B`;\r\n if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;\r\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\r\n};\r\n\r\nconst fileKey = (file: File) =>\r\n `${file.name}-${file.size}-${file.lastModified}`;\r\n\r\nexport function FileDropzone({\r\n value = [],\r\n onValueChange,\r\n label,\r\n description,\r\n error,\r\n required,\r\n disabled,\r\n multiple = false,\r\n accept,\r\n maxSize,\r\n id: externalId,\r\n className,\r\n}: FileDropzoneProps) {\r\n const generatedId = React.useId();\r\n const id = externalId ?? generatedId;\r\n const descId = description ? `${id}-desc` : undefined;\r\n const errId = error ? `${id}-err` : undefined;\r\n const [isDragging, setIsDragging] = React.useState(false);\r\n\r\n const addFiles = (incoming: File[]) => {\r\n const allowed = maxSize\r\n ? incoming.filter(file => file.size <= maxSize)\r\n : incoming;\r\n if (allowed.length === 0) return;\r\n\r\n if (!multiple) {\r\n onValueChange?.(allowed.slice(0, 1));\r\n return;\r\n }\r\n\r\n const existing = new Set(value.map(fileKey));\r\n onValueChange?.([\r\n ...value,\r\n ...allowed.filter(file => !existing.has(fileKey(file))),\r\n ]);\r\n };\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', className)}>\r\n {label ? (\r\n <Label htmlFor={id}>\r\n {label}\r\n {required ? <span className='text-destructive ms-0.5'>*</span> : null}\r\n </Label>\r\n ) : null}\r\n {description ? (\r\n <p id={descId} className='text-muted-foreground text-sm'>\r\n {description}\r\n </p>\r\n ) : null}\r\n <label\r\n htmlFor={id}\r\n onDragOver={event => {\r\n event.preventDefault();\r\n if (!disabled) setIsDragging(true);\r\n }}\r\n onDragLeave={() => setIsDragging(false)}\r\n onDrop={event => {\r\n event.preventDefault();\r\n setIsDragging(false);\r\n if (disabled) return;\r\n addFiles(Array.from(event.dataTransfer.files));\r\n }}\r\n className={cn(\r\n 'border-input bg-background flex min-h-32 cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed px-4 py-6 text-center transition-colors',\r\n isDragging && 'border-ring bg-muted/50',\r\n error && 'border-destructive',\r\n disabled && 'pointer-events-none opacity-50'\r\n )}\r\n >\r\n <UploadIcon className='text-muted-foreground size-6' />\r\n <span className='text-sm'>{m.ui_dropzone_prompt()}</span>\r\n <span className='text-primary text-sm font-medium'>\r\n {m.ui_browse_files()}\r\n </span>\r\n {maxSize ? (\r\n <span className='text-muted-foreground text-xs'>\r\n {m.ui_max_size({ size: formatBytes(maxSize) })}\r\n </span>\r\n ) : null}\r\n <Input\r\n id={id}\r\n type='file'\r\n accept={accept}\r\n multiple={multiple}\r\n disabled={disabled}\r\n className='sr-only'\r\n aria-describedby={\r\n [descId, errId].filter(Boolean).join(' ') || undefined\r\n }\r\n aria-invalid={!!error}\r\n onChange={event => {\r\n addFiles(Array.from(event.target.files ?? []));\r\n event.target.value = '';\r\n }}\r\n />\r\n </label>\r\n {value.length > 0 ? (\r\n <ul className='flex flex-col gap-1'>\r\n {value.map(file => (\r\n <li\r\n key={fileKey(file)}\r\n className='bg-muted flex items-center gap-2 rounded-md px-2 py-1.5 text-sm'\r\n >\r\n <span className='min-w-0 flex-1 truncate text-start'>\r\n {file.name}\r\n </span>\r\n <span className='text-muted-foreground shrink-0 text-xs'>\r\n {formatBytes(file.size)}\r\n </span>\r\n <Button\r\n type='button'\r\n variant='ghost'\r\n size='icon-xs'\r\n disabled={disabled}\r\n aria-label={m.ui_remove_file()}\r\n onClick={() =>\r\n onValueChange?.(\r\n value.filter(item => fileKey(item) !== fileKey(file))\r\n )\r\n }\r\n >\r\n <XIcon />\r\n </Button>\r\n </li>\r\n ))}\r\n </ul>\r\n ) : null}\r\n {error ? (\r\n <p id={errId} role='alert' className='text-destructive text-sm'>\r\n {error}\r\n </p>\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n",
|
|
1308
|
+
"src/components/ui/dialog.tsx": "import * as React from \"react\"\r\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button } from \"#/components/ui/button.tsx\"\r\nimport { XIcon } from \"lucide-react\"\r\n\r\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\r\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />\r\n}\r\n\r\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\r\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />\r\n}\r\n\r\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\r\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />\r\n}\r\n\r\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\r\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />\r\n}\r\n\r\nfunction DialogOverlay({\r\n className,\r\n ...props\r\n}: DialogPrimitive.Backdrop.Props) {\r\n return (\r\n <DialogPrimitive.Backdrop\r\n data-slot=\"dialog-overlay\"\r\n className={cn(\r\n \"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DialogContent({\r\n className,\r\n children,\r\n showCloseButton = true,\r\n ...props\r\n}: DialogPrimitive.Popup.Props & {\r\n showCloseButton?: boolean\r\n}) {\r\n return (\r\n <DialogPortal>\r\n <DialogOverlay />\r\n <DialogPrimitive.Popup\r\n data-slot=\"dialog-content\"\r\n className={cn(\r\n \"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-popover p-6 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n {showCloseButton && (\r\n <DialogPrimitive.Close\r\n data-slot=\"dialog-close\"\r\n render={\r\n <Button\r\n variant=\"ghost\"\r\n className=\"absolute top-4 right-4\"\r\n size=\"icon-sm\"\r\n />\r\n }\r\n >\r\n <XIcon\r\n />\r\n <span className=\"sr-only\">Close</span>\r\n </DialogPrimitive.Close>\r\n )}\r\n </DialogPrimitive.Popup>\r\n </DialogPortal>\r\n )\r\n}\r\n\r\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"dialog-header\"\r\n className={cn(\"flex flex-col gap-2\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DialogFooter({\r\n className,\r\n showCloseButton = false,\r\n children,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n showCloseButton?: boolean\r\n}) {\r\n return (\r\n <div\r\n data-slot=\"dialog-footer\"\r\n className={cn(\r\n \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n {showCloseButton && (\r\n <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\r\n Close\r\n </DialogPrimitive.Close>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\r\n return (\r\n <DialogPrimitive.Title\r\n data-slot=\"dialog-title\"\r\n className={cn(\"font-heading leading-none font-medium\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DialogDescription({\r\n className,\r\n ...props\r\n}: DialogPrimitive.Description.Props) {\r\n return (\r\n <DialogPrimitive.Description\r\n data-slot=\"dialog-description\"\r\n className={cn(\r\n \"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Dialog,\r\n DialogClose,\r\n DialogContent,\r\n DialogDescription,\r\n DialogFooter,\r\n DialogHeader,\r\n DialogOverlay,\r\n DialogPortal,\r\n DialogTitle,\r\n DialogTrigger,\r\n}\r\n",
|
|
1309
|
+
"src/components/ui/drawer.tsx": "import * as React from \"react\"\r\nimport { Drawer as DrawerPrimitive } from \"vaul\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Drawer({\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Root>) {\r\n return <DrawerPrimitive.Root data-slot=\"drawer\" {...props} />\r\n}\r\n\r\nfunction DrawerTrigger({\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {\r\n return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />\r\n}\r\n\r\nfunction DrawerPortal({\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {\r\n return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />\r\n}\r\n\r\nfunction DrawerClose({\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Close>) {\r\n return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />\r\n}\r\n\r\nfunction DrawerOverlay({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {\r\n return (\r\n <DrawerPrimitive.Overlay\r\n data-slot=\"drawer-overlay\"\r\n className={cn(\r\n \"fixed inset-0 z-50 bg-black/10 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DrawerContent({\r\n className,\r\n children,\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Content>) {\r\n return (\r\n <DrawerPortal data-slot=\"drawer-portal\">\r\n <DrawerOverlay />\r\n <DrawerPrimitive.Content\r\n data-slot=\"drawer-content\"\r\n className={cn(\r\n \"group/drawer-content fixed z-50 flex h-auto flex-col bg-popover text-sm text-popover-foreground data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <div className=\"mx-auto mt-4 hidden h-1.5 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\r\n {children}\r\n </DrawerPrimitive.Content>\r\n </DrawerPortal>\r\n )\r\n}\r\n\r\nfunction DrawerHeader({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"drawer-header\"\r\n className={cn(\r\n \"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DrawerFooter({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"drawer-footer\"\r\n className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DrawerTitle({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Title>) {\r\n return (\r\n <DrawerPrimitive.Title\r\n data-slot=\"drawer-title\"\r\n className={cn(\"font-heading font-medium text-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DrawerDescription({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof DrawerPrimitive.Description>) {\r\n return (\r\n <DrawerPrimitive.Description\r\n data-slot=\"drawer-description\"\r\n className={cn(\"text-sm text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Drawer,\r\n DrawerPortal,\r\n DrawerOverlay,\r\n DrawerTrigger,\r\n DrawerClose,\r\n DrawerContent,\r\n DrawerHeader,\r\n DrawerFooter,\r\n DrawerTitle,\r\n DrawerDescription,\r\n}\r\n",
|
|
1310
|
+
"src/components/ui/dropdown-menu.tsx": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { ChevronRightIcon, CheckIcon } from \"lucide-react\"\r\n\r\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\r\n return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\r\n}\r\n\r\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\r\n return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\r\n}\r\n\r\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\r\n return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />\r\n}\r\n\r\nfunction DropdownMenuContent({\r\n align = \"start\",\r\n alignOffset = 0,\r\n side = \"bottom\",\r\n sideOffset = 4,\r\n className,\r\n ...props\r\n}: MenuPrimitive.Popup.Props &\r\n Pick<\r\n MenuPrimitive.Positioner.Props,\r\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\r\n >) {\r\n return (\r\n <MenuPrimitive.Portal>\r\n <MenuPrimitive.Positioner\r\n className=\"isolate z-50 outline-none\"\r\n align={align}\r\n alignOffset={alignOffset}\r\n side={side}\r\n sideOffset={sideOffset}\r\n >\r\n <MenuPrimitive.Popup\r\n data-slot=\"dropdown-menu-content\"\r\n className={cn(\"z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95\", className )}\r\n {...props}\r\n />\r\n </MenuPrimitive.Positioner>\r\n </MenuPrimitive.Portal>\r\n )\r\n}\r\n\r\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\r\n return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\r\n}\r\n\r\nfunction DropdownMenuLabel({\r\n className,\r\n inset,\r\n ...props\r\n}: MenuPrimitive.GroupLabel.Props & {\r\n inset?: boolean\r\n}) {\r\n return (\r\n <MenuPrimitive.GroupLabel\r\n data-slot=\"dropdown-menu-label\"\r\n data-inset={inset}\r\n className={cn(\r\n \"px-2 py-1.5 text-xs font-medium text-muted-foreground data-inset:pl-8\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DropdownMenuItem({\r\n className,\r\n inset,\r\n variant = \"default\",\r\n ...props\r\n}: MenuPrimitive.Item.Props & {\r\n inset?: boolean\r\n variant?: \"default\" | \"destructive\"\r\n}) {\r\n return (\r\n <MenuPrimitive.Item\r\n data-slot=\"dropdown-menu-item\"\r\n data-inset={inset}\r\n data-variant={variant}\r\n className={cn(\r\n \"group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\r\n return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />\r\n}\r\n\r\nfunction DropdownMenuSubTrigger({\r\n className,\r\n inset,\r\n children,\r\n ...props\r\n}: MenuPrimitive.SubmenuTrigger.Props & {\r\n inset?: boolean\r\n}) {\r\n return (\r\n <MenuPrimitive.SubmenuTrigger\r\n data-slot=\"dropdown-menu-sub-trigger\"\r\n data-inset={inset}\r\n className={cn(\r\n \"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <ChevronRightIcon className=\"ml-auto\" />\r\n </MenuPrimitive.SubmenuTrigger>\r\n )\r\n}\r\n\r\nfunction DropdownMenuSubContent({\r\n align = \"start\",\r\n alignOffset = -3,\r\n side = \"right\",\r\n sideOffset = 0,\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof DropdownMenuContent>) {\r\n return (\r\n <DropdownMenuContent\r\n data-slot=\"dropdown-menu-sub-content\"\r\n className={cn(\"w-auto min-w-[96px] rounded-md bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\", className )}\r\n align={align}\r\n alignOffset={alignOffset}\r\n side={side}\r\n sideOffset={sideOffset}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DropdownMenuCheckboxItem({\r\n className,\r\n children,\r\n checked,\r\n inset,\r\n ...props\r\n}: MenuPrimitive.CheckboxItem.Props & {\r\n inset?: boolean\r\n}) {\r\n return (\r\n <MenuPrimitive.CheckboxItem\r\n data-slot=\"dropdown-menu-checkbox-item\"\r\n data-inset={inset}\r\n className={cn(\r\n \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n checked={checked}\r\n {...props}\r\n >\r\n <span\r\n className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\r\n data-slot=\"dropdown-menu-checkbox-item-indicator\"\r\n >\r\n <MenuPrimitive.CheckboxItemIndicator>\r\n <CheckIcon\r\n />\r\n </MenuPrimitive.CheckboxItemIndicator>\r\n </span>\r\n {children}\r\n </MenuPrimitive.CheckboxItem>\r\n )\r\n}\r\n\r\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\r\n return (\r\n <MenuPrimitive.RadioGroup\r\n data-slot=\"dropdown-menu-radio-group\"\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DropdownMenuRadioItem({\r\n className,\r\n children,\r\n inset,\r\n ...props\r\n}: MenuPrimitive.RadioItem.Props & {\r\n inset?: boolean\r\n}) {\r\n return (\r\n <MenuPrimitive.RadioItem\r\n data-slot=\"dropdown-menu-radio-item\"\r\n data-inset={inset}\r\n className={cn(\r\n \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-8 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <span\r\n className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\r\n data-slot=\"dropdown-menu-radio-item-indicator\"\r\n >\r\n <MenuPrimitive.RadioItemIndicator>\r\n <CheckIcon\r\n />\r\n </MenuPrimitive.RadioItemIndicator>\r\n </span>\r\n {children}\r\n </MenuPrimitive.RadioItem>\r\n )\r\n}\r\n\r\nfunction DropdownMenuSeparator({\r\n className,\r\n ...props\r\n}: MenuPrimitive.Separator.Props) {\r\n return (\r\n <MenuPrimitive.Separator\r\n data-slot=\"dropdown-menu-separator\"\r\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction DropdownMenuShortcut({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"span\">) {\r\n return (\r\n <span\r\n data-slot=\"dropdown-menu-shortcut\"\r\n className={cn(\r\n \"ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n DropdownMenu,\r\n DropdownMenuPortal,\r\n DropdownMenuTrigger,\r\n DropdownMenuContent,\r\n DropdownMenuGroup,\r\n DropdownMenuLabel,\r\n DropdownMenuItem,\r\n DropdownMenuCheckboxItem,\r\n DropdownMenuRadioGroup,\r\n DropdownMenuRadioItem,\r\n DropdownMenuSeparator,\r\n DropdownMenuShortcut,\r\n DropdownMenuSub,\r\n DropdownMenuSubTrigger,\r\n DropdownMenuSubContent,\r\n}\r\n",
|
|
1311
|
+
"src/components/ui/empty.tsx": "import { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Empty({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"empty\"\r\n className={cn(\r\n \"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-lg border-dashed p-12 text-center text-balance\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"empty-header\"\r\n className={cn(\"flex max-w-sm flex-col items-center gap-2\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nconst emptyMediaVariants = cva(\r\n \"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\r\n {\r\n variants: {\r\n variant: {\r\n default: \"bg-transparent\",\r\n icon: \"flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"default\",\r\n },\r\n }\r\n)\r\n\r\nfunction EmptyMedia({\r\n className,\r\n variant = \"default\",\r\n ...props\r\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyMediaVariants>) {\r\n return (\r\n <div\r\n data-slot=\"empty-icon\"\r\n data-variant={variant}\r\n className={cn(emptyMediaVariants({ variant, className }))}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"empty-title\"\r\n className={cn(\"text-lg font-medium tracking-tight\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"empty-description\"\r\n className={cn(\r\n \"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"empty-content\"\r\n className={cn(\r\n \"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Empty,\r\n EmptyHeader,\r\n EmptyTitle,\r\n EmptyDescription,\r\n EmptyContent,\r\n EmptyMedia,\r\n}\r\n",
|
|
1312
|
+
"src/components/ui/field.tsx": "import { useMemo } from \"react\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Label } from \"#/components/ui/label.tsx\"\r\nimport { Separator } from \"#/components/ui/separator.tsx\"\r\n\r\nfunction FieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\r\n return (\r\n <fieldset\r\n data-slot=\"field-set\"\r\n className={cn(\r\n \"flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction FieldLegend({\r\n className,\r\n variant = \"legend\",\r\n ...props\r\n}: React.ComponentProps<\"legend\"> & { variant?: \"legend\" | \"label\" }) {\r\n return (\r\n <legend\r\n data-slot=\"field-legend\"\r\n data-variant={variant}\r\n className={cn(\r\n \"mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"field-group\"\r\n className={cn(\r\n \"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nconst fieldVariants = cva(\r\n \"group/field flex w-full gap-3 data-[invalid=true]:text-destructive\",\r\n {\r\n variants: {\r\n orientation: {\r\n vertical: \"flex-col *:w-full [&>.sr-only]:w-auto\",\r\n horizontal:\r\n \"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\r\n responsive:\r\n \"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\r\n },\r\n },\r\n defaultVariants: {\r\n orientation: \"vertical\",\r\n },\r\n }\r\n)\r\n\r\nfunction Field({\r\n className,\r\n orientation = \"vertical\",\r\n ...props\r\n}: React.ComponentProps<\"div\"> & VariantProps<typeof fieldVariants>) {\r\n return (\r\n <div\r\n role=\"group\"\r\n data-slot=\"field\"\r\n data-orientation={orientation}\r\n className={cn(fieldVariants({ orientation }), className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"field-content\"\r\n className={cn(\r\n \"group/field-content flex flex-1 flex-col gap-1 leading-snug\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction FieldLabel({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof Label>) {\r\n return (\r\n <Label\r\n data-slot=\"field-label\"\r\n className={cn(\r\n \"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-3 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10\",\r\n \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction FieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"field-label\"\r\n className={cn(\r\n \"flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction FieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\r\n return (\r\n <p\r\n data-slot=\"field-description\"\r\n className={cn(\r\n \"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5\",\r\n \"last:mt-0 nth-last-2:-mt-1\",\r\n \"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction FieldSeparator({\r\n children,\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n children?: React.ReactNode\r\n}) {\r\n return (\r\n <div\r\n data-slot=\"field-separator\"\r\n data-content={!!children}\r\n className={cn(\r\n \"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <Separator className=\"absolute inset-0 top-1/2\" />\r\n {children && (\r\n <span\r\n className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\r\n data-slot=\"field-separator-content\"\r\n >\r\n {children}\r\n </span>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nfunction FieldError({\r\n className,\r\n children,\r\n errors,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n errors?: Array<{ message?: string } | undefined>\r\n}) {\r\n const content = useMemo(() => {\r\n if (children) {\r\n return children\r\n }\r\n\r\n if (!errors?.length) {\r\n return null\r\n }\r\n\r\n const uniqueErrors = [\r\n ...new Map(errors.map((error) => [error?.message, error])).values(),\r\n ]\r\n\r\n if (uniqueErrors?.length == 1) {\r\n return uniqueErrors[0]?.message\r\n }\r\n\r\n return (\r\n <ul className=\"ml-4 flex list-disc flex-col gap-1\">\r\n {uniqueErrors.map(\r\n (error, index) =>\r\n error?.message && <li key={index}>{error.message}</li>\r\n )}\r\n </ul>\r\n )\r\n }, [children, errors])\r\n\r\n if (!content) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n className={cn(\"text-sm font-normal text-destructive\", className)}\r\n {...props}\r\n >\r\n {content}\r\n </div>\r\n )\r\n}\r\n\r\nexport {\r\n Field,\r\n FieldLabel,\r\n FieldDescription,\r\n FieldError,\r\n FieldGroup,\r\n FieldLegend,\r\n FieldSeparator,\r\n FieldSet,\r\n FieldContent,\r\n FieldTitle,\r\n}\r\n",
|
|
1313
|
+
"src/components/ui/input-group.tsx": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button } from \"#/components/ui/button.tsx\"\r\nimport { Input } from \"#/components/ui/input.tsx\"\r\nimport { Textarea } from \"#/components/ui/textarea.tsx\"\r\n\r\nfunction InputGroup({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"input-group\"\r\n role=\"group\"\r\n className={cn(\r\n \"group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nconst inputGroupAddonVariants = cva(\r\n \"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\r\n {\r\n variants: {\r\n align: {\r\n \"inline-start\":\r\n \"order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]\",\r\n \"inline-end\":\r\n \"order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]\",\r\n \"block-start\":\r\n \"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2\",\r\n \"block-end\":\r\n \"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2\",\r\n },\r\n },\r\n defaultVariants: {\r\n align: \"inline-start\",\r\n },\r\n }\r\n)\r\n\r\nfunction InputGroupAddon({\r\n className,\r\n align = \"inline-start\",\r\n ...props\r\n}: React.ComponentProps<\"div\"> & VariantProps<typeof inputGroupAddonVariants>) {\r\n return (\r\n <div\r\n role=\"group\"\r\n data-slot=\"input-group-addon\"\r\n data-align={align}\r\n className={cn(inputGroupAddonVariants({ align }), className)}\r\n onClick={(e) => {\r\n if ((e.target as HTMLElement).closest(\"button\")) {\r\n return\r\n }\r\n e.currentTarget.parentElement?.querySelector(\"input\")?.focus()\r\n }}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nconst inputGroupButtonVariants = cva(\r\n \"flex items-center gap-2 text-sm shadow-none\",\r\n {\r\n variants: {\r\n size: {\r\n xs: \"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\r\n sm: \"\",\r\n \"icon-xs\":\r\n \"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0\",\r\n \"icon-sm\": \"size-8 p-0 has-[>svg]:p-0\",\r\n },\r\n },\r\n defaultVariants: {\r\n size: \"xs\",\r\n },\r\n }\r\n)\r\n\r\nfunction InputGroupButton({\r\n className,\r\n type = \"button\",\r\n variant = \"ghost\",\r\n size = \"xs\",\r\n ...props\r\n}: Omit<React.ComponentProps<typeof Button>, \"size\" | \"type\"> &\r\n VariantProps<typeof inputGroupButtonVariants> & {\r\n type?: \"button\" | \"submit\" | \"reset\"\r\n }) {\r\n return (\r\n <Button\r\n type={type}\r\n data-size={size}\r\n variant={variant}\r\n className={cn(inputGroupButtonVariants({ size }), className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\r\n return (\r\n <span\r\n className={cn(\r\n \"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction InputGroupInput({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"input\">) {\r\n return (\r\n <Input\r\n data-slot=\"input-group-control\"\r\n className={cn(\r\n \"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction InputGroupTextarea({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"textarea\">) {\r\n return (\r\n <Textarea\r\n data-slot=\"input-group-control\"\r\n className={cn(\r\n \"flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n InputGroup,\r\n InputGroupAddon,\r\n InputGroupButton,\r\n InputGroupText,\r\n InputGroupInput,\r\n InputGroupTextarea,\r\n}\r\n",
|
|
1314
|
+
"src/components/ui/input-otp.tsx": "import * as React from \"react\"\r\nimport { OTPInput, OTPInputContext } from \"input-otp\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { MinusIcon } from \"lucide-react\"\r\n\r\nfunction InputOTP({\r\n className,\r\n containerClassName,\r\n ...props\r\n}: React.ComponentProps<typeof OTPInput> & {\r\n containerClassName?: string\r\n}) {\r\n return (\r\n <OTPInput\r\n data-slot=\"input-otp\"\r\n containerClassName={cn(\r\n \"cn-input-otp flex items-center has-disabled:opacity-50\",\r\n containerClassName\r\n )}\r\n spellCheck={false}\r\n className={cn(\"disabled:cursor-not-allowed\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction InputOTPGroup({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"input-otp-group\"\r\n className={cn(\r\n \"flex items-center rounded-md has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction InputOTPSlot({\r\n index,\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n index: number\r\n}) {\r\n const inputOTPContext = React.useContext(OTPInputContext)\r\n const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}\r\n\r\n return (\r\n <div\r\n data-slot=\"input-otp-slot\"\r\n data-active={isActive}\r\n className={cn(\r\n \"relative flex size-9 items-center justify-center border-y border-r border-input text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-3 data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {char}\r\n {hasFakeCaret && (\r\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\r\n <div className=\"h-4 w-px animate-caret-blink bg-foreground duration-1000\" />\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nfunction InputOTPSeparator({ ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"input-otp-separator\"\r\n className=\"flex items-center [&_svg:not([class*='size-'])]:size-4\"\r\n role=\"separator\"\r\n {...props}\r\n >\r\n <MinusIcon\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }\r\n",
|
|
1315
|
+
"src/components/ui/input.tsx": "import * as React from \"react\"\r\nimport { Input as InputPrimitive } from \"@base-ui/react/input\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\r\n return (\r\n <InputPrimitive\r\n type={type}\r\n data-slot=\"input\"\r\n className={cn(\r\n \"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Input }\r\n",
|
|
1316
|
+
"src/components/ui/label.tsx": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Label({ className, ...props }: React.ComponentProps<\"label\">) {\r\n return (\r\n <label\r\n data-slot=\"label\"\r\n className={cn(\r\n \"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Label }\r\n",
|
|
1317
|
+
"src/components/ui/navigation-menu.tsx": "import { NavigationMenu as NavigationMenuPrimitive } from \"@base-ui/react/navigation-menu\"\r\nimport { cva } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { ChevronDownIcon } from \"lucide-react\"\r\n\r\nfunction NavigationMenu({\r\n align = \"start\",\r\n className,\r\n children,\r\n ...props\r\n}: NavigationMenuPrimitive.Root.Props &\r\n Pick<NavigationMenuPrimitive.Positioner.Props, \"align\">) {\r\n return (\r\n <NavigationMenuPrimitive.Root\r\n data-slot=\"navigation-menu\"\r\n className={cn(\r\n \"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <NavigationMenuPositioner align={align} />\r\n </NavigationMenuPrimitive.Root>\r\n )\r\n}\r\n\r\nfunction NavigationMenuList({\r\n className,\r\n ...props\r\n}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.List>) {\r\n return (\r\n <NavigationMenuPrimitive.List\r\n data-slot=\"navigation-menu-list\"\r\n className={cn(\r\n \"group flex flex-1 list-none items-center justify-center gap-0\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction NavigationMenuItem({\r\n className,\r\n ...props\r\n}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Item>) {\r\n return (\r\n <NavigationMenuPrimitive.Item\r\n data-slot=\"navigation-menu-item\"\r\n className={cn(\"relative\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nconst navigationMenuTriggerStyle = cva(\r\n \"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted data-open:bg-muted/50 data-open:hover:bg-muted data-open:focus:bg-muted\"\r\n)\r\n\r\nfunction NavigationMenuTrigger({\r\n className,\r\n children,\r\n ...props\r\n}: NavigationMenuPrimitive.Trigger.Props) {\r\n return (\r\n <NavigationMenuPrimitive.Trigger\r\n data-slot=\"navigation-menu-trigger\"\r\n className={cn(navigationMenuTriggerStyle(), \"group\", className)}\r\n {...props}\r\n >\r\n {children}{\" \"}\r\n <ChevronDownIcon className=\"relative top-px ml-1 size-3 transition duration-300 group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180\" aria-hidden=\"true\" />\r\n </NavigationMenuPrimitive.Trigger>\r\n )\r\n}\r\n\r\nfunction NavigationMenuContent({\r\n className,\r\n ...props\r\n}: NavigationMenuPrimitive.Content.Props) {\r\n return (\r\n <NavigationMenuPrimitive.Content\r\n data-slot=\"navigation-menu-content\"\r\n className={cn(\r\n \"data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] h-full w-auto p-2 pr-2.5 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction NavigationMenuPositioner({\r\n className,\r\n side = \"bottom\",\r\n sideOffset = 8,\r\n align = \"start\",\r\n alignOffset = 0,\r\n ...props\r\n}: NavigationMenuPrimitive.Positioner.Props) {\r\n return (\r\n <NavigationMenuPrimitive.Portal>\r\n <NavigationMenuPrimitive.Positioner\r\n side={side}\r\n sideOffset={sideOffset}\r\n align={align}\r\n alignOffset={alignOffset}\r\n className={cn(\r\n \"isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <NavigationMenuPrimitive.Popup className=\"data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0\">\r\n <NavigationMenuPrimitive.Viewport className=\"relative size-full overflow-hidden\" />\r\n </NavigationMenuPrimitive.Popup>\r\n </NavigationMenuPrimitive.Positioner>\r\n </NavigationMenuPrimitive.Portal>\r\n )\r\n}\r\n\r\nfunction NavigationMenuLink({\r\n className,\r\n ...props\r\n}: NavigationMenuPrimitive.Link.Props) {\r\n return (\r\n <NavigationMenuPrimitive.Link\r\n data-slot=\"navigation-menu-link\"\r\n className={cn(\r\n \"flex items-center gap-1.5 rounded-md p-2 text-sm transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 in-data-[slot=navigation-menu-content]:rounded-sm data-[active=true]:bg-muted/50 data-[active=true]:hover:bg-muted data-[active=true]:focus:bg-muted [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction NavigationMenuIndicator({\r\n className,\r\n ...props\r\n}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Icon>) {\r\n return (\r\n <NavigationMenuPrimitive.Icon\r\n data-slot=\"navigation-menu-indicator\"\r\n className={cn(\r\n \"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <div className=\"relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md\" />\r\n </NavigationMenuPrimitive.Icon>\r\n )\r\n}\r\n\r\nexport {\r\n NavigationMenu,\r\n NavigationMenuContent,\r\n NavigationMenuIndicator,\r\n NavigationMenuItem,\r\n NavigationMenuLink,\r\n NavigationMenuList,\r\n NavigationMenuTrigger,\r\n navigationMenuTriggerStyle,\r\n NavigationMenuPositioner,\r\n}\r\n",
|
|
1318
|
+
"src/components/ui/pagination.tsx": "import * as React from \"react\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button } from \"#/components/ui/button.tsx\"\r\nimport { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\"\r\n\r\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\r\n return (\r\n <nav\r\n role=\"navigation\"\r\n aria-label=\"pagination\"\r\n data-slot=\"pagination\"\r\n className={cn(\"mx-auto flex w-full justify-center\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction PaginationContent({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"ul\">) {\r\n return (\r\n <ul\r\n data-slot=\"pagination-content\"\r\n className={cn(\"flex items-center gap-1\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\r\n return <li data-slot=\"pagination-item\" {...props} />\r\n}\r\n\r\ntype PaginationLinkProps = {\r\n isActive?: boolean\r\n} & Pick<React.ComponentProps<typeof Button>, \"size\"> &\r\n React.ComponentProps<\"a\">\r\n\r\nfunction PaginationLink({\r\n className,\r\n isActive,\r\n size = \"icon\",\r\n ...props\r\n}: PaginationLinkProps) {\r\n return (\r\n <Button\r\n variant={isActive ? \"outline\" : \"ghost\"}\r\n size={size}\r\n className={cn(className)}\r\n nativeButton={false}\r\n render={\r\n <a\r\n aria-current={isActive ? \"page\" : undefined}\r\n data-slot=\"pagination-link\"\r\n data-active={isActive}\r\n {...props}\r\n />\r\n }\r\n />\r\n )\r\n}\r\n\r\nfunction PaginationPrevious({\r\n className,\r\n text = \"Previous\",\r\n ...props\r\n}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {\r\n return (\r\n <PaginationLink\r\n aria-label=\"Go to previous page\"\r\n size=\"default\"\r\n className={cn(\"pl-2!\", className)}\r\n {...props}\r\n >\r\n <ChevronLeftIcon data-icon=\"inline-start\" />\r\n <span className=\"hidden sm:block\">{text}</span>\r\n </PaginationLink>\r\n )\r\n}\r\n\r\nfunction PaginationNext({\r\n className,\r\n text = \"Next\",\r\n ...props\r\n}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {\r\n return (\r\n <PaginationLink\r\n aria-label=\"Go to next page\"\r\n size=\"default\"\r\n className={cn(\"pr-2!\", className)}\r\n {...props}\r\n >\r\n <span className=\"hidden sm:block\">{text}</span>\r\n <ChevronRightIcon data-icon=\"inline-end\" />\r\n </PaginationLink>\r\n )\r\n}\r\n\r\nfunction PaginationEllipsis({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"span\">) {\r\n return (\r\n <span\r\n aria-hidden\r\n data-slot=\"pagination-ellipsis\"\r\n className={cn(\r\n \"flex size-9 items-center justify-center [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <MoreHorizontalIcon\r\n />\r\n <span className=\"sr-only\">More pages</span>\r\n </span>\r\n )\r\n}\r\n\r\nexport {\r\n Pagination,\r\n PaginationContent,\r\n PaginationEllipsis,\r\n PaginationItem,\r\n PaginationLink,\r\n PaginationNext,\r\n PaginationPrevious,\r\n}\r\n",
|
|
1319
|
+
"src/components/ui/popover.tsx": "import * as React from \"react\"\r\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Popover({ ...props }: PopoverPrimitive.Root.Props) {\r\n return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />\r\n}\r\n\r\nfunction PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {\r\n return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />\r\n}\r\n\r\nfunction PopoverContent({\r\n className,\r\n align = \"center\",\r\n alignOffset = 0,\r\n side = \"bottom\",\r\n sideOffset = 4,\r\n ...props\r\n}: PopoverPrimitive.Popup.Props &\r\n Pick<\r\n PopoverPrimitive.Positioner.Props,\r\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\r\n >) {\r\n return (\r\n <PopoverPrimitive.Portal>\r\n <PopoverPrimitive.Positioner\r\n align={align}\r\n alignOffset={alignOffset}\r\n side={side}\r\n sideOffset={sideOffset}\r\n className=\"isolate z-50\"\r\n >\r\n <PopoverPrimitive.Popup\r\n data-slot=\"popover-content\"\r\n className={cn(\r\n \"z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-md bg-popover p-4 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n </PopoverPrimitive.Positioner>\r\n </PopoverPrimitive.Portal>\r\n )\r\n}\r\n\r\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"popover-header\"\r\n className={cn(\"flex flex-col gap-1 text-sm\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {\r\n return (\r\n <PopoverPrimitive.Title\r\n data-slot=\"popover-title\"\r\n className={cn(\"font-medium\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction PopoverDescription({\r\n className,\r\n ...props\r\n}: PopoverPrimitive.Description.Props) {\r\n return (\r\n <PopoverPrimitive.Description\r\n data-slot=\"popover-description\"\r\n className={cn(\"text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Popover,\r\n PopoverContent,\r\n PopoverDescription,\r\n PopoverHeader,\r\n PopoverTitle,\r\n PopoverTrigger,\r\n}\r\n",
|
|
1320
|
+
"src/components/ui/radio-group.tsx": "\"use client\"\r\n\r\nimport { Radio as RadioPrimitive } from \"@base-ui/react/radio\"\r\nimport { RadioGroup as RadioGroupPrimitive } from \"@base-ui/react/radio-group\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {\r\n return (\r\n <RadioGroupPrimitive\r\n data-slot=\"radio-group\"\r\n className={cn(\"grid w-full gap-3\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {\r\n return (\r\n <RadioPrimitive.Root\r\n data-slot=\"radio-group-item\"\r\n className={cn(\r\n \"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <RadioPrimitive.Indicator\r\n data-slot=\"radio-group-indicator\"\r\n className=\"flex size-4 items-center justify-center\"\r\n >\r\n <span className=\"absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground\" />\r\n </RadioPrimitive.Indicator>\r\n </RadioPrimitive.Root>\r\n )\r\n}\r\n\r\nexport { RadioGroup, RadioGroupItem }\r\n",
|
|
1321
|
+
"src/components/ui/select.tsx": "import * as React from \"react\"\r\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from \"lucide-react\"\r\n\r\nconst Select = SelectPrimitive.Root\r\n\r\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\r\n return (\r\n <SelectPrimitive.Group\r\n data-slot=\"select-group\"\r\n className={cn(\"scroll-my-1 p-1\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\r\n return (\r\n <SelectPrimitive.Value\r\n data-slot=\"select-value\"\r\n className={cn(\"flex flex-1 text-left\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SelectTrigger({\r\n className,\r\n size = \"default\",\r\n children,\r\n ...props\r\n}: SelectPrimitive.Trigger.Props & {\r\n size?: \"sm\" | \"default\"\r\n}) {\r\n return (\r\n <SelectPrimitive.Trigger\r\n data-slot=\"select-trigger\"\r\n data-size={size}\r\n className={cn(\r\n \"flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <SelectPrimitive.Icon\r\n render={\r\n <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\r\n }\r\n />\r\n </SelectPrimitive.Trigger>\r\n )\r\n}\r\n\r\nfunction SelectContent({\r\n className,\r\n children,\r\n side = \"bottom\",\r\n sideOffset = 4,\r\n align = \"center\",\r\n alignOffset = 0,\r\n alignItemWithTrigger = true,\r\n ...props\r\n}: SelectPrimitive.Popup.Props &\r\n Pick<\r\n SelectPrimitive.Positioner.Props,\r\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\" | \"alignItemWithTrigger\"\r\n >) {\r\n return (\r\n <SelectPrimitive.Portal>\r\n <SelectPrimitive.Positioner\r\n side={side}\r\n sideOffset={sideOffset}\r\n align={align}\r\n alignOffset={alignOffset}\r\n alignItemWithTrigger={alignItemWithTrigger}\r\n className=\"isolate z-50\"\r\n >\r\n <SelectPrimitive.Popup\r\n data-slot=\"select-content\"\r\n data-align-trigger={alignItemWithTrigger}\r\n className={cn(\"relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\", className )}\r\n {...props}\r\n >\r\n <SelectScrollUpButton />\r\n <SelectPrimitive.List>{children}</SelectPrimitive.List>\r\n <SelectScrollDownButton />\r\n </SelectPrimitive.Popup>\r\n </SelectPrimitive.Positioner>\r\n </SelectPrimitive.Portal>\r\n )\r\n}\r\n\r\nfunction SelectLabel({\r\n className,\r\n ...props\r\n}: SelectPrimitive.GroupLabel.Props) {\r\n return (\r\n <SelectPrimitive.GroupLabel\r\n data-slot=\"select-label\"\r\n className={cn(\"px-2 py-1.5 text-xs text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SelectItem({\r\n className,\r\n children,\r\n ...props\r\n}: SelectPrimitive.Item.Props) {\r\n return (\r\n <SelectPrimitive.Item\r\n data-slot=\"select-item\"\r\n className={cn(\r\n \"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\r\n {children}\r\n </SelectPrimitive.ItemText>\r\n <SelectPrimitive.ItemIndicator\r\n render={\r\n <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\r\n }\r\n >\r\n <CheckIcon className=\"pointer-events-none\" />\r\n </SelectPrimitive.ItemIndicator>\r\n </SelectPrimitive.Item>\r\n )\r\n}\r\n\r\nfunction SelectSeparator({\r\n className,\r\n ...props\r\n}: SelectPrimitive.Separator.Props) {\r\n return (\r\n <SelectPrimitive.Separator\r\n data-slot=\"select-separator\"\r\n className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SelectScrollUpButton({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\r\n return (\r\n <SelectPrimitive.ScrollUpArrow\r\n data-slot=\"select-scroll-up-button\"\r\n className={cn(\r\n \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <ChevronUpIcon\r\n />\r\n </SelectPrimitive.ScrollUpArrow>\r\n )\r\n}\r\n\r\nfunction SelectScrollDownButton({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\r\n return (\r\n <SelectPrimitive.ScrollDownArrow\r\n data-slot=\"select-scroll-down-button\"\r\n className={cn(\r\n \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <ChevronDownIcon\r\n />\r\n </SelectPrimitive.ScrollDownArrow>\r\n )\r\n}\r\n\r\nexport {\r\n Select,\r\n SelectContent,\r\n SelectGroup,\r\n SelectItem,\r\n SelectLabel,\r\n SelectScrollDownButton,\r\n SelectScrollUpButton,\r\n SelectSeparator,\r\n SelectTrigger,\r\n SelectValue,\r\n}\r\n",
|
|
1322
|
+
"src/components/ui/separator.tsx": "\"use client\"\r\n\r\nimport { Separator as SeparatorPrimitive } from \"@base-ui/react/separator\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Separator({\r\n className,\r\n orientation = \"horizontal\",\r\n ...props\r\n}: SeparatorPrimitive.Props) {\r\n return (\r\n <SeparatorPrimitive\r\n data-slot=\"separator\"\r\n orientation={orientation}\r\n className={cn(\r\n \"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Separator }\r\n",
|
|
1323
|
+
"src/components/ui/sheet.tsx": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { Dialog as SheetPrimitive } from \"@base-ui/react/dialog\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button } from \"#/components/ui/button.tsx\"\r\nimport { XIcon } from \"lucide-react\"\r\n\r\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\r\n return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />\r\n}\r\n\r\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\r\n return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />\r\n}\r\n\r\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\r\n return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />\r\n}\r\n\r\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\r\n return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />\r\n}\r\n\r\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\r\n return (\r\n <SheetPrimitive.Backdrop\r\n data-slot=\"sheet-overlay\"\r\n className={cn(\r\n \"fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SheetContent({\r\n className,\r\n children,\r\n side = \"right\",\r\n showCloseButton = true,\r\n ...props\r\n}: SheetPrimitive.Popup.Props & {\r\n side?: \"top\" | \"right\" | \"bottom\" | \"left\"\r\n showCloseButton?: boolean\r\n}) {\r\n return (\r\n <SheetPortal>\r\n <SheetOverlay />\r\n <SheetPrimitive.Popup\r\n data-slot=\"sheet-content\"\r\n data-side={side}\r\n className={cn(\r\n \"fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n {showCloseButton && (\r\n <SheetPrimitive.Close\r\n data-slot=\"sheet-close\"\r\n render={\r\n <Button\r\n variant=\"ghost\"\r\n className=\"absolute top-4 right-4\"\r\n size=\"icon-sm\"\r\n />\r\n }\r\n >\r\n <XIcon\r\n />\r\n <span className=\"sr-only\">Close</span>\r\n </SheetPrimitive.Close>\r\n )}\r\n </SheetPrimitive.Popup>\r\n </SheetPortal>\r\n )\r\n}\r\n\r\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sheet-header\"\r\n className={cn(\"flex flex-col gap-1.5 p-4\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sheet-footer\"\r\n className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\r\n return (\r\n <SheetPrimitive.Title\r\n data-slot=\"sheet-title\"\r\n className={cn(\"font-heading font-medium text-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SheetDescription({\r\n className,\r\n ...props\r\n}: SheetPrimitive.Description.Props) {\r\n return (\r\n <SheetPrimitive.Description\r\n data-slot=\"sheet-description\"\r\n className={cn(\"text-sm text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Sheet,\r\n SheetTrigger,\r\n SheetClose,\r\n SheetContent,\r\n SheetHeader,\r\n SheetFooter,\r\n SheetTitle,\r\n SheetDescription,\r\n}\r\n",
|
|
1324
|
+
"src/components/ui/sidebar.tsx": "import * as React from \"react\"\r\nimport { mergeProps } from \"@base-ui/react/merge-props\"\r\nimport { useRender } from \"@base-ui/react/use-render\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { useIsMobile } from \"#/hooks/use-mobile.ts\"\r\nimport { cn } from \"#/lib/utils.ts\"\r\nimport { Button } from \"#/components/ui/button.tsx\"\r\nimport { Input } from \"#/components/ui/input.tsx\"\r\nimport { Separator } from \"#/components/ui/separator.tsx\"\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetDescription,\r\n SheetHeader,\r\n SheetTitle,\r\n} from \"#/components/ui/sheet.tsx\"\r\nimport { Skeleton } from \"#/components/ui/skeleton.tsx\"\r\nimport {\r\n Tooltip,\r\n TooltipContent,\r\n TooltipTrigger,\r\n} from \"#/components/ui/tooltip.tsx\"\r\nimport { PanelLeftIcon } from \"lucide-react\"\r\n\r\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\"\r\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\r\nconst SIDEBAR_WIDTH = \"16rem\"\r\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\r\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\r\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\"\r\n\r\ntype SidebarContextProps = {\r\n state: \"expanded\" | \"collapsed\"\r\n open: boolean\r\n setOpen: (open: boolean) => void\r\n openMobile: boolean\r\n setOpenMobile: (open: boolean) => void\r\n isMobile: boolean\r\n toggleSidebar: () => void\r\n}\r\n\r\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null)\r\n\r\nfunction useSidebar() {\r\n const context = React.useContext(SidebarContext)\r\n if (!context) {\r\n throw new Error(\"useSidebar must be used within a SidebarProvider.\")\r\n }\r\n\r\n return context\r\n}\r\n\r\nfunction SidebarProvider({\r\n defaultOpen = true,\r\n open: openProp,\r\n onOpenChange: setOpenProp,\r\n className,\r\n style,\r\n children,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n defaultOpen?: boolean\r\n open?: boolean\r\n onOpenChange?: (open: boolean) => void\r\n}) {\r\n const isMobile = useIsMobile()\r\n const [openMobile, setOpenMobile] = React.useState(false)\r\n\r\n // This is the internal state of the sidebar.\r\n // We use openProp and setOpenProp for control from outside the component.\r\n const [_open, _setOpen] = React.useState(defaultOpen)\r\n const open = openProp ?? _open\r\n const setOpen = React.useCallback(\r\n (value: boolean | ((value: boolean) => boolean)) => {\r\n const openState = typeof value === \"function\" ? value(open) : value\r\n if (setOpenProp) {\r\n setOpenProp(openState)\r\n } else {\r\n _setOpen(openState)\r\n }\r\n\r\n // This sets the cookie to keep the sidebar state.\r\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\r\n },\r\n [setOpenProp, open]\r\n )\r\n\r\n // Helper to toggle the sidebar.\r\n const toggleSidebar = React.useCallback(() => {\r\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)\r\n }, [isMobile, setOpen, setOpenMobile])\r\n\r\n // Adds a keyboard shortcut to toggle the sidebar.\r\n React.useEffect(() => {\r\n const handleKeyDown = (event: KeyboardEvent) => {\r\n if (\r\n event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\r\n (event.metaKey || event.ctrlKey)\r\n ) {\r\n event.preventDefault()\r\n toggleSidebar()\r\n }\r\n }\r\n\r\n window.addEventListener(\"keydown\", handleKeyDown)\r\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\r\n }, [toggleSidebar])\r\n\r\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\r\n // This makes it easier to style the sidebar with Tailwind classes.\r\n const state = open ? \"expanded\" : \"collapsed\"\r\n\r\n const contextValue = React.useMemo<SidebarContextProps>(\r\n () => ({\r\n state,\r\n open,\r\n setOpen,\r\n isMobile,\r\n openMobile,\r\n setOpenMobile,\r\n toggleSidebar,\r\n }),\r\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\r\n )\r\n\r\n return (\r\n <SidebarContext.Provider value={contextValue}>\r\n <div\r\n data-slot=\"sidebar-wrapper\"\r\n style={\r\n {\r\n \"--sidebar-width\": SIDEBAR_WIDTH,\r\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\r\n ...style,\r\n } as React.CSSProperties\r\n }\r\n className={cn(\r\n \"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n </div>\r\n </SidebarContext.Provider>\r\n )\r\n}\r\n\r\nfunction Sidebar({\r\n side = \"left\",\r\n variant = \"sidebar\",\r\n collapsible = \"offcanvas\",\r\n className,\r\n children,\r\n dir,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n side?: \"left\" | \"right\"\r\n variant?: \"sidebar\" | \"floating\" | \"inset\"\r\n collapsible?: \"offcanvas\" | \"icon\" | \"none\"\r\n}) {\r\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar()\r\n\r\n if (collapsible === \"none\") {\r\n return (\r\n <div\r\n data-slot=\"sidebar\"\r\n className={cn(\r\n \"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n </div>\r\n )\r\n }\r\n\r\n if (isMobile) {\r\n return (\r\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\r\n <SheetContent\r\n dir={dir}\r\n data-sidebar=\"sidebar\"\r\n data-slot=\"sidebar\"\r\n data-mobile=\"true\"\r\n className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\r\n style={\r\n {\r\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\r\n } as React.CSSProperties\r\n }\r\n side={side}\r\n >\r\n <SheetHeader className=\"sr-only\">\r\n <SheetTitle>Sidebar</SheetTitle>\r\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\r\n </SheetHeader>\r\n <div className=\"flex h-full w-full flex-col\">{children}</div>\r\n </SheetContent>\r\n </Sheet>\r\n )\r\n }\r\n\r\n return (\r\n <div\r\n className=\"group peer hidden text-sidebar-foreground md:block\"\r\n data-state={state}\r\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\r\n data-variant={variant}\r\n data-side={side}\r\n data-slot=\"sidebar\"\r\n >\r\n {/* This is what handles the sidebar gap on desktop */}\r\n <div\r\n data-slot=\"sidebar-gap\"\r\n className={cn(\r\n \"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\r\n \"group-data-[collapsible=offcanvas]:w-0\",\r\n \"group-data-[side=right]:rotate-180\",\r\n variant === \"floating\" || variant === \"inset\"\r\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]\"\r\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\"\r\n )}\r\n />\r\n <div\r\n data-slot=\"sidebar-container\"\r\n data-side={side}\r\n className={cn(\r\n \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex\",\r\n // Adjust the padding for floating and inset variants.\r\n variant === \"floating\" || variant === \"inset\"\r\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]\"\r\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n <div\r\n data-sidebar=\"sidebar\"\r\n data-slot=\"sidebar-inner\"\r\n className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\r\n >\r\n {children}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nfunction SidebarTrigger({\r\n className,\r\n onClick,\r\n ...props\r\n}: React.ComponentProps<typeof Button>) {\r\n const { toggleSidebar } = useSidebar()\r\n\r\n return (\r\n <Button\r\n data-sidebar=\"trigger\"\r\n data-slot=\"sidebar-trigger\"\r\n variant=\"ghost\"\r\n size=\"icon-sm\"\r\n className={cn(className)}\r\n onClick={(event) => {\r\n onClick?.(event)\r\n toggleSidebar()\r\n }}\r\n {...props}\r\n >\r\n <PanelLeftIcon />\r\n <span className=\"sr-only\">Toggle Sidebar</span>\r\n </Button>\r\n )\r\n}\r\n\r\nfunction SidebarRail({ className, ...props }: React.ComponentProps<\"button\">) {\r\n const { toggleSidebar } = useSidebar()\r\n\r\n return (\r\n <button\r\n data-sidebar=\"rail\"\r\n data-slot=\"sidebar-rail\"\r\n aria-label=\"Toggle Sidebar\"\r\n tabIndex={-1}\r\n onClick={toggleSidebar}\r\n title=\"Toggle Sidebar\"\r\n className={cn(\r\n \"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2\",\r\n \"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize\",\r\n \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\r\n \"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar\",\r\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\r\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\r\n return (\r\n <main\r\n data-slot=\"sidebar-inset\"\r\n className={cn(\r\n \"relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarInput({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof Input>) {\r\n return (\r\n <Input\r\n data-slot=\"sidebar-input\"\r\n data-sidebar=\"input\"\r\n className={cn(\"h-8 w-full bg-background shadow-none\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sidebar-header\"\r\n data-sidebar=\"header\"\r\n className={cn(\"flex flex-col gap-2 p-2\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sidebar-footer\"\r\n data-sidebar=\"footer\"\r\n className={cn(\"flex flex-col gap-2 p-2\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarSeparator({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof Separator>) {\r\n return (\r\n <Separator\r\n data-slot=\"sidebar-separator\"\r\n data-sidebar=\"separator\"\r\n className={cn(\"mx-2 w-auto bg-sidebar-border\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sidebar-content\"\r\n data-sidebar=\"content\"\r\n className={cn(\r\n \"no-scrollbar flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sidebar-group\"\r\n data-sidebar=\"group\"\r\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarGroupLabel({\r\n className,\r\n render,\r\n ...props\r\n}: useRender.ComponentProps<\"div\"> & React.ComponentProps<\"div\">) {\r\n return useRender({\r\n defaultTagName: \"div\",\r\n props: mergeProps<\"div\">(\r\n {\r\n className: cn(\r\n \"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\r\n className\r\n ),\r\n },\r\n props\r\n ),\r\n render,\r\n state: {\r\n slot: \"sidebar-group-label\",\r\n sidebar: \"group-label\",\r\n },\r\n })\r\n}\r\n\r\nfunction SidebarGroupAction({\r\n className,\r\n render,\r\n ...props\r\n}: useRender.ComponentProps<\"button\"> & React.ComponentProps<\"button\">) {\r\n return useRender({\r\n defaultTagName: \"button\",\r\n props: mergeProps<\"button\">(\r\n {\r\n className: cn(\r\n \"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\r\n className\r\n ),\r\n },\r\n props\r\n ),\r\n render,\r\n state: {\r\n slot: \"sidebar-group-action\",\r\n sidebar: \"group-action\",\r\n },\r\n })\r\n}\r\n\r\nfunction SidebarGroupContent({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sidebar-group-content\"\r\n data-sidebar=\"group-content\"\r\n className={cn(\"w-full text-sm\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\r\n return (\r\n <ul\r\n data-slot=\"sidebar-menu\"\r\n data-sidebar=\"menu\"\r\n className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\r\n return (\r\n <li\r\n data-slot=\"sidebar-menu-item\"\r\n data-sidebar=\"menu-item\"\r\n className={cn(\"group/menu-item relative\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nconst sidebarMenuButtonVariants = cva(\r\n \"peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate\",\r\n {\r\n variants: {\r\n variant: {\r\n default: \"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\",\r\n outline:\r\n \"bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]\",\r\n },\r\n size: {\r\n default: \"h-8 text-sm\",\r\n sm: \"h-7 text-xs\",\r\n lg: \"h-12 text-sm group-data-[collapsible=icon]:p-0!\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"default\",\r\n size: \"default\",\r\n },\r\n }\r\n)\r\n\r\nfunction SidebarMenuButton({\r\n render,\r\n isActive = false,\r\n variant = \"default\",\r\n size = \"default\",\r\n tooltip,\r\n className,\r\n ...props\r\n}: useRender.ComponentProps<\"button\"> &\r\n React.ComponentProps<\"button\"> & {\r\n isActive?: boolean\r\n tooltip?: string | React.ComponentProps<typeof TooltipContent>\r\n } & VariantProps<typeof sidebarMenuButtonVariants>) {\r\n const { isMobile, state } = useSidebar()\r\n const comp = useRender({\r\n defaultTagName: \"button\",\r\n props: mergeProps<\"button\">(\r\n {\r\n className: cn(sidebarMenuButtonVariants({ variant, size }), className),\r\n },\r\n props\r\n ),\r\n render: !tooltip ? render : <TooltipTrigger render={render} />,\r\n state: {\r\n slot: \"sidebar-menu-button\",\r\n sidebar: \"menu-button\",\r\n size,\r\n active: isActive,\r\n },\r\n })\r\n\r\n if (!tooltip) {\r\n return comp\r\n }\r\n\r\n if (typeof tooltip === \"string\") {\r\n tooltip = {\r\n children: tooltip,\r\n }\r\n }\r\n\r\n return (\r\n <Tooltip>\r\n {comp}\r\n <TooltipContent\r\n side=\"right\"\r\n align=\"center\"\r\n hidden={state !== \"collapsed\" || isMobile}\r\n {...tooltip}\r\n />\r\n </Tooltip>\r\n )\r\n}\r\n\r\nfunction SidebarMenuAction({\r\n className,\r\n render,\r\n showOnHover = false,\r\n ...props\r\n}: useRender.ComponentProps<\"button\"> &\r\n React.ComponentProps<\"button\"> & {\r\n showOnHover?: boolean\r\n }) {\r\n return useRender({\r\n defaultTagName: \"button\",\r\n props: mergeProps<\"button\">(\r\n {\r\n className: cn(\r\n \"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0\",\r\n showOnHover &&\r\n \"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0\",\r\n className\r\n ),\r\n },\r\n props\r\n ),\r\n render,\r\n state: {\r\n slot: \"sidebar-menu-action\",\r\n sidebar: \"menu-action\",\r\n },\r\n })\r\n}\r\n\r\nfunction SidebarMenuBadge({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"sidebar-menu-badge\"\r\n data-sidebar=\"menu-badge\"\r\n className={cn(\r\n \"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarMenuSkeleton({\r\n className,\r\n showIcon = false,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n showIcon?: boolean\r\n}) {\r\n // Random width between 50 to 90%.\r\n const [width] = React.useState(() => {\r\n return `${Math.floor(Math.random() * 40) + 50}%`\r\n })\r\n\r\n return (\r\n <div\r\n data-slot=\"sidebar-menu-skeleton\"\r\n data-sidebar=\"menu-skeleton\"\r\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\r\n {...props}\r\n >\r\n {showIcon && (\r\n <Skeleton\r\n className=\"size-4 rounded-md\"\r\n data-sidebar=\"menu-skeleton-icon\"\r\n />\r\n )}\r\n <Skeleton\r\n className=\"h-4 max-w-(--skeleton-width) flex-1\"\r\n data-sidebar=\"menu-skeleton-text\"\r\n style={\r\n {\r\n \"--skeleton-width\": width,\r\n } as React.CSSProperties\r\n }\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<\"ul\">) {\r\n return (\r\n <ul\r\n data-slot=\"sidebar-menu-sub\"\r\n data-sidebar=\"menu-sub\"\r\n className={cn(\r\n \"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarMenuSubItem({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"li\">) {\r\n return (\r\n <li\r\n data-slot=\"sidebar-menu-sub-item\"\r\n data-sidebar=\"menu-sub-item\"\r\n className={cn(\"group/menu-sub-item relative\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction SidebarMenuSubButton({\r\n render,\r\n size = \"md\",\r\n isActive = false,\r\n className,\r\n ...props\r\n}: useRender.ComponentProps<\"a\"> &\r\n React.ComponentProps<\"a\"> & {\r\n size?: \"sm\" | \"md\"\r\n isActive?: boolean\r\n }) {\r\n return useRender({\r\n defaultTagName: \"a\",\r\n props: mergeProps<\"a\">(\r\n {\r\n className: cn(\r\n \"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground\",\r\n className\r\n ),\r\n },\r\n props\r\n ),\r\n render,\r\n state: {\r\n slot: \"sidebar-menu-sub-button\",\r\n sidebar: \"menu-sub-button\",\r\n size,\r\n active: isActive,\r\n },\r\n })\r\n}\r\n\r\nexport {\r\n Sidebar,\r\n SidebarContent,\r\n SidebarFooter,\r\n SidebarGroup,\r\n SidebarGroupAction,\r\n SidebarGroupContent,\r\n SidebarGroupLabel,\r\n SidebarHeader,\r\n SidebarInput,\r\n SidebarInset,\r\n SidebarMenu,\r\n SidebarMenuAction,\r\n SidebarMenuBadge,\r\n SidebarMenuButton,\r\n SidebarMenuItem,\r\n SidebarMenuSkeleton,\r\n SidebarMenuSub,\r\n SidebarMenuSubButton,\r\n SidebarMenuSubItem,\r\n SidebarProvider,\r\n SidebarRail,\r\n SidebarSeparator,\r\n SidebarTrigger,\r\n useSidebar,\r\n}\r\n",
|
|
1325
|
+
"src/components/ui/skeleton.tsx": "import { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\r\n return (\r\n <div\r\n data-slot=\"skeleton\"\r\n className={cn(\"animate-pulse rounded-md bg-muted\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Skeleton }\r\n",
|
|
1326
|
+
"src/components/ui/sonner.tsx": "import { useTheme } from \"next-themes\"\r\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\r\nimport { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from \"lucide-react\"\r\n\r\nconst Toaster = ({ ...props }: ToasterProps) => {\r\n const { theme = \"system\" } = useTheme()\r\n\r\n return (\r\n <Sonner\r\n theme={theme as ToasterProps[\"theme\"]}\r\n className=\"toaster group\"\r\n icons={{\r\n success: (\r\n <CircleCheckIcon className=\"size-4\" />\r\n ),\r\n info: (\r\n <InfoIcon className=\"size-4\" />\r\n ),\r\n warning: (\r\n <TriangleAlertIcon className=\"size-4\" />\r\n ),\r\n error: (\r\n <OctagonXIcon className=\"size-4\" />\r\n ),\r\n loading: (\r\n <Loader2Icon className=\"size-4 animate-spin\" />\r\n ),\r\n }}\r\n style={\r\n {\r\n \"--normal-bg\": \"var(--popover)\",\r\n \"--normal-text\": \"var(--popover-foreground)\",\r\n \"--normal-border\": \"var(--border)\",\r\n \"--border-radius\": \"var(--radius)\",\r\n } as React.CSSProperties\r\n }\r\n toastOptions={{\r\n classNames: {\r\n toast: \"cn-toast\",\r\n },\r\n }}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Toaster }\r\n",
|
|
1327
|
+
"src/components/ui/table.tsx": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Table({ className, ...props }: React.ComponentProps<\"table\">) {\r\n return (\r\n <div\r\n data-slot=\"table-container\"\r\n className=\"relative w-full overflow-x-auto\"\r\n >\r\n <table\r\n data-slot=\"table\"\r\n className={cn(\"w-full caption-bottom text-sm\", className)}\r\n {...props}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nfunction TableHeader({ className, ...props }: React.ComponentProps<\"thead\">) {\r\n return (\r\n <thead\r\n data-slot=\"table-header\"\r\n className={cn(\"[&_tr]:border-b\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TableBody({ className, ...props }: React.ComponentProps<\"tbody\">) {\r\n return (\r\n <tbody\r\n data-slot=\"table-body\"\r\n className={cn(\"[&_tr:last-child]:border-0\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TableFooter({ className, ...props }: React.ComponentProps<\"tfoot\">) {\r\n return (\r\n <tfoot\r\n data-slot=\"table-footer\"\r\n className={cn(\r\n \"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TableRow({ className, ...props }: React.ComponentProps<\"tr\">) {\r\n return (\r\n <tr\r\n data-slot=\"table-row\"\r\n className={cn(\r\n \"border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TableHead({ className, ...props }: React.ComponentProps<\"th\">) {\r\n return (\r\n <th\r\n data-slot=\"table-head\"\r\n className={cn(\r\n \"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TableCell({ className, ...props }: React.ComponentProps<\"td\">) {\r\n return (\r\n <td\r\n data-slot=\"table-cell\"\r\n className={cn(\r\n \"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TableCaption({\r\n className,\r\n ...props\r\n}: React.ComponentProps<\"caption\">) {\r\n return (\r\n <caption\r\n data-slot=\"table-caption\"\r\n className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport {\r\n Table,\r\n TableHeader,\r\n TableBody,\r\n TableFooter,\r\n TableHead,\r\n TableRow,\r\n TableCell,\r\n TableCaption,\r\n}\r\n",
|
|
1328
|
+
"src/components/ui/tabs.tsx": "import { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Tabs({\r\n className,\r\n orientation = \"horizontal\",\r\n ...props\r\n}: TabsPrimitive.Root.Props) {\r\n return (\r\n <TabsPrimitive.Root\r\n data-slot=\"tabs\"\r\n data-orientation={orientation}\r\n className={cn(\r\n \"group/tabs flex gap-2 data-horizontal:flex-col\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nconst tabsListVariants = cva(\r\n \"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none\",\r\n {\r\n variants: {\r\n variant: {\r\n default: \"bg-muted\",\r\n line: \"gap-1 bg-transparent\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"default\",\r\n },\r\n }\r\n)\r\n\r\nfunction TabsList({\r\n className,\r\n variant = \"default\",\r\n ...props\r\n}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {\r\n return (\r\n <TabsPrimitive.List\r\n data-slot=\"tabs-list\"\r\n data-variant={variant}\r\n className={cn(tabsListVariants({ variant }), className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\r\n return (\r\n <TabsPrimitive.Tab\r\n data-slot=\"tabs-trigger\"\r\n className={cn(\r\n \"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n \"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent\",\r\n \"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground\",\r\n \"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\r\n return (\r\n <TabsPrimitive.Panel\r\n data-slot=\"tabs-content\"\r\n className={cn(\"flex-1 text-sm outline-none\", className)}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }\r\n",
|
|
1329
|
+
"src/components/ui/textarea.tsx": "import * as React from \"react\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\r\n return (\r\n <textarea\r\n data-slot=\"textarea\"\r\n className={cn(\r\n \"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nexport { Textarea }\r\n",
|
|
1330
|
+
"src/components/ui/tooltip.tsx": "\"use client\"\r\n\r\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\"\r\n\r\nimport { cn } from \"#/lib/utils.ts\"\r\n\r\nfunction TooltipProvider({\r\n delay = 0,\r\n ...props\r\n}: TooltipPrimitive.Provider.Props) {\r\n return (\r\n <TooltipPrimitive.Provider\r\n data-slot=\"tooltip-provider\"\r\n delay={delay}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\r\n return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\r\n}\r\n\r\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\r\n return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\r\n}\r\n\r\nfunction TooltipContent({\r\n className,\r\n side = \"top\",\r\n sideOffset = 4,\r\n align = \"center\",\r\n alignOffset = 0,\r\n children,\r\n ...props\r\n}: TooltipPrimitive.Popup.Props &\r\n Pick<\r\n TooltipPrimitive.Positioner.Props,\r\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\r\n >) {\r\n return (\r\n <TooltipPrimitive.Portal>\r\n <TooltipPrimitive.Positioner\r\n align={align}\r\n alignOffset={alignOffset}\r\n side={side}\r\n sideOffset={sideOffset}\r\n className=\"isolate z-50\"\r\n >\r\n <TooltipPrimitive.Popup\r\n data-slot=\"tooltip-content\"\r\n className={cn(\r\n \"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\r\n className\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <TooltipPrimitive.Arrow className=\"z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5\" />\r\n </TooltipPrimitive.Popup>\r\n </TooltipPrimitive.Positioner>\r\n </TooltipPrimitive.Portal>\r\n )\r\n}\r\n\r\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\r\n",
|
|
1331
|
+
"src/routes/(public-pages)/(auth)/route.tsx": "import { Outlet, createFileRoute, redirect } from '@tanstack/react-router';\r\nimport { AuthLayout } from '@/components/layouts';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\n/**\r\n * Client-side guard for the auth pages under `(public-pages)/(auth)`\r\n * (login, etc.). It keeps already-authenticated users out of these pages.\r\n *\r\n * This app runs as a client-side SPA (see `src/start.ts`'s `defaultSsr:\r\n * false`), so `beforeLoad` only ever runs in the browser — reading\r\n * `isAuthenticated` straight from the Zustand store (hydrated synchronously\r\n * from the `token` cookie) is sufficient; no server round trip is needed.\r\n */\r\nexport const Route = createFileRoute('/(public-pages)/(auth)')({\r\n beforeLoad: () => {\r\n if (useAuthStore.getState().isAuthenticated) {\r\n throw redirect({ to: '/dashboard' });\r\n }\r\n },\r\n\r\n component: PublicLayout,\r\n});\r\n\r\nfunction PublicLayout() {\r\n return (\r\n <AuthLayout>\r\n <Outlet />\r\n </AuthLayout>\r\n );\r\n}\r\n",
|
|
1332
|
+
"src/routes/(public-pages)/(auth)/login.tsx": "import { createFileRoute } from '@tanstack/react-router';\r\nimport { LoginScreen } from '@/components/screens/login';\r\nimport { m } from '#/paraglide/messages';\r\n\r\nexport const Route = createFileRoute('/(public-pages)/(auth)/login')({\r\n head: () => ({ meta: [{ title: m.login_page() }] }),\r\n component: LoginScreen,\r\n});\r\n",
|
|
1333
|
+
"src/components/screens/login/index.ts": "export { LoginScreen } from './login-screen';\r\n",
|
|
1334
|
+
"src/components/screens/login/login-screen.tsx": "import { m } from '#/paraglide/messages';\r\n\r\nimport { LoginForm } from './login-form';\r\n\r\nexport function LoginScreen() {\r\n return (\r\n <main className='w-full max-w-md'>\r\n <div className='rounded-xl border border-border bg-card p-8 shadow-sm'>\r\n <div className='mb-8 text-center'>\r\n <h1 className='text-2xl font-bold tracking-tight text-foreground'>\r\n {m.login_title()}\r\n </h1>\r\n <p className='text-muted-foreground mt-2 text-sm'>\r\n {m.login_intro()}\r\n </p>\r\n </div>\r\n\r\n <LoginForm />\r\n </div>\r\n </main>\r\n );\r\n}\r\n",
|
|
1335
|
+
"src/components/screens/login/login-form.tsx": "import { zodResolver } from '@hookform/resolvers/zod';\r\nimport { useForm } from 'react-hook-form';\r\n\r\nimport { useLoginMutation } from '@/apis/auth/hooks';\r\nimport { InputFieldController } from '@/components/common/controllers';\r\nimport { LoadingButton } from '@/components/ui/composed/button/loading-button';\r\nimport { setResponseFormErrors } from '@/hooks/use-response-error';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nimport { loginSchema } from './login-schema';\r\n\r\nimport type { TLoginFormValues } from './login-schema';\r\n\r\nexport function LoginForm() {\r\n const { mutate, isPending, isSuccess } = useLoginMutation();\r\n\r\n const form = useForm<TLoginFormValues>({\r\n resolver: zodResolver(loginSchema()),\r\n defaultValues: { email: '', password: '' },\r\n });\r\n\r\n // Keep the button busy through the post-success redirect.\r\n const isLoading = isPending || isSuccess;\r\n\r\n const onSubmit = (values: TLoginFormValues) =>\r\n mutate(values, {\r\n onError: error => setResponseFormErrors(form.setError, error),\r\n });\r\n\r\n return (\r\n <form\r\n noValidate\r\n autoComplete='off'\r\n onSubmit={form.handleSubmit(onSubmit)}\r\n className='flex flex-col gap-6'\r\n >\r\n <InputFieldController\r\n control={form.control}\r\n name='email'\r\n type='email'\r\n inputMode='email'\r\n autoComplete='email'\r\n label={m.login_email_label()}\r\n placeholder={m.login_email_placeholder()}\r\n />\r\n\r\n <InputFieldController\r\n control={form.control}\r\n name='password'\r\n type='password'\r\n autoComplete='current-password'\r\n label={m.login_password_label()}\r\n placeholder={m.login_password_placeholder()}\r\n />\r\n\r\n <LoadingButton\r\n type='submit'\r\n className='w-full'\r\n loading={isLoading}\r\n loadingText={m.login_submitting()}\r\n >\r\n {m.login_submit()}\r\n </LoadingButton>\r\n </form>\r\n );\r\n}\r\n",
|
|
1336
|
+
"src/components/screens/login/login-schema.ts": "import { z } from 'zod';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\n/**\r\n * Built lazily as a function so the Paraglide message getters resolve against\r\n * the active locale at validation time (ported from flowi-admin-frontend).\r\n */\r\nexport const loginSchema = () =>\r\n z.object({\r\n email: z\r\n .string()\r\n .min(1, { message: m.validation_email_required() })\r\n .email({ message: m.validation_email_invalid() }),\r\n password: z.string().min(1, { message: m.validation_password_required() }),\r\n });\r\n\r\nexport type TLoginFormValues = z.infer<ReturnType<typeof loginSchema>>;\r\n",
|
|
1337
|
+
"src/components/layouts/index.ts": "export { AuthLayout } from './auth-layout';\r\nexport { DashboardLayout } from './dashboard-layout';\r\n",
|
|
1338
|
+
"src/components/layouts/auth-layout.tsx": "import type { ReactNode } from 'react';\r\n\r\nimport { ThemeToggle } from '@/components/ui/composed/button/theme-toggle';\r\nimport { LocaleSwitcher } from '@/components/ui/composed/dropdown-menu/locale-switcher';\r\n\r\ntype AuthLayoutProps = {\r\n children: ReactNode;\r\n};\r\n\r\nexport function AuthLayout({ children }: AuthLayoutProps) {\r\n return (\r\n <div className='bg-background relative flex min-h-svh flex-col items-center justify-center px-6 py-12'>\r\n <div className='absolute top-4 inset-e-4 flex items-center gap-2'>\r\n <LocaleSwitcher />\r\n <ThemeToggle />\r\n </div>\r\n {children}\r\n </div>\r\n );\r\n}\r\n",
|
|
1339
|
+
"src/apis/auth/client/login.ts": "import { clientFetch } from '@/lib/fetch/client';\r\n\r\nexport type TLoginPayload = {\r\n email: string;\r\n password: string;\r\n};\r\n\r\nexport type TLoginResponse = {\r\n data: {\r\n token: string;\r\n token_type?: string;\r\n expires_in?: number;\r\n };\r\n};\r\n\r\n/**\r\n * Authenticate with email/password. On success the backend returns a bearer\r\n * token. The token is wired into the session by the `useLoginMutation` hook\r\n * (cookie + Zustand store), not here.\r\n *\r\n * NOTE: `auth/login` is the assumed endpoint for this boilerplate — adjust to\r\n * the real login route if it differs.\r\n */\r\nexport const login = async (payload: TLoginPayload) => {\r\n const response = await clientFetch.post('auth/login', { json: payload });\r\n return response.json<TLoginResponse>();\r\n};\r\n",
|
|
1340
|
+
"src/apis/auth/client/register.ts": "import { clientFetch } from '@/lib/fetch/client';\r\n\r\nexport type TRegisterPayload = {\r\n name: string;\r\n email: string;\r\n password: string;\r\n};\r\n\r\nexport type TRegisterResponse = {\r\n data: {\r\n token: string;\r\n token_type?: string;\r\n expires_in?: number;\r\n };\r\n};\r\n\r\n/**\r\n * Register a new account with name/email/password. On success the backend\r\n * returns a bearer token, same shape as login. The token is wired into the\r\n * session by the `useRegisterMutation` hook (cookie + Zustand store), not\r\n * here.\r\n *\r\n * NOTE: `auth/register` is the assumed endpoint for this boilerplate — adjust\r\n * to the real registration route if it differs.\r\n */\r\nexport const register = async (payload: TRegisterPayload) => {\r\n const response = await clientFetch.post('auth/register', { json: payload });\r\n return response.json<TRegisterResponse>();\r\n};\r\n",
|
|
1341
|
+
"src/apis/auth/client/index.ts": "export { getProfile } from './get-profile';\r\nexport { login } from './login';\r\nexport type { TLoginPayload, TLoginResponse } from './login';\r\nexport { register } from './register';\r\nexport type { TRegisterPayload, TRegisterResponse } from './register';\r\n",
|
|
1342
|
+
"src/apis/auth/client/get-profile.ts": "import { clientFetch } from '@/lib/fetch/client';\r\n\r\nimport type { TUserProfileResponse } from '@/@types/user';\r\n\r\n/**\r\n * Fetch the current user's profile from the browser. A 401 here means the\r\n * token is invalid/expired; `clientFetch` handles the redirect to `/login`.\r\n *\r\n * The current-user route on the flowi backend is `auth` (GET).\r\n */\r\nexport const getProfile = async () => {\r\n const response = await clientFetch.get('auth');\r\n return response.json<TUserProfileResponse>();\r\n};\r\n",
|
|
1343
|
+
"src/apis/auth/hooks/index.ts": "export * from './mutations';\r\n",
|
|
1344
|
+
"src/apis/auth/hooks/mutations/index.ts": "export { useLoginMutation } from './use-login.mutation';\r\nexport { useRegisterMutation } from './use-register.mutation';\r\n",
|
|
1345
|
+
"src/apis/auth/hooks/mutations/use-login.mutation.ts": "import { useMutation } from '@tanstack/react-query';\r\nimport { useNavigate } from '@tanstack/react-router';\r\nimport { toast } from 'sonner';\r\n\r\nimport { getProfile, login } from '@/apis/auth/client';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nimport type { TLoginPayload, TLoginResponse } from '@/apis/auth/client';\r\n\r\n/**\r\n * Drives the full login flow (ported from flowi-admin-frontend):\r\n * 1. POST credentials, receive a bearer token.\r\n * 2. Fetch the current user's profile.\r\n * 3. Hydrate the Zustand auth store, which persists the token to the\r\n * shared auth cookie so `clientFetch` attaches the `Authorization`\r\n * header on subsequent requests.\r\n * 4. Redirect into the protected area.\r\n *\r\n * Errors are surfaced as toasts by the `clientFetch` response interceptor, so\r\n * no `onError` handler is needed here.\r\n */\r\nexport const useLoginMutation = () => {\r\n const navigate = useNavigate();\r\n\r\n return useMutation<TLoginResponse, Error, TLoginPayload>({\r\n mutationKey: ['LOGIN'],\r\n mutationFn: payload => login(payload),\r\n onSuccess: async ({ data }) => {\r\n useAuthStore.getState().setToken(data.token);\r\n\r\n const profile = await getProfile();\r\n useAuthStore.getState().storeLogin({ user: profile, token: data.token });\r\n\r\n toast.success(m.login_success());\r\n navigate({ to: '/dashboard' });\r\n },\r\n });\r\n};\r\n",
|
|
1346
|
+
"src/apis/auth/hooks/mutations/use-register.mutation.ts": "import { useMutation } from '@tanstack/react-query';\r\nimport { useNavigate } from '@tanstack/react-router';\r\nimport { toast } from 'sonner';\r\n\r\nimport { getProfile, register } from '@/apis/auth/client';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nimport type { TRegisterPayload, TRegisterResponse } from '@/apis/auth/client';\r\n\r\n/**\r\n * Drives the full signup flow, mirroring `useLoginMutation`:\r\n * 1. POST name/email/password, receive a bearer token.\r\n * 2. Fetch the current user's profile.\r\n * 3. Hydrate the Zustand auth store, which persists the token to the\r\n * shared auth cookie so `clientFetch` attaches the `Authorization`\r\n * header on subsequent requests.\r\n * 4. Redirect into the protected area.\r\n *\r\n * Errors are surfaced as toasts by the `clientFetch` response interceptor, so\r\n * no `onError` handler is needed here.\r\n */\r\nexport const useRegisterMutation = () => {\r\n const navigate = useNavigate();\r\n\r\n return useMutation<TRegisterResponse, Error, TRegisterPayload>({\r\n mutationKey: ['REGISTER'],\r\n mutationFn: payload => register(payload),\r\n onSuccess: async ({ data }) => {\r\n useAuthStore.getState().setToken(data.token);\r\n\r\n const profile = await getProfile();\r\n useAuthStore.getState().storeLogin({ user: profile, token: data.token });\r\n\r\n toast.success(m.signup_success());\r\n navigate({ to: '/dashboard' });\r\n },\r\n });\r\n};\r\n",
|
|
1347
|
+
"src/apis/query-keys/auth.ts": "export const authKeys = {\r\n all: ['auth'] as const,\r\n me: () => [...authKeys.all, 'me'] as const,\r\n} as const;\r\n\r\nexport type AuthKeys = typeof authKeys;\r\n",
|
|
1348
|
+
"src/store/auth-state.ts": "import jsCookie from 'js-cookie';\r\nimport { create } from 'zustand';\r\nimport { queryClient } from '@/lib/query-client';\r\nimport { AUTH_TOKEN_COOKIE } from '@/lib/auth/token-cookie';\r\nimport type { TUserProfileResponse } from '@/@types/user';\r\n\r\ntype AuthState = {\r\n isAuthenticated: boolean;\r\n user: Partial<TUserProfileResponse> | null;\r\n token: string | null;\r\n storeUser: (user: TUserProfileResponse | null) => void;\r\n logout: () => void;\r\n setToken: (token: string) => void;\r\n storeLogin: ({\r\n user,\r\n token,\r\n }: {\r\n user: TUserProfileResponse | null;\r\n token: string;\r\n }) => void;\r\n};\r\n\r\nconst initialToken = jsCookie.get(AUTH_TOKEN_COOKIE) ?? null;\r\n\r\nexport const useAuthStore = create<AuthState>()(set => ({\r\n isAuthenticated: Boolean(initialToken),\r\n user: null,\r\n token: initialToken,\r\n storeUser: user => {\r\n set({\r\n user,\r\n isAuthenticated: Boolean(user),\r\n });\r\n },\r\n logout: () => {\r\n set({\r\n isAuthenticated: false,\r\n user: null,\r\n token: null,\r\n });\r\n queryClient.clear();\r\n jsCookie.remove(AUTH_TOKEN_COOKIE, { path: '/' });\r\n },\r\n setToken: token => {\r\n jsCookie.set(AUTH_TOKEN_COOKIE, token, { expires: 3, path: '/' });\r\n set({ token });\r\n },\r\n storeLogin: ({ user, token }) => {\r\n jsCookie.set(AUTH_TOKEN_COOKIE, token, { expires: 3, path: '/' });\r\n set({ isAuthenticated: true, user, token });\r\n },\r\n}));\r\n",
|
|
1349
|
+
"src/lib/auth/token-cookie.ts": "export const AUTH_TOKEN_COOKIE = 'token';\r\n",
|
|
1350
|
+
"src/@types/user.ts": "// =============================================================RESPONSES=============================================================\r\n\r\nimport type { TMeta } from './common';\r\n\r\nexport type TUsersListResponse = {\r\n data: TUsersListData;\r\n meta: TMeta;\r\n};\r\n\r\nexport type TUserDetailsResponse = {\r\n data: TUserDetailsData;\r\n};\r\n\r\nexport type TUserProfileResponse = {\r\n data: TUserProfileData;\r\n};\r\n\r\n// =============================================================Data=============================================================\r\n\r\nexport type TUsersListData = TUsersListItem[];\r\n\r\nexport type TUserDetailsData = TUserDetailsItem;\r\n\r\nexport type TUserProfileData = TUserProfileItem;\r\n\r\n// =============================================================Item=============================================================\r\n\r\nexport type TUsersListItem = {\r\n id: number;\r\n name: string;\r\n email: string;\r\n avatar?: string;\r\n is_active: boolean;\r\n};\r\n\r\nexport type TUserDetailsItem = {\r\n id: number;\r\n name: string;\r\n email: string;\r\n phone?: string;\r\n avatar?: string;\r\n is_active: boolean;\r\n role: TUserRole;\r\n permissions: string[];\r\n created_at: string;\r\n updated_at: string;\r\n};\r\n\r\nexport type TUserProfileItem = {\r\n id: number;\r\n name: string;\r\n email: string;\r\n phone?: string;\r\n avatar?: string;\r\n is_active: boolean;\r\n role: TUserRole;\r\n permissions: string[];\r\n};\r\n\r\n// =============================================================Others=============================================================\r\n\r\nexport type TUserRole = {\r\n id: number;\r\n name: string;\r\n slug: string;\r\n};\r\n\r\n// Alias for backwards compatibility with auth-state\r\nexport type TUser = TUserProfileItem;\r\nexport type User = TUserProfileItem;\r\n",
|
|
1351
|
+
"src/components/common/controllers/input-field-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { LabeledInput } from '@/components/ui/composed/input/labeled-input';\r\n\r\ntype InputFieldControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof LabeledInput>,\r\n 'value' | 'onChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n formatter?: (value: string) => string;\r\n parser?: (value: string) => string;\r\n};\r\n\r\nexport function InputFieldController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n formatter,\r\n parser,\r\n ...rest\r\n}: InputFieldControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n const displayValue =\r\n typeof field.value === 'string'\r\n ? formatter\r\n ? formatter(field.value)\r\n : field.value\r\n : (field.value ?? '');\r\n\r\n return (\r\n <LabeledInput\r\n {...rest}\r\n value={displayValue}\r\n onChange={e => {\r\n const raw = e.target.value;\r\n field.onChange(parser ? parser(raw) : raw);\r\n }}\r\n onBlur={field.onBlur}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1352
|
+
"src/hooks/use-response-error.ts": "import type { FieldValues, UseFormSetError } from 'react-hook-form';\r\nimport type { ApiErrorResponse } from '@/lib/fetch/client';\r\n\r\nexport type ResponseError = ApiErrorResponse | { body?: ApiErrorResponse };\r\n\r\nconst isApiErrorResponse = (value: unknown): value is ApiErrorResponse => {\r\n if (!value || typeof value !== 'object') return false;\r\n\r\n return 'errors' in value || 'message' in value;\r\n};\r\n\r\nconst getResponseErrorBody = (error: unknown): ApiErrorResponse | null => {\r\n if (!error || typeof error !== 'object') return null;\r\n\r\n if ('body' in error && isApiErrorResponse(error.body)) {\r\n return error.body;\r\n }\r\n\r\n if (isApiErrorResponse(error)) {\r\n return error;\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Map a structured API error response into react-hook-form field errors.\r\n *\r\n * Call this from a mutation's `onError` (or any event handler) — there is no\r\n * reactive hook here. The previous `useEffect`-driven version is replaced by\r\n * direct invocation at the moment the error arrives, which is the pattern\r\n * CLAUDE.md prescribes for responding to user-triggered side effects.\r\n *\r\n * Example:\r\n * const { mutate } = useMutation({\r\n * mutationFn,\r\n * onError: (err: ResponseError) => setResponseFormErrors(setError, err),\r\n * })\r\n */\r\nexport const setResponseFormErrors = <TFieldValues extends FieldValues>(\r\n setError: UseFormSetError<TFieldValues>,\r\n error: unknown\r\n): void => {\r\n const body = getResponseErrorBody(error);\r\n if (!body?.errors) return;\r\n\r\n const { errors } = body;\r\n Object.keys(errors).forEach(fieldName => {\r\n const errorMessage = errors[fieldName][0];\r\n setError(fieldName as never, {\r\n type: 'manual',\r\n message: errorMessage,\r\n });\r\n });\r\n};\r\n",
|
|
1353
|
+
"cli/templates/nextjs/src/app/(public)/(auth)/layout.tsx": "'use client';\r\n\r\nimport { useEffect, type ReactNode } from 'react';\r\nimport { useRouter } from 'next/navigation';\r\n\r\nimport { AuthLayout } from '@/components/layouts';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\nexport default function AuthGroupLayout({ children }: { children: ReactNode }) {\r\n const router = useRouter();\r\n const isAuthenticated = useAuthStore(state => state.isAuthenticated);\r\n\r\n useEffect(() => {\r\n if (isAuthenticated) router.replace('/dashboard');\r\n }, [isAuthenticated, router]);\r\n\r\n if (isAuthenticated) return null;\r\n\r\n return <AuthLayout>{children}</AuthLayout>;\r\n}\r\n",
|
|
1354
|
+
"cli/templates/nextjs/src/app/(public)/(auth)/login/page.tsx": "'use client';\r\n\r\nimport { LoginScreen } from '@/components/screens/login';\r\n\r\nexport default function LoginPage() {\r\n return <LoginScreen />;\r\n}\r\n",
|
|
1355
|
+
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-login.mutation.ts": "'use client';\r\n\r\nimport { useMutation } from '@tanstack/react-query';\r\nimport { useRouter } from 'next/navigation';\r\nimport { toast } from 'sonner';\r\n\r\nimport { getProfile, login } from '@/apis/auth/client';\r\nimport { t } from '@/lib/composed-strings';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\nimport type { TLoginPayload, TLoginResponse } from '@/apis/auth/client';\r\n\r\nexport const useLoginMutation = () => {\r\n const router = useRouter();\r\n\r\n return useMutation<TLoginResponse, Error, TLoginPayload>({\r\n mutationKey: ['LOGIN'],\r\n mutationFn: payload => login(payload),\r\n onSuccess: async ({ data }) => {\r\n useAuthStore.getState().setToken(data.token);\r\n\r\n const profile = await getProfile();\r\n useAuthStore.getState().storeLogin({ user: profile, token: data.token });\r\n\r\n toast.success(t('login_success'));\r\n router.push('/dashboard');\r\n },\r\n });\r\n};\r\n",
|
|
1356
|
+
"cli/templates/nextjs/src/apis/auth/hooks/mutations/use-register.mutation.ts": "'use client';\r\n\r\nimport { useMutation } from '@tanstack/react-query';\r\nimport { useRouter } from 'next/navigation';\r\nimport { toast } from 'sonner';\r\n\r\nimport { getProfile, register } from '@/apis/auth/client';\r\nimport { t } from '@/lib/composed-strings';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\nimport type { TRegisterPayload, TRegisterResponse } from '@/apis/auth/client';\r\n\r\nexport const useRegisterMutation = () => {\r\n const router = useRouter();\r\n\r\n return useMutation<TRegisterResponse, Error, TRegisterPayload>({\r\n mutationKey: ['REGISTER'],\r\n mutationFn: payload => register(payload),\r\n onSuccess: async ({ data }) => {\r\n useAuthStore.getState().setToken(data.token);\r\n\r\n const profile = await getProfile();\r\n useAuthStore.getState().storeLogin({ user: profile, token: data.token });\r\n\r\n toast.success(t('signup_success'));\r\n router.push('/dashboard');\r\n },\r\n });\r\n};\r\n",
|
|
1357
|
+
"src/lib/query-client.ts": "import { QueryClient } from '@tanstack/react-query';\r\n\r\nexport const queryClient = new QueryClient({\r\n defaultOptions: {\r\n queries: {\r\n staleTime: 60 * 1000, // 1 minute\r\n gcTime: 10 * 60 * 1000, // 10 minutes\r\n retry: 0,\r\n refetchOnWindowFocus: false,\r\n },\r\n mutations: {\r\n retry: 0,\r\n },\r\n },\r\n});\r\n",
|
|
1358
|
+
"src/routes/(public-pages)/(auth)/signup.tsx": "import { createFileRoute } from '@tanstack/react-router';\r\nimport { SignupScreen } from '@/components/screens/signup';\r\nimport { m } from '#/paraglide/messages';\r\n\r\nexport const Route = createFileRoute('/(public-pages)/(auth)/signup')({\r\n head: () => ({ meta: [{ title: m.signup_page() }] }),\r\n component: SignupScreen,\r\n});\r\n",
|
|
1359
|
+
"src/components/screens/signup/index.ts": "export { SignupScreen } from './signup-screen';\r\n",
|
|
1360
|
+
"src/components/screens/signup/signup-screen.tsx": "import { m } from '#/paraglide/messages';\r\n\r\nimport { SignupForm } from './signup-form';\r\n\r\nexport function SignupScreen() {\r\n return (\r\n <main className='w-full max-w-md'>\r\n <div className='rounded-xl border border-border bg-card p-8 shadow-sm'>\r\n <div className='mb-8 text-center'>\r\n <h1 className='text-2xl font-bold tracking-tight text-foreground'>\r\n {m.signup_title()}\r\n </h1>\r\n <p className='text-muted-foreground mt-2 text-sm'>\r\n {m.signup_intro()}\r\n </p>\r\n </div>\r\n\r\n <SignupForm />\r\n </div>\r\n </main>\r\n );\r\n}\r\n",
|
|
1361
|
+
"src/components/screens/signup/signup-form.tsx": "import { zodResolver } from '@hookform/resolvers/zod';\r\nimport { useForm } from 'react-hook-form';\r\n\r\nimport { useRegisterMutation } from '@/apis/auth/hooks';\r\nimport { InputFieldController } from '@/components/common/controllers';\r\nimport { LoadingButton } from '@/components/ui/composed/button/loading-button';\r\nimport { setResponseFormErrors } from '@/hooks/use-response-error';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nimport { signupSchema } from './signup-schema';\r\n\r\nimport type { TSignupFormValues } from './signup-schema';\r\n\r\nexport function SignupForm() {\r\n const { mutate, isPending, isSuccess } = useRegisterMutation();\r\n\r\n const form = useForm<TSignupFormValues>({\r\n resolver: zodResolver(signupSchema()),\r\n defaultValues: { name: '', email: '', password: '', confirmPassword: '' },\r\n });\r\n\r\n // Keep the button busy through the post-success redirect.\r\n const isLoading = isPending || isSuccess;\r\n\r\n const onSubmit = (values: TSignupFormValues) =>\r\n mutate(\r\n { name: values.name, email: values.email, password: values.password },\r\n { onError: error => setResponseFormErrors(form.setError, error) }\r\n );\r\n\r\n return (\r\n <form\r\n noValidate\r\n autoComplete='off'\r\n onSubmit={form.handleSubmit(onSubmit)}\r\n className='flex flex-col gap-6'\r\n >\r\n <InputFieldController\r\n control={form.control}\r\n name='name'\r\n type='text'\r\n autoComplete='name'\r\n label={m.signup_name_label()}\r\n placeholder={m.signup_name_placeholder()}\r\n />\r\n\r\n <InputFieldController\r\n control={form.control}\r\n name='email'\r\n type='email'\r\n inputMode='email'\r\n autoComplete='email'\r\n label={m.signup_email_label()}\r\n placeholder={m.signup_email_placeholder()}\r\n />\r\n\r\n <InputFieldController\r\n control={form.control}\r\n name='password'\r\n type='password'\r\n autoComplete='new-password'\r\n label={m.signup_password_label()}\r\n placeholder={m.signup_password_placeholder()}\r\n />\r\n\r\n <InputFieldController\r\n control={form.control}\r\n name='confirmPassword'\r\n type='password'\r\n autoComplete='new-password'\r\n label={m.signup_confirm_password_label()}\r\n placeholder={m.signup_confirm_password_placeholder()}\r\n />\r\n\r\n <LoadingButton\r\n type='submit'\r\n className='w-full'\r\n loading={isLoading}\r\n loadingText={m.signup_submitting()}\r\n >\r\n {m.signup_submit()}\r\n </LoadingButton>\r\n </form>\r\n );\r\n}\r\n",
|
|
1362
|
+
"src/components/screens/signup/signup-schema.ts": "import { z } from 'zod';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\n/**\r\n * Built lazily as a function so the Paraglide message getters resolve against\r\n * the active locale at validation time (same pattern as loginSchema).\r\n */\r\nexport const signupSchema = () =>\r\n z\r\n .object({\r\n name: z.string().min(1, { message: m.validation_name_required() }),\r\n email: z\r\n .string()\r\n .min(1, { message: m.validation_email_required() })\r\n .email({ message: m.validation_email_invalid() }),\r\n password: z\r\n .string()\r\n .min(1, { message: m.validation_password_required() }),\r\n confirmPassword: z\r\n .string()\r\n .min(1, { message: m.validation_confirm_password_required() }),\r\n })\r\n .refine(values => values.password === values.confirmPassword, {\r\n message: m.validation_password_mismatch(),\r\n path: ['confirmPassword'],\r\n });\r\n\r\nexport type TSignupFormValues = z.infer<ReturnType<typeof signupSchema>>;\r\n",
|
|
1363
|
+
"cli/templates/nextjs/src/app/(public)/(auth)/signup/page.tsx": "'use client';\r\n\r\nimport { SignupScreen } from '@/components/screens/signup';\r\n\r\nexport default function SignupPage() {\r\n return <SignupScreen />;\r\n}\r\n",
|
|
1364
|
+
"src/routes/(protected-pages)/route.tsx": "import { Outlet, createFileRoute, redirect } from '@tanstack/react-router';\r\nimport { useQuery } from '@tanstack/react-query';\r\n\r\nimport { getProfile } from '@/apis/auth/client';\r\nimport { authKeys } from '@/apis/query-keys';\r\nimport { DashboardLayout } from '@/components/layouts';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\n/**\r\n * Client-side auth guard for every page under `(protected-pages)`.\r\n *\r\n * This app runs as a client-side SPA (see `src/start.ts`'s `defaultSsr:\r\n * false`), so `beforeLoad` only ever runs in the browser — reading\r\n * `isAuthenticated` straight from the Zustand store (hydrated synchronously\r\n * from the `token` cookie) is sufficient; no server round trip is needed.\r\n *\r\n * The layout component separately fetches the current user via React Query\r\n * and stores it in Zustand — that's reactive profile state (instant display\r\n * elsewhere in the UI, kept fresh via React Query), not the auth guard\r\n * itself.\r\n */\r\nexport const Route = createFileRoute('/(protected-pages)')({\r\n beforeLoad: () => {\r\n if (!useAuthStore.getState().isAuthenticated) {\r\n throw redirect({ to: '/login' });\r\n }\r\n },\r\n\r\n component: ProtectedLayout,\r\n});\r\n\r\nfunction ProtectedLayout() {\r\n useQuery({\r\n queryKey: authKeys.me(),\r\n queryFn: async () => {\r\n const profile = await getProfile();\r\n useAuthStore.getState().storeUser(profile);\r\n return profile;\r\n },\r\n });\r\n\r\n return (\r\n <DashboardLayout>\r\n <Outlet />\r\n </DashboardLayout>\r\n );\r\n}\r\n",
|
|
1365
|
+
"src/routes/(protected-pages)/dashboard.tsx": "import { createFileRoute } from '@tanstack/react-router';\r\n\r\nimport { DashboardScreen } from '@/components/screens/dashboard';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nexport const Route = createFileRoute('/(protected-pages)/dashboard')({\r\n head: () => ({ meta: [{ title: m.dashboard_page() }] }),\r\n component: DashboardScreen,\r\n});\r\n",
|
|
1366
|
+
"src/components/screens/dashboard/index.ts": "export { DashboardScreen } from './dashboard-screen';\r\n",
|
|
1367
|
+
"src/components/screens/dashboard/dashboard-screen.tsx": "import { m } from '#/paraglide/messages';\r\n\r\nexport function DashboardScreen() {\r\n return (\r\n <div className='mx-auto max-w-3xl px-6 py-12'>\r\n <h1 className='text-4xl font-bold tracking-tight'>\r\n {m.dashboard_title()}\r\n </h1>\r\n <p className='text-muted-foreground mt-4 text-lg'>\r\n {m.dashboard_intro()}\r\n </p>\r\n </div>\r\n );\r\n}\r\n",
|
|
1368
|
+
"src/components/layouts/dashboard-layout.tsx": "import type { ReactNode } from 'react';\r\n\r\nimport {\r\n SidebarInset,\r\n SidebarProvider,\r\n SidebarTrigger,\r\n} from '@/components/ui/sidebar';\r\nimport { ThemeToggle } from '@/components/ui/composed/button/theme-toggle';\r\nimport { LocaleSwitcher } from '@/components/ui/composed/dropdown-menu/locale-switcher';\r\nimport { TooltipProvider } from '@/components/ui/tooltip';\r\nimport { useDir } from '@/hooks/use-dir';\r\n\r\nimport { DashboardSidebar } from './dashboard-sidebar';\r\n\r\ntype DashboardLayoutProps = {\r\n children: ReactNode;\r\n};\r\n\r\nexport function DashboardLayout({ children }: DashboardLayoutProps) {\r\n const direction = useDir();\r\n const side = direction === 'rtl' ? 'right' : 'left';\r\n\r\n return (\r\n <TooltipProvider>\r\n <SidebarProvider>\r\n <DashboardSidebar side={side} dir={direction} />\r\n <SidebarInset>\r\n <header className='bg-background sticky top-0 z-10 flex h-14 items-center gap-2 border-b px-4'>\r\n <SidebarTrigger />\r\n <div className='ms-auto flex items-center gap-2'>\r\n <LocaleSwitcher />\r\n <ThemeToggle />\r\n </div>\r\n </header>\r\n <div className='flex-1'>{children}</div>\r\n </SidebarInset>\r\n </SidebarProvider>\r\n </TooltipProvider>\r\n );\r\n}\r\n",
|
|
1369
|
+
"src/components/layouts/dashboard-sidebar.tsx": "import { Link, useMatchRoute, useNavigate } from '@tanstack/react-router';\r\nimport { LayoutDashboardIcon, LogOutIcon } from 'lucide-react';\r\n\r\nimport { UserAvatar } from '@/components/ui/composed/avatar/user-avatar';\r\nimport {\r\n Sidebar,\r\n SidebarContent,\r\n SidebarFooter,\r\n SidebarGroup,\r\n SidebarGroupContent,\r\n SidebarHeader,\r\n SidebarMenu,\r\n SidebarMenuButton,\r\n SidebarMenuItem,\r\n SidebarRail,\r\n} from '@/components/ui/sidebar';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\ntype DashboardSidebarProps = {\r\n side?: 'left' | 'right';\r\n dir?: 'ltr' | 'rtl';\r\n};\r\n\r\nexport function DashboardSidebar({\r\n side = 'left',\r\n dir,\r\n}: DashboardSidebarProps) {\r\n const matchRoute = useMatchRoute();\r\n const navigate = useNavigate();\r\n const user = useAuthStore(state => state.user);\r\n const logout = useAuthStore(state => state.logout);\r\n\r\n const profile = user?.data;\r\n const displayName = profile?.name ?? profile?.email ?? '';\r\n const email = profile?.email ?? '';\r\n const isDashboard = Boolean(matchRoute({ to: '/dashboard' }));\r\n\r\n const onLogout = () => {\r\n logout();\r\n navigate({ to: '/login' });\r\n };\r\n\r\n return (\r\n <Sidebar side={side} dir={dir} collapsible='icon'>\r\n <SidebarHeader>\r\n <SidebarMenu>\r\n <SidebarMenuItem>\r\n <SidebarMenuButton size='lg' render={<Link to='/dashboard' />}>\r\n <span className='text-sm font-semibold'>\r\n {m.dashboard_page()}\r\n </span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </SidebarHeader>\r\n <SidebarContent>\r\n <SidebarGroup>\r\n <SidebarGroupContent>\r\n <SidebarMenu>\r\n <SidebarMenuItem>\r\n <SidebarMenuButton\r\n isActive={isDashboard}\r\n tooltip={m.dashboard_page()}\r\n render={<Link to='/dashboard' />}\r\n >\r\n <LayoutDashboardIcon />\r\n <span>{m.dashboard_page()}</span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </SidebarGroupContent>\r\n </SidebarGroup>\r\n </SidebarContent>\r\n <SidebarFooter>\r\n <SidebarMenu>\r\n {displayName ? (\r\n <SidebarMenuItem>\r\n <SidebarMenuButton size='lg' className='pointer-events-none'>\r\n <UserAvatar name={displayName} src={profile?.avatar} />\r\n <span className='grid flex-1 text-start text-sm leading-tight'>\r\n <span className='truncate font-medium'>{displayName}</span>\r\n {email && displayName !== email ? (\r\n <span className='text-muted-foreground truncate text-xs'>\r\n {email}\r\n </span>\r\n ) : null}\r\n </span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n ) : null}\r\n <SidebarMenuItem>\r\n <SidebarMenuButton\r\n tooltip={m.dashboard_logout()}\r\n onClick={onLogout}\r\n >\r\n <LogOutIcon />\r\n <span>{m.dashboard_logout()}</span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </SidebarFooter>\r\n <SidebarRail />\r\n </Sidebar>\r\n );\r\n}\r\n",
|
|
1370
|
+
"cli/templates/nextjs/src/app/(protected)/layout.tsx": "'use client';\r\n\r\nimport { useEffect, type ReactNode } from 'react';\r\nimport { useRouter } from 'next/navigation';\r\n\r\nimport { getProfile } from '@/apis/auth/client';\r\nimport { DashboardLayout } from '@/components/layouts';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\nexport default function ProtectedLayout({ children }: { children: ReactNode }) {\r\n const router = useRouter();\r\n const isAuthenticated = useAuthStore(state => state.isAuthenticated);\r\n\r\n useEffect(() => {\r\n if (!isAuthenticated) {\r\n router.replace('/login');\r\n return;\r\n }\r\n void getProfile().then(profile => useAuthStore.getState().storeUser(profile));\r\n }, [isAuthenticated, router]);\r\n\r\n if (!isAuthenticated) return null;\r\n\r\n return <DashboardLayout>{children}</DashboardLayout>;\r\n}\r\n",
|
|
1371
|
+
"cli/templates/nextjs/src/app/(protected)/dashboard/page.tsx": "'use client';\r\n\r\nimport { DashboardScreen } from '@/components/screens/dashboard';\r\n\r\nexport default function DashboardPage() {\r\n return <DashboardScreen />;\r\n}\r\n",
|
|
1372
|
+
"cli/templates/nextjs/src/components/layouts/dashboard-sidebar.tsx": "'use client';\r\n\r\nimport Link from 'next/link';\r\nimport { usePathname, useRouter } from 'next/navigation';\r\nimport { LayoutDashboardIcon, LogOutIcon } from 'lucide-react';\r\n\r\nimport { UserAvatar } from '@/components/ui/composed/avatar/user-avatar';\r\nimport {\r\n Sidebar,\r\n SidebarContent,\r\n SidebarFooter,\r\n SidebarGroup,\r\n SidebarGroupContent,\r\n SidebarHeader,\r\n SidebarMenu,\r\n SidebarMenuButton,\r\n SidebarMenuItem,\r\n SidebarRail,\r\n} from '@/components/ui/sidebar';\r\nimport { t } from '@/lib/composed-strings';\r\nimport { useAuthStore } from '@/store/auth-state';\r\n\r\ntype DashboardSidebarProps = {\r\n side?: 'left' | 'right';\r\n dir?: 'ltr' | 'rtl';\r\n};\r\n\r\nexport function DashboardSidebar({\r\n side = 'left',\r\n dir,\r\n}: DashboardSidebarProps) {\r\n const pathname = usePathname();\r\n const router = useRouter();\r\n const user = useAuthStore(state => state.user);\r\n const logout = useAuthStore(state => state.logout);\r\n\r\n const profile = user?.data;\r\n const displayName = profile?.name ?? profile?.email ?? '';\r\n const email = profile?.email ?? '';\r\n const isDashboard = pathname === '/dashboard' || pathname.startsWith('/dashboard/');\r\n\r\n const onLogout = () => {\r\n logout();\r\n router.push('/login');\r\n };\r\n\r\n return (\r\n <Sidebar side={side} dir={dir} collapsible='icon'>\r\n <SidebarHeader>\r\n <SidebarMenu>\r\n <SidebarMenuItem>\r\n <SidebarMenuButton size='lg' render={<Link href='/dashboard' />}>\r\n <span className='text-sm font-semibold'>{t('dashboard_page')}</span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </SidebarHeader>\r\n <SidebarContent>\r\n <SidebarGroup>\r\n <SidebarGroupContent>\r\n <SidebarMenu>\r\n <SidebarMenuItem>\r\n <SidebarMenuButton\r\n isActive={isDashboard}\r\n tooltip={t('dashboard_page')}\r\n render={<Link href='/dashboard' />}\r\n >\r\n <LayoutDashboardIcon />\r\n <span>{t('dashboard_page')}</span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </SidebarGroupContent>\r\n </SidebarGroup>\r\n </SidebarContent>\r\n <SidebarFooter>\r\n <SidebarMenu>\r\n {displayName ? (\r\n <SidebarMenuItem>\r\n <SidebarMenuButton size='lg' className='pointer-events-none'>\r\n <UserAvatar name={displayName} src={profile?.avatar} />\r\n <span className='grid flex-1 text-start text-sm leading-tight'>\r\n <span className='truncate font-medium'>{displayName}</span>\r\n {email && displayName !== email ? (\r\n <span className='text-muted-foreground truncate text-xs'>\r\n {email}\r\n </span>\r\n ) : null}\r\n </span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n ) : null}\r\n <SidebarMenuItem>\r\n <SidebarMenuButton tooltip={t('dashboard_logout')} onClick={onLogout}>\r\n <LogOutIcon />\r\n <span>{t('dashboard_logout')}</span>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </SidebarFooter>\r\n <SidebarRail />\r\n </Sidebar>\r\n );\r\n}\r\n",
|
|
1373
|
+
"src/routes/(public-pages)/about.tsx": "import { createFileRoute } from '@tanstack/react-router';\r\n\r\nimport { AboutScreen } from '@/components/screens/about';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nexport const Route = createFileRoute('/(public-pages)/about')({\r\n head: () => ({ meta: [{ title: m.about_page() }] }),\r\n component: AboutScreen,\r\n});\r\n",
|
|
1374
|
+
"src/components/screens/about/index.ts": "export { AboutScreen } from './about-screen';\r\n",
|
|
1375
|
+
"src/components/screens/about/about-screen.tsx": "import { m } from '#/paraglide/messages';\r\n\r\nexport function AboutScreen() {\r\n return (\r\n <main className='mx-auto max-w-3xl px-6 py-12'>\r\n <h1 className='text-4xl font-bold tracking-tight'>{m.about_title()}</h1>\r\n <p className='text-muted-foreground mt-4 text-lg'>{m.about_intro()}</p>\r\n </main>\r\n );\r\n}\r\n",
|
|
1376
|
+
"cli/templates/nextjs/src/app/(public)/about/page.tsx": "import { AboutScreen } from '@/components/screens/about';\r\n\r\nexport default function AboutPage() {\r\n return <AboutScreen />;\r\n}\r\n",
|
|
1377
|
+
"src/routes/(public-pages)/contact.tsx": "import { createFileRoute } from '@tanstack/react-router';\r\n\r\nimport { ContactScreen } from '@/components/screens/contact';\r\n\r\nimport { m } from '#/paraglide/messages';\r\n\r\nexport const Route = createFileRoute('/(public-pages)/contact')({\r\n head: () => ({ meta: [{ title: m.contact_page() }] }),\r\n component: ContactScreen,\r\n});\r\n",
|
|
1378
|
+
"src/components/screens/contact/index.ts": "export { ContactScreen } from './contact-screen';\r\n",
|
|
1379
|
+
"src/components/screens/contact/contact-screen.tsx": "import { m } from '#/paraglide/messages';\r\n\r\nexport function ContactScreen() {\r\n return (\r\n <main className='mx-auto max-w-3xl px-6 py-12'>\r\n <h1 className='text-4xl font-bold tracking-tight'>{m.contact_title()}</h1>\r\n <p className='text-muted-foreground mt-4 text-lg'>{m.contact_intro()}</p>\r\n </main>\r\n );\r\n}\r\n",
|
|
1380
|
+
"cli/templates/nextjs/src/app/(public)/contact/page.tsx": "import { ContactScreen } from '@/components/screens/contact';\r\n\r\nexport default function ContactPage() {\r\n return <ContactScreen />;\r\n}\r\n",
|
|
1381
|
+
"src/components/common/controllers/checkbox-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { CheckboxField } from '@/components/ui/composed/checkbox/checkbox-field';\r\n\r\ntype CheckboxControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof CheckboxField>,\r\n 'checked' | 'onCheckedChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function CheckboxController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: CheckboxControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <CheckboxField\r\n {...rest}\r\n checked={field.value ?? false}\r\n onCheckedChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1382
|
+
"src/components/common/controllers/checkbox-group-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { CheckboxGroup } from '@/components/ui/composed/checkbox/checkbox-group';\r\n\r\ntype CheckboxGroupControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof CheckboxGroup>,\r\n 'value' | 'defaultValue' | 'onValueChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function CheckboxGroupController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: CheckboxGroupControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <CheckboxGroup\r\n {...rest}\r\n value={field.value ?? []}\r\n onValueChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1383
|
+
"src/components/common/controllers/combobox-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { SearchableSelect } from '@/components/ui/composed/combobox/searchable-select';\r\n\r\ntype ComboboxControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof SearchableSelect>,\r\n 'value' | 'onValueChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function ComboboxController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: ComboboxControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <SearchableSelect\r\n {...rest}\r\n value={field.value ?? ''}\r\n onValueChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1384
|
+
"src/components/common/controllers/date-picker-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { DatePickerField } from '@/components/ui/composed/date-picker/date-picker-field';\r\n\r\ntype DatePickerControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof DatePickerField>,\r\n 'value' | 'onValueChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function DatePickerController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: DatePickerControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <DatePickerField\r\n {...rest}\r\n value={field.value}\r\n onValueChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1385
|
+
"src/components/common/controllers/file-dropzone-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { FileDropzone } from '@/components/ui/composed/upload/file-dropzone';\r\n\r\ntype FileDropzoneControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof FileDropzone>,\r\n 'value' | 'onValueChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function FileDropzoneController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: FileDropzoneControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <FileDropzone\r\n {...rest}\r\n value={field.value ?? []}\r\n onValueChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1386
|
+
"src/components/common/controllers/index.ts": "export { CheckboxController } from './checkbox-controller';\r\nexport { CheckboxGroupController } from './checkbox-group-controller';\r\nexport { InputFieldController } from './input-field-controller';\r\nexport { OtpFieldController } from './input-otp-controller';\r\nexport { RadioGroupController } from './radio-group-controller';\r\nexport { ComboboxController } from './combobox-controller';\r\nexport { DatePickerController } from './date-picker-controller';\r\nexport { FileDropzoneController } from './file-dropzone-controller';\r\nexport { SelectController } from './select-controller';\r\nexport { SelectGroupsController } from './select-groups-controller';\r\nexport { TextareaController } from './textarea-controller';\r\n",
|
|
1387
|
+
"src/components/common/controllers/input-otp-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { OtpField } from '@/components/ui/composed/input-otp/otp-field';\r\n\r\ntype OtpFieldControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof OtpField>,\r\n 'value' | 'onChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function OtpFieldController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: OtpFieldControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <OtpField\r\n {...rest}\r\n value={field.value ?? ''}\r\n onChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1388
|
+
"src/components/common/controllers/radio-group-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { OptionsRadioGroup } from '@/components/ui/composed/radio-group/options-radio-group';\r\n\r\ntype RadioGroupControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof OptionsRadioGroup>,\r\n 'value' | 'onValueChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function RadioGroupController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: RadioGroupControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <OptionsRadioGroup\r\n {...rest}\r\n value={field.value}\r\n onValueChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1389
|
+
"src/components/common/controllers/select-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { LabeledSelect } from '@/components/ui/composed/select/labeled-select';\r\n\r\ntype SelectControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof LabeledSelect>,\r\n 'value' | 'onValueChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function SelectController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: SelectControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <LabeledSelect\r\n {...rest}\r\n value={field.value ?? ''}\r\n onValueChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1390
|
+
"src/components/common/controllers/select-groups-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { SelectGroups } from '@/components/ui/composed/select/select-groups';\r\n\r\ntype SelectGroupsControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof SelectGroups>,\r\n 'value' | 'onValueChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function SelectGroupsController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: SelectGroupsControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <SelectGroups\r\n {...rest}\r\n value={field.value ?? ''}\r\n onValueChange={field.onChange}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1391
|
+
"src/components/common/controllers/textarea-controller.tsx": "import type { ComponentProps } from 'react';\r\nimport { useController } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n RegisterOptions,\r\n} from 'react-hook-form';\r\n\r\nimport { LabeledTextarea } from '@/components/ui/composed/textarea/labeled-textarea';\r\n\r\ntype TextareaControllerProps<T extends FieldValues> = Omit<\r\n ComponentProps<typeof LabeledTextarea>,\r\n 'value' | 'onChange' | 'error'\r\n> & {\r\n name: Path<T>;\r\n control?: Control<T>;\r\n rules?: RegisterOptions<T>;\r\n};\r\n\r\nexport function TextareaController<T extends FieldValues>({\r\n name,\r\n control,\r\n rules,\r\n ...rest\r\n}: TextareaControllerProps<T>) {\r\n const { field, fieldState } = useController({ name, control, rules });\r\n\r\n return (\r\n <LabeledTextarea\r\n {...rest}\r\n value={field.value ?? ''}\r\n onChange={field.onChange}\r\n onBlur={field.onBlur}\r\n error={fieldState.error?.message}\r\n />\r\n );\r\n}\r\n",
|
|
1392
|
+
"src/lib/utils.ts": "import { clsx } from 'clsx';\r\nimport type { ClassValue } from 'clsx';\r\nimport { twMerge } from 'tailwind-merge';\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n"
|
|
1393
|
+
},
|
|
1394
|
+
"messages": {
|
|
1395
|
+
"en": {
|
|
1396
|
+
"about_page": "About page",
|
|
1397
|
+
"about_title": "About us",
|
|
1398
|
+
"about_intro": "Learn more about who we are and what we do.",
|
|
1399
|
+
"contact_page": "Contact page",
|
|
1400
|
+
"contact_title": "Contact us",
|
|
1401
|
+
"contact_intro": "Get in touch and we'll get back to you shortly.",
|
|
1402
|
+
"dashboard_page": "Dashboard",
|
|
1403
|
+
"dashboard_title": "Dashboard",
|
|
1404
|
+
"dashboard_intro": "Welcome back. Here's an overview of your account.",
|
|
1405
|
+
"dashboard_signed_in_as": "Signed in as {email}",
|
|
1406
|
+
"dashboard_logout": "Log out",
|
|
1407
|
+
"home_page": "Home",
|
|
1408
|
+
"home_title": "Welcome to TanStack Start",
|
|
1409
|
+
"home_intro": "Edit src/components/screens/home/home-screen.tsx to get started.",
|
|
1410
|
+
"login_page": "Log in",
|
|
1411
|
+
"login_title": "Log in",
|
|
1412
|
+
"login_intro": "Sign in to continue to your account.",
|
|
1413
|
+
"login_email_label": "Email",
|
|
1414
|
+
"login_email_placeholder": "Enter your email",
|
|
1415
|
+
"login_password_label": "Password",
|
|
1416
|
+
"login_password_placeholder": "Enter your password",
|
|
1417
|
+
"login_submit": "Log in",
|
|
1418
|
+
"login_submitting": "Logging in...",
|
|
1419
|
+
"login_success": "Welcome! You have successfully logged in.",
|
|
1420
|
+
"signup_page": "Sign up",
|
|
1421
|
+
"signup_title": "Create an account",
|
|
1422
|
+
"signup_intro": "Sign up to get started.",
|
|
1423
|
+
"signup_name_label": "Full name",
|
|
1424
|
+
"signup_name_placeholder": "Enter your full name",
|
|
1425
|
+
"signup_email_label": "Email",
|
|
1426
|
+
"signup_email_placeholder": "Enter your email",
|
|
1427
|
+
"signup_password_label": "Password",
|
|
1428
|
+
"signup_password_placeholder": "Create a password",
|
|
1429
|
+
"signup_confirm_password_label": "Confirm password",
|
|
1430
|
+
"signup_confirm_password_placeholder": "Re-enter your password",
|
|
1431
|
+
"signup_submit": "Create account",
|
|
1432
|
+
"signup_submitting": "Creating account...",
|
|
1433
|
+
"signup_success": "Welcome! Your account has been created.",
|
|
1434
|
+
"status_title_404": "Page not found",
|
|
1435
|
+
"status_error_404": "The requested resource was not found.",
|
|
1436
|
+
"status_title_500": "Something went wrong",
|
|
1437
|
+
"status_error_500": "Something went wrong. Please try again.",
|
|
1438
|
+
"status_go_home": "Go to homepage",
|
|
1439
|
+
"status_go_back": "Go back",
|
|
1440
|
+
"status_retry": "Try again",
|
|
1441
|
+
"ui_select_placeholder": "Select an option",
|
|
1442
|
+
"ui_search_placeholder": "Search...",
|
|
1443
|
+
"ui_no_results": "No results found.",
|
|
1444
|
+
"ui_pick_date": "Pick a date",
|
|
1445
|
+
"ui_confirm": "Confirm",
|
|
1446
|
+
"ui_cancel": "Cancel",
|
|
1447
|
+
"ui_open_actions": "Open actions",
|
|
1448
|
+
"ui_more_information": "More information",
|
|
1449
|
+
"ui_clear_search": "Clear search",
|
|
1450
|
+
"ui_previous": "Previous",
|
|
1451
|
+
"ui_next": "Next",
|
|
1452
|
+
"ui_previous_page": "Go to previous page",
|
|
1453
|
+
"ui_next_page": "Go to next page",
|
|
1454
|
+
"ui_toggle_theme": "Toggle theme",
|
|
1455
|
+
"ui_switch_language": "Switch language",
|
|
1456
|
+
"ui_language_en": "English",
|
|
1457
|
+
"ui_language_ar": "Arabic",
|
|
1458
|
+
"ui_dropzone_prompt": "Drag and drop a file here, or browse",
|
|
1459
|
+
"ui_browse_files": "Browse files",
|
|
1460
|
+
"ui_remove_file": "Remove file",
|
|
1461
|
+
"ui_max_size": "Maximum size {size}",
|
|
1462
|
+
"validation_email_required": "Email field is required",
|
|
1463
|
+
"validation_email_invalid": "Email is invalid",
|
|
1464
|
+
"validation_password_required": "Password field is required",
|
|
1465
|
+
"validation_name_required": "Name field is required",
|
|
1466
|
+
"validation_confirm_password_required": "Please confirm your password",
|
|
1467
|
+
"validation_password_mismatch": "Passwords do not match"
|
|
1468
|
+
},
|
|
1469
|
+
"ar": {
|
|
1470
|
+
"about_page": "صفحة من نحن",
|
|
1471
|
+
"about_title": "من نحن",
|
|
1472
|
+
"about_intro": "تعرّف أكثر على من نحن وما نقوم به.",
|
|
1473
|
+
"contact_page": "صفحة التواصل",
|
|
1474
|
+
"contact_title": "تواصل معنا",
|
|
1475
|
+
"contact_intro": "تواصل معنا وسنردّ عليك في أقرب وقت.",
|
|
1476
|
+
"dashboard_page": "لوحة التحكم",
|
|
1477
|
+
"dashboard_title": "لوحة التحكم",
|
|
1478
|
+
"dashboard_intro": "مرحبًا بعودتك. إليك نظرة عامة على حسابك.",
|
|
1479
|
+
"dashboard_signed_in_as": "تم تسجيل الدخول باسم {email}",
|
|
1480
|
+
"dashboard_logout": "تسجيل الخروج",
|
|
1481
|
+
"home_page": "الرئيسية",
|
|
1482
|
+
"home_title": "مرحبًا بك في TanStack Start",
|
|
1483
|
+
"home_intro": "عدّل src/components/screens/home/home-screen.tsx للبدء.",
|
|
1484
|
+
"login_page": "تسجيل الدخول",
|
|
1485
|
+
"login_title": "تسجيل الدخول",
|
|
1486
|
+
"login_intro": "سجّل الدخول للمتابعة إلى حسابك.",
|
|
1487
|
+
"login_email_label": "البريد الإلكتروني",
|
|
1488
|
+
"login_email_placeholder": "أدخل بريدك الإلكتروني",
|
|
1489
|
+
"login_password_label": "كلمة المرور",
|
|
1490
|
+
"login_password_placeholder": "أدخل كلمة المرور",
|
|
1491
|
+
"login_submit": "تسجيل الدخول",
|
|
1492
|
+
"login_submitting": "جارٍ تسجيل الدخول...",
|
|
1493
|
+
"login_success": "مرحبًا! لقد سجّلت الدخول بنجاح.",
|
|
1494
|
+
"signup_page": "إنشاء حساب",
|
|
1495
|
+
"signup_title": "إنشاء حساب",
|
|
1496
|
+
"signup_intro": "أنشئ حسابًا للبدء.",
|
|
1497
|
+
"signup_name_label": "الاسم الكامل",
|
|
1498
|
+
"signup_name_placeholder": "أدخل اسمك الكامل",
|
|
1499
|
+
"signup_email_label": "البريد الإلكتروني",
|
|
1500
|
+
"signup_email_placeholder": "أدخل بريدك الإلكتروني",
|
|
1501
|
+
"signup_password_label": "كلمة المرور",
|
|
1502
|
+
"signup_password_placeholder": "أنشئ كلمة مرور",
|
|
1503
|
+
"signup_confirm_password_label": "تأكيد كلمة المرور",
|
|
1504
|
+
"signup_confirm_password_placeholder": "أعد إدخال كلمة المرور",
|
|
1505
|
+
"signup_submit": "إنشاء حساب",
|
|
1506
|
+
"signup_submitting": "جارٍ إنشاء الحساب...",
|
|
1507
|
+
"signup_success": "مرحبًا! تم إنشاء حسابك بنجاح.",
|
|
1508
|
+
"status_title_404": "الصفحة غير موجودة",
|
|
1509
|
+
"status_error_404": "تعذّر العثور على المورد المطلوب.",
|
|
1510
|
+
"status_title_500": "حدث خطأ ما",
|
|
1511
|
+
"status_error_500": "حدث خطأ ما. يُرجى المحاولة مرة أخرى.",
|
|
1512
|
+
"status_go_home": "الذهاب إلى الصفحة الرئيسية",
|
|
1513
|
+
"status_go_back": "رجوع",
|
|
1514
|
+
"status_retry": "حاول مرة أخرى",
|
|
1515
|
+
"ui_select_placeholder": "اختر خيارًا",
|
|
1516
|
+
"ui_search_placeholder": "بحث...",
|
|
1517
|
+
"ui_no_results": "لا توجد نتائج.",
|
|
1518
|
+
"ui_pick_date": "اختر تاريخًا",
|
|
1519
|
+
"ui_confirm": "تأكيد",
|
|
1520
|
+
"ui_cancel": "إلغاء",
|
|
1521
|
+
"ui_open_actions": "فتح الإجراءات",
|
|
1522
|
+
"ui_more_information": "مزيد من المعلومات",
|
|
1523
|
+
"ui_clear_search": "مسح البحث",
|
|
1524
|
+
"ui_previous": "السابق",
|
|
1525
|
+
"ui_next": "التالي",
|
|
1526
|
+
"ui_previous_page": "الانتقال إلى الصفحة السابقة",
|
|
1527
|
+
"ui_next_page": "الانتقال إلى الصفحة التالية",
|
|
1528
|
+
"ui_toggle_theme": "تبديل المظهر",
|
|
1529
|
+
"ui_switch_language": "تغيير اللغة",
|
|
1530
|
+
"ui_language_en": "الإنجليزية",
|
|
1531
|
+
"ui_language_ar": "العربية",
|
|
1532
|
+
"ui_dropzone_prompt": "اسحب الملف وأفلته هنا، أو تصفح",
|
|
1533
|
+
"ui_browse_files": "تصفح الملفات",
|
|
1534
|
+
"ui_remove_file": "إزالة الملف",
|
|
1535
|
+
"ui_max_size": "الحد الأقصى للحجم {size}",
|
|
1536
|
+
"validation_email_required": "حقل البريد الإلكتروني مطلوب",
|
|
1537
|
+
"validation_email_invalid": "البريد الإلكتروني غير صالح",
|
|
1538
|
+
"validation_password_required": "حقل كلمة المرور مطلوب",
|
|
1539
|
+
"validation_name_required": "حقل الاسم مطلوب",
|
|
1540
|
+
"validation_confirm_password_required": "يرجى تأكيد كلمة المرور",
|
|
1541
|
+
"validation_password_mismatch": "كلمتا المرور غير متطابقتين"
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1509
1544
|
}
|