@vendure/dashboard 3.3.6-master-202507030732 → 3.3.6-master-202507030835

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.
Files changed (58) hide show
  1. package/dist/plugin/vite-plugin-vendure-dashboard.js +1 -1
  2. package/package.json +40 -27
  3. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +32 -0
  4. package/src/app/routes/_authenticated/_collections/collections.tsx +153 -133
  5. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +34 -1
  6. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +430 -0
  7. package/src/app/routes/_authenticated/_collections/components/move-single-collection.tsx +33 -0
  8. package/src/app/routes/_authenticated/_customers/components/customer-address-card.tsx +8 -3
  9. package/src/lib/components/data-table/data-table.tsx +1 -1
  10. package/src/lib/components/shared/paginated-list-data-table.tsx +1 -1
  11. package/src/lib/components/ui/accordion.tsx +50 -45
  12. package/src/lib/components/ui/alert-dialog.tsx +122 -93
  13. package/src/lib/components/ui/alert.tsx +54 -48
  14. package/src/lib/components/ui/aspect-ratio.tsx +9 -0
  15. package/src/lib/components/ui/avatar.tsx +53 -0
  16. package/src/lib/components/ui/badge.tsx +37 -29
  17. package/src/lib/components/ui/breadcrumb.tsx +89 -82
  18. package/src/lib/components/ui/button.tsx +52 -51
  19. package/src/lib/components/ui/calendar.tsx +196 -435
  20. package/src/lib/components/ui/card.tsx +78 -33
  21. package/src/lib/components/ui/carousel.tsx +241 -0
  22. package/src/lib/components/ui/chart.tsx +351 -0
  23. package/src/lib/components/ui/checkbox.tsx +28 -23
  24. package/src/lib/components/ui/collapsible.tsx +0 -2
  25. package/src/lib/components/ui/command.tsx +159 -114
  26. package/src/lib/components/ui/context-menu.tsx +252 -0
  27. package/src/lib/components/ui/dialog.tsx +115 -90
  28. package/src/lib/components/ui/drawer.tsx +133 -0
  29. package/src/lib/components/ui/dropdown-menu.tsx +207 -170
  30. package/src/lib/components/ui/form.tsx +138 -114
  31. package/src/lib/components/ui/hover-card.tsx +32 -26
  32. package/src/lib/components/ui/input-otp.tsx +77 -0
  33. package/src/lib/components/ui/input.tsx +17 -15
  34. package/src/lib/components/ui/label.tsx +19 -16
  35. package/src/lib/components/ui/menubar.tsx +274 -0
  36. package/src/lib/components/ui/navigation-menu.tsx +168 -0
  37. package/src/lib/components/ui/pagination.tsx +108 -87
  38. package/src/lib/components/ui/popover.tsx +36 -28
  39. package/src/lib/components/ui/progress.tsx +29 -0
  40. package/src/lib/components/ui/radio-group.tsx +45 -0
  41. package/src/lib/components/ui/resizable.tsx +54 -0
  42. package/src/lib/components/ui/scroll-area.tsx +48 -40
  43. package/src/lib/components/ui/select.tsx +151 -129
  44. package/src/lib/components/ui/separator.tsx +22 -20
  45. package/src/lib/components/ui/sheet.tsx +110 -91
  46. package/src/lib/components/ui/sidebar.tsx +652 -622
  47. package/src/lib/components/ui/skeleton.tsx +10 -10
  48. package/src/lib/components/ui/slider.tsx +63 -0
  49. package/src/lib/components/ui/sonner.tsx +7 -11
  50. package/src/lib/components/ui/switch.tsx +27 -22
  51. package/src/lib/components/ui/table.tsx +96 -64
  52. package/src/lib/components/ui/tabs.tsx +56 -38
  53. package/src/lib/components/ui/textarea.tsx +14 -14
  54. package/src/lib/components/ui/toggle-group.tsx +73 -0
  55. package/src/lib/components/ui/toggle.tsx +45 -0
  56. package/src/lib/components/ui/tooltip.tsx +45 -37
  57. package/src/lib/framework/page/list-page.tsx +1 -1
  58. package/vite/vite-plugin-vendure-dashboard.ts +1 -1
@@ -0,0 +1,430 @@
1
+ import { useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query';
2
+ import { useDebounce } from '@uidotdev/usehooks';
3
+ import { useRef, useState } from 'react';
4
+ import { toast } from 'sonner';
5
+
6
+ import { Alert, AlertDescription } from '@/components/ui/alert.js';
7
+ import { Button } from '@/components/ui/button.js';
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from '@/components/ui/dialog.js';
16
+ import { Input } from '@/components/ui/input.js';
17
+ import { ScrollArea } from '@/components/ui/scroll-area.js';
18
+ import { api } from '@/graphql/api.js';
19
+ import { Trans, useLingui } from '@/lib/trans.js';
20
+ import { ChevronRight, Folder, FolderOpen, Search } from 'lucide-react';
21
+
22
+ import { collectionListForMoveDocument, moveCollectionDocument } from '../collections.graphql.js';
23
+
24
+ type Collection = {
25
+ id: string;
26
+ name: string;
27
+ slug: string;
28
+ children?: { id: string }[] | null;
29
+ breadcrumbs: Array<{ id: string; name: string; slug: string }>;
30
+ parentId?: string;
31
+ };
32
+
33
+ interface MoveCollectionsDialogProps {
34
+ open: boolean;
35
+ onOpenChange: (open: boolean) => void;
36
+ collectionsToMove: Collection[];
37
+ onSuccess?: () => void;
38
+ }
39
+
40
+ interface CollectionTreeNodeProps {
41
+ collection: Collection;
42
+ depth: number;
43
+ expanded: Record<string, boolean>;
44
+ onToggleExpanded: (id: string) => void;
45
+ onSelect: (collection: Collection) => void;
46
+ selectedCollectionId?: string;
47
+ collectionsToMove: Collection[];
48
+ childCollectionsByParentId: Record<string, Collection[]>;
49
+ }
50
+
51
+ interface TargetAlertProps {
52
+ selectedCollectionId?: string;
53
+ collectionsToMove: Collection[];
54
+ topLevelCollectionId?: string;
55
+ collectionNameCache: React.MutableRefObject<Map<string, string>>;
56
+ }
57
+
58
+ interface MoveToTopLevelProps {
59
+ selectedCollectionId?: string;
60
+ topLevelCollectionId?: string;
61
+ onSelect: (id?: string) => void;
62
+ }
63
+
64
+ function TargetAlert({
65
+ selectedCollectionId,
66
+ collectionsToMove,
67
+ topLevelCollectionId,
68
+ collectionNameCache,
69
+ }: Readonly<TargetAlertProps>) {
70
+ return (
71
+ <Alert className={selectedCollectionId ? 'border-blue-200 bg-blue-50' : ''}>
72
+ <Folder className="h-4 w-4" />
73
+ <AlertDescription>
74
+ {selectedCollectionId ? (
75
+ <Trans>
76
+ Moving {collectionsToMove.length} collection
77
+ {collectionsToMove.length === 1 ? '' : 's'} into{' '}
78
+ {selectedCollectionId === topLevelCollectionId
79
+ ? 'top level'
80
+ : collectionNameCache.current.get(selectedCollectionId) || 'selected collection'}
81
+ </Trans>
82
+ ) : (
83
+ <Trans>Select a destination collection</Trans>
84
+ )}
85
+ </AlertDescription>
86
+ </Alert>
87
+ );
88
+ }
89
+
90
+ function MoveToTopLevel({
91
+ selectedCollectionId,
92
+ topLevelCollectionId,
93
+ onSelect,
94
+ }: Readonly<MoveToTopLevelProps>) {
95
+ return (
96
+ <button
97
+ type="button"
98
+ className={`flex items-center gap-2 py-2 px-3 hover:bg-accent rounded-sm cursor-pointer w-full text-left ${
99
+ selectedCollectionId === topLevelCollectionId ? 'bg-accent' : ''
100
+ }`}
101
+ onClick={() => onSelect(topLevelCollectionId)}
102
+ >
103
+ <div className="w-3 h-3" />
104
+ <div className="flex items-center gap-2">
105
+ <Folder className="h-4 w-4 text-muted-foreground" />
106
+ <span className="text-sm font-medium">
107
+ <Trans>Move to the top level</Trans>
108
+ </span>
109
+ </div>
110
+ </button>
111
+ );
112
+ }
113
+
114
+ function CollectionTreeNode({
115
+ collection,
116
+ depth,
117
+ expanded,
118
+ onToggleExpanded,
119
+ onSelect,
120
+ selectedCollectionId,
121
+ collectionsToMove,
122
+ childCollectionsByParentId,
123
+ }: Readonly<CollectionTreeNodeProps>) {
124
+ const hasChildren = collection.children && collection.children.length > 0;
125
+ const isExpanded = expanded[collection.id];
126
+ const isSelected = selectedCollectionId === collection.id;
127
+ const isBeingMoved = collectionsToMove.some(c => c.id === collection.id);
128
+ const isChildOfBeingMoved = collectionsToMove.some(c => collection.breadcrumbs.some(b => b.id === c.id));
129
+
130
+ // Don't allow selecting collections that are being moved or are children of collections being moved
131
+ const isSelectable = !isBeingMoved && !isChildOfBeingMoved;
132
+
133
+ const childCollections = childCollectionsByParentId[collection.id] || [];
134
+
135
+ return (
136
+ <div className="my-0.5">
137
+ <div className="flex items-center" style={{ marginLeft: depth * 20 }}>
138
+ {hasChildren && (
139
+ <Button
140
+ size="icon"
141
+ variant="ghost"
142
+ className="h-4 w-4 p-0 mr-1"
143
+ onClick={() => onToggleExpanded(collection.id)}
144
+ >
145
+ {isExpanded ? (
146
+ <ChevronRight className="h-3 w-3 rotate-90" />
147
+ ) : (
148
+ <ChevronRight className="h-3 w-3" />
149
+ )}
150
+ </Button>
151
+ )}
152
+ {!hasChildren && <div className="w-5 h-4 mr-1" />}
153
+ <button
154
+ type="button"
155
+ className={`flex items-center gap-2 py-2 px-3 hover:bg-accent rounded-sm cursor-pointer w-full text-left ${
156
+ isSelected ? 'bg-accent' : ''
157
+ } ${!isSelectable ? 'opacity-50 cursor-not-allowed' : ''}`}
158
+ onClick={() => {
159
+ if (isSelectable) {
160
+ onSelect(collection);
161
+ }
162
+ }}
163
+ disabled={!isSelectable}
164
+ >
165
+ <div className="flex items-center gap-2">
166
+ {hasChildren &&
167
+ (isExpanded ? (
168
+ <FolderOpen className="h-4 w-4 text-muted-foreground" />
169
+ ) : (
170
+ <Folder className="h-4 w-4 text-muted-foreground" />
171
+ ))}
172
+ {!hasChildren && <div className="w-4 h-4" />}
173
+ <div className="flex flex-col">
174
+ <span className="text-sm">{collection.name}</span>
175
+ {collection.breadcrumbs.length > 1 && (
176
+ <span className="text-xs text-muted-foreground">
177
+ {collection.breadcrumbs
178
+ .slice(1)
179
+ .map(b => b.name)
180
+ .join(' / ')}
181
+ </span>
182
+ )}
183
+ </div>
184
+ </div>
185
+ </button>
186
+ </div>
187
+ {hasChildren && isExpanded && (
188
+ <div>
189
+ {childCollections.map((childCollection: Collection) => (
190
+ <CollectionTreeNode
191
+ key={childCollection.id}
192
+ collection={childCollection}
193
+ depth={depth + 1}
194
+ expanded={expanded}
195
+ onToggleExpanded={onToggleExpanded}
196
+ onSelect={onSelect}
197
+ selectedCollectionId={selectedCollectionId}
198
+ collectionsToMove={collectionsToMove}
199
+ childCollectionsByParentId={childCollectionsByParentId}
200
+ />
201
+ ))}
202
+ </div>
203
+ )}
204
+ </div>
205
+ );
206
+ }
207
+
208
+ export function MoveCollectionsDialog({
209
+ open,
210
+ onOpenChange,
211
+ collectionsToMove,
212
+ onSuccess,
213
+ }: Readonly<MoveCollectionsDialogProps>) {
214
+ const [expanded, setExpanded] = useState<Record<string, boolean>>({});
215
+ const [selectedCollectionId, setSelectedCollectionId] = useState<string>();
216
+ const [searchTerm, setSearchTerm] = useState('');
217
+ const debouncedSearchTerm = useDebounce(searchTerm, 300);
218
+ const collectionNameCache = useRef<Map<string, string>>(new Map());
219
+ const queryClient = useQueryClient();
220
+ const { i18n } = useLingui();
221
+ const collectionForMoveKey = ['collectionsForMove', debouncedSearchTerm];
222
+ const childCollectionsForMoveKey = (collectionId?: string) =>
223
+ collectionId ? ['childCollectionsForMove', collectionId] : ['childCollectionsForMove'];
224
+
225
+ const { data: collectionsData, isLoading } = useQuery({
226
+ queryKey: collectionForMoveKey,
227
+ queryFn: () =>
228
+ api.query(collectionListForMoveDocument, {
229
+ options: {
230
+ take: 100,
231
+ topLevelOnly: !debouncedSearchTerm,
232
+ ...(debouncedSearchTerm && {
233
+ filter: {
234
+ name: { contains: debouncedSearchTerm },
235
+ },
236
+ }),
237
+ },
238
+ }),
239
+ staleTime: 1000 * 60 * 5,
240
+ enabled: open,
241
+ });
242
+
243
+ const topLevelCollectionId = collectionsData?.collections.items[0]?.parentId;
244
+ const selectionHasTopLevelParent = collectionsToMove.some(c => c.parentId === topLevelCollectionId);
245
+
246
+ // Load child collections for expanded nodes
247
+ const childrenQueries = useQueries({
248
+ queries: Object.entries(expanded).map(([collectionId, isExpanded]) => {
249
+ return {
250
+ queryKey: childCollectionsForMoveKey(collectionId),
251
+ queryFn: () =>
252
+ api.query(collectionListForMoveDocument, {
253
+ options: {
254
+ filter: {
255
+ parentId: { eq: collectionId },
256
+ },
257
+ },
258
+ }),
259
+ staleTime: 1000 * 60 * 5,
260
+ };
261
+ }),
262
+ });
263
+
264
+ const childCollectionsByParentId = childrenQueries.reduce(
265
+ (acc, query, index) => {
266
+ const collectionId = Object.keys(expanded)[index];
267
+ if (query.data) {
268
+ const collections = query.data.collections.items as Collection[];
269
+ // Populate the name cache with these collections
270
+ collections.forEach(collection => {
271
+ collectionNameCache.current.set(collection.id, collection.name);
272
+ });
273
+ acc[collectionId] = collections;
274
+ }
275
+ return acc;
276
+ },
277
+ {} as Record<string, Collection[]>,
278
+ );
279
+
280
+ const moveCollectionsMutation = useMutation({
281
+ mutationFn: api.mutate(moveCollectionDocument),
282
+ onSuccess: () => {
283
+ toast.success(i18n.t('Collections moved successfully'));
284
+ queryClient.invalidateQueries({ queryKey: collectionForMoveKey });
285
+ queryClient.invalidateQueries({ queryKey: childCollectionsForMoveKey() });
286
+ onSuccess?.();
287
+ onOpenChange(false);
288
+ },
289
+ onError: error => {
290
+ toast.error(i18n.t('Failed to move collections'));
291
+ console.error('Move collections error:', error);
292
+ },
293
+ });
294
+
295
+ const handleToggleExpanded = (id: string) => {
296
+ setExpanded(prev => ({
297
+ ...prev,
298
+ [id]: !prev[id],
299
+ }));
300
+ };
301
+
302
+ const handleSelect = (collection: Collection) => {
303
+ setSelectedCollectionId(collection.id);
304
+ };
305
+
306
+ const handleMove = () => {
307
+ if (!selectedCollectionId) {
308
+ toast.error(i18n.t('Please select a target collection'));
309
+ return;
310
+ }
311
+ // Move to a specific parent using moveCollection
312
+ const movePromises = collectionsToMove.map((collection: Collection) =>
313
+ moveCollectionsMutation.mutateAsync({
314
+ input: {
315
+ collectionId: collection.id,
316
+ parentId: selectedCollectionId,
317
+ index: 0, // Move to the beginning of the target collection
318
+ },
319
+ }),
320
+ );
321
+ Promise.all(movePromises);
322
+ };
323
+
324
+ const collections = (collectionsData?.collections.items as Collection[]) || [];
325
+
326
+ // Populate the name cache with top-level collections
327
+ collections.forEach(collection => {
328
+ collectionNameCache.current.set(collection.id, collection.name);
329
+ });
330
+
331
+ return (
332
+ <Dialog open={open} onOpenChange={onOpenChange}>
333
+ <DialogContent className="sm:max-w-[600px] max-h-[80vh]">
334
+ <DialogHeader>
335
+ <DialogTitle>
336
+ <Trans>Move Collections</Trans>
337
+ </DialogTitle>
338
+ <DialogDescription>
339
+ <Trans>
340
+ Select a target collection to move{' '}
341
+ {collectionsToMove.length === 1
342
+ ? 'this collection'
343
+ : `${collectionsToMove.length} collections`}{' '}
344
+ to.
345
+ </Trans>
346
+ </DialogDescription>
347
+ </DialogHeader>
348
+ <div className="px-6 py-3 bg-muted/50 border-b">
349
+ <div className="flex flex-wrap gap-2">
350
+ {collectionsToMove.map(collection => (
351
+ <div
352
+ key={collection.id}
353
+ className="flex items-center gap-2 px-3 py-1 bg-background border rounded-md text-sm"
354
+ >
355
+ <Folder className="h-3 w-3 text-muted-foreground" />
356
+ <span>{collection.name}</span>
357
+ </div>
358
+ ))}
359
+ </div>
360
+ </div>
361
+ <div className="py-4">
362
+ <div className="px-6 pb-3">
363
+ <div className="relative mb-3">
364
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
365
+ <Input
366
+ placeholder={i18n.t('Filter by collection name')}
367
+ value={searchTerm}
368
+ onChange={e => setSearchTerm(e.target.value)}
369
+ className="pl-10"
370
+ />
371
+ </div>
372
+ <ScrollArea className="h-[400px]">
373
+ <div className="space-y-1">
374
+ {isLoading ? (
375
+ <div className="flex items-center justify-center py-8">
376
+ <Trans>Loading collections...</Trans>
377
+ </div>
378
+ ) : (
379
+ <>
380
+ {!debouncedSearchTerm && !selectionHasTopLevelParent && (
381
+ <MoveToTopLevel
382
+ selectedCollectionId={selectedCollectionId}
383
+ topLevelCollectionId={topLevelCollectionId}
384
+ onSelect={setSelectedCollectionId}
385
+ />
386
+ )}
387
+ {collections.map((collection: Collection) => (
388
+ <CollectionTreeNode
389
+ key={collection.id}
390
+ collection={collection}
391
+ depth={0}
392
+ expanded={expanded}
393
+ onToggleExpanded={handleToggleExpanded}
394
+ onSelect={handleSelect}
395
+ selectedCollectionId={selectedCollectionId}
396
+ collectionsToMove={collectionsToMove}
397
+ childCollectionsByParentId={childCollectionsByParentId}
398
+ />
399
+ ))}
400
+ </>
401
+ )}
402
+ </div>
403
+ </ScrollArea>
404
+ <TargetAlert
405
+ selectedCollectionId={selectedCollectionId}
406
+ collectionsToMove={collectionsToMove}
407
+ topLevelCollectionId={topLevelCollectionId}
408
+ collectionNameCache={collectionNameCache}
409
+ />
410
+ </div>
411
+ </div>
412
+ <DialogFooter>
413
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
414
+ <Trans>Cancel</Trans>
415
+ </Button>
416
+ <Button
417
+ onClick={handleMove}
418
+ disabled={!selectedCollectionId || moveCollectionsMutation.isPending}
419
+ >
420
+ {moveCollectionsMutation.isPending ? (
421
+ <Trans>Moving...</Trans>
422
+ ) : (
423
+ <Trans>Move Collections</Trans>
424
+ )}
425
+ </Button>
426
+ </DialogFooter>
427
+ </DialogContent>
428
+ </Dialog>
429
+ );
430
+ }
@@ -0,0 +1,33 @@
1
+ import { ResultOf } from 'gql.tada';
2
+ import { useState } from 'react';
3
+
4
+ import { collectionListDocument } from '../collections.graphql.js';
5
+ import { MoveCollectionsDialog } from './move-collections-dialog.js';
6
+
7
+ type Collection = ResultOf<typeof collectionListDocument>['collections']['items'][number];
8
+
9
+ export function useMoveSingleCollection() {
10
+ const [moveDialogOpen, setMoveDialogOpen] = useState(false);
11
+ const [collectionsToMove, setCollectionsToMove] = useState<Collection[]>([]);
12
+
13
+ const handleMoveClick = (collection: Collection) => {
14
+ setCollectionsToMove([collection]);
15
+ setMoveDialogOpen(true);
16
+ };
17
+
18
+ const MoveDialog = () => (
19
+ <MoveCollectionsDialog
20
+ open={moveDialogOpen}
21
+ onOpenChange={setMoveDialogOpen}
22
+ collectionsToMove={collectionsToMove}
23
+ onSuccess={() => {
24
+ // The dialog will handle invalidating queries internally
25
+ }}
26
+ />
27
+ );
28
+
29
+ return {
30
+ handleMoveClick,
31
+ MoveDialog,
32
+ };
33
+ }
@@ -11,6 +11,7 @@ import {
11
11
  DialogTrigger,
12
12
  } from '@/vdb/components/ui/dialog.js';
13
13
  import { api } from '@/vdb/graphql/api.js';
14
+ import { Button } from '@/vdb/index.js';
14
15
  import { Trans, useLingui } from '@/vdb/lib/trans.js';
15
16
  import { useMutation } from '@tanstack/react-query';
16
17
  import { EditIcon, TrashIcon } from 'lucide-react';
@@ -120,8 +121,10 @@ export function CustomerAddressCard({
120
121
  <div className="flex gap-4 mt-3 pt-3 border-t border-border">
121
122
  {editable && (
122
123
  <Dialog open={open} onOpenChange={setOpen}>
123
- <DialogTrigger>
124
- <EditIcon className="w-4 h-4" />
124
+ <DialogTrigger asChild>
125
+ <Button size="icon" variant="secondary">
126
+ <EditIcon />
127
+ </Button>
125
128
  </DialogTrigger>
126
129
  <DialogContent>
127
130
  <DialogHeader>
@@ -145,7 +148,9 @@ export function CustomerAddressCard({
145
148
  onDelete?.();
146
149
  }}
147
150
  >
148
- <TrashIcon className="w-4 h-4 text-destructive" />
151
+ <Button size="icon" variant="destructive">
152
+ <TrashIcon />
153
+ </Button>
149
154
  </ConfirmationDialog>
150
155
  )}
151
156
  </div>
@@ -81,7 +81,7 @@ export function DataTable<TData>({
81
81
  bulkActions,
82
82
  setTableOptions,
83
83
  onRefresh,
84
- }: DataTableProps<TData>) {
84
+ }: Readonly<DataTableProps<TData>>) {
85
85
  const [sorting, setSorting] = React.useState<SortingState>(sortingInitialState || []);
86
86
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(filtersInitialState || []);
87
87
  const { activeChannel } = useChannel();
@@ -273,7 +273,7 @@ export function PaginatedListDataTable<
273
273
  setTableOptions,
274
274
  transformData,
275
275
  registerRefresher,
276
- }: PaginatedListDataTableProps<T, U, V, AC>) {
276
+ }: Readonly<PaginatedListDataTableProps<T, U, V, AC>>) {
277
277
  const [searchTerm, setSearchTerm] = React.useState<string>('');
278
278
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
279
279
  const queryClient = useQueryClient();
@@ -1,59 +1,64 @@
1
- import * as AccordionPrimitive from '@radix-ui/react-accordion';
2
- import { ChevronDownIcon } from 'lucide-react';
3
- import * as React from 'react';
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDownIcon } from "lucide-react"
4
4
 
5
- import { cn } from '@/vdb/lib/utils.js';
5
+ import { cn } from "@/vdb/lib/utils"
6
6
 
7
- function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
8
- return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
7
+ function Accordion({
8
+ ...props
9
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
10
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />
9
11
  }
10
12
 
11
- function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
12
- return (
13
- <AccordionPrimitive.Item
14
- data-slot="accordion-item"
15
- className={cn('border-b last:border-b-0', className)}
16
- {...props}
17
- />
18
- );
13
+ function AccordionItem({
14
+ className,
15
+ ...props
16
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
17
+ return (
18
+ <AccordionPrimitive.Item
19
+ data-slot="accordion-item"
20
+ className={cn("border-b last:border-b-0", className)}
21
+ {...props}
22
+ />
23
+ )
19
24
  }
20
25
 
21
26
  function AccordionTrigger({
22
- className,
23
- children,
24
- ...props
27
+ className,
28
+ children,
29
+ ...props
25
30
  }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
26
- return (
27
- <AccordionPrimitive.Header className="flex">
28
- <AccordionPrimitive.Trigger
29
- data-slot="accordion-trigger"
30
- className={cn(
31
- 'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
32
- className,
33
- )}
34
- {...props}
35
- >
36
- {children}
37
- <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
38
- </AccordionPrimitive.Trigger>
39
- </AccordionPrimitive.Header>
40
- );
31
+ return (
32
+ <AccordionPrimitive.Header className="flex">
33
+ <AccordionPrimitive.Trigger
34
+ data-slot="accordion-trigger"
35
+ className={cn(
36
+ "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
37
+ className
38
+ )}
39
+ {...props}
40
+ >
41
+ {children}
42
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
43
+ </AccordionPrimitive.Trigger>
44
+ </AccordionPrimitive.Header>
45
+ )
41
46
  }
42
47
 
43
48
  function AccordionContent({
44
- className,
45
- children,
46
- ...props
49
+ className,
50
+ children,
51
+ ...props
47
52
  }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
48
- return (
49
- <AccordionPrimitive.Content
50
- data-slot="accordion-content"
51
- className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
52
- {...props}
53
- >
54
- <div className={cn('pt-0 pb-4', className)}>{children}</div>
55
- </AccordionPrimitive.Content>
56
- );
53
+ return (
54
+ <AccordionPrimitive.Content
55
+ data-slot="accordion-content"
56
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
57
+ {...props}
58
+ >
59
+ <div className={cn("pt-0 pb-4", className)}>{children}</div>
60
+ </AccordionPrimitive.Content>
61
+ )
57
62
  }
58
63
 
59
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
64
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }