metabinaries 1.3.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.
@@ -0,0 +1,1025 @@
1
+ // UI Components Part 4 - Checkbox, Collapsible, Command, Dialog, Drawer, DropdownMenu, Popover, ScrollArea, Select, Separator, Sheet
2
+
3
+ export const uiTemplates4 = {
4
+ 'components/ui/checkbox.tsx': `'use client';
5
+
6
+ import * as React from 'react';
7
+ import { Check } from 'lucide-react';
8
+
9
+ import { cn } from '@/lib/utils';
10
+
11
+ export interface CheckboxProps extends Omit<
12
+ React.InputHTMLAttributes<HTMLInputElement>,
13
+ 'onChange'
14
+ > {
15
+ onCheckedChange?: (checked: boolean) => void;
16
+ }
17
+
18
+ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
19
+ ({ className, checked, onCheckedChange, ...props }, ref) => {
20
+ return (
21
+ <button
22
+ type='button'
23
+ role='checkbox'
24
+ aria-checked={checked}
25
+ onClick={() => onCheckedChange?.(!checked)}
26
+ className={cn(
27
+ 'h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background',
28
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
29
+ 'disabled:cursor-not-allowed disabled:opacity-50',
30
+ 'flex items-center justify-center transition-colors',
31
+ checked && 'bg-primary text-primary-foreground',
32
+ className
33
+ )}
34
+ {...(props as React.ButtonHTMLAttributes<HTMLButtonElement>)}
35
+ >
36
+ {checked && <Check className='h-3 w-3' />}
37
+ </button>
38
+ );
39
+ }
40
+ );
41
+ Checkbox.displayName = 'Checkbox';
42
+
43
+ export { Checkbox };`,
44
+
45
+ 'components/ui/collapsible.tsx': `'use client';
46
+
47
+ import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
48
+
49
+ function Collapsible({
50
+ ...props
51
+ }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
52
+ return <CollapsiblePrimitive.Root data-slot='collapsible' {...props} />;
53
+ }
54
+
55
+ function CollapsibleTrigger({
56
+ ...props
57
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
58
+ return (
59
+ <CollapsiblePrimitive.CollapsibleTrigger
60
+ data-slot='collapsible-trigger'
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+
66
+ function CollapsibleContent({
67
+ ...props
68
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
69
+ return (
70
+ <CollapsiblePrimitive.CollapsibleContent
71
+ data-slot='collapsible-content'
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+
77
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };`,
78
+
79
+ 'components/ui/popover.tsx': `'use client';
80
+
81
+ import * as React from 'react';
82
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
83
+
84
+ import { cn } from '@/lib/utils';
85
+
86
+ function Popover({
87
+ ...props
88
+ }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
89
+ return <PopoverPrimitive.Root data-slot='popover' {...props} />;
90
+ }
91
+
92
+ function PopoverTrigger({
93
+ ...props
94
+ }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
95
+ return <PopoverPrimitive.Trigger data-slot='popover-trigger' {...props} />;
96
+ }
97
+
98
+ function PopoverContent({
99
+ className,
100
+ align = 'center',
101
+ sideOffset = 4,
102
+ ...props
103
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
104
+ return (
105
+ <PopoverPrimitive.Portal>
106
+ <PopoverPrimitive.Content
107
+ data-slot='popover-content'
108
+ align={align}
109
+ sideOffset={sideOffset}
110
+ className={cn(
111
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 w-72 rounded-md border p-4 shadow-md outline-hidden',
112
+ className
113
+ )}
114
+ {...props}
115
+ />
116
+ </PopoverPrimitive.Portal>
117
+ );
118
+ }
119
+
120
+ function PopoverAnchor({
121
+ ...props
122
+ }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
123
+ return <PopoverPrimitive.Anchor data-slot='popover-anchor' {...props} />;
124
+ }
125
+
126
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };`,
127
+
128
+ 'components/ui/scroll-area.tsx': `'use client';
129
+
130
+ import * as React from 'react';
131
+ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
132
+
133
+ import { cn } from '@/lib/utils';
134
+
135
+ function ScrollArea({
136
+ className,
137
+ children,
138
+ ...props
139
+ }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
140
+ return (
141
+ <ScrollAreaPrimitive.Root
142
+ data-slot='scroll-area'
143
+ className={cn('relative overflow-hidden', className)}
144
+ {...props}
145
+ >
146
+ <ScrollAreaPrimitive.Viewport
147
+ data-slot='scroll-area-viewport'
148
+ className='h-full w-full rounded-[inherit]'
149
+ >
150
+ {children}
151
+ </ScrollAreaPrimitive.Viewport>
152
+ <ScrollBar />
153
+ <ScrollAreaPrimitive.Corner />
154
+ </ScrollAreaPrimitive.Root>
155
+ );
156
+ }
157
+
158
+ function ScrollBar({
159
+ className,
160
+ orientation = 'vertical',
161
+ ...props
162
+ }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
163
+ return (
164
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
165
+ data-slot='scroll-area-scrollbar'
166
+ orientation={orientation}
167
+ className={cn(
168
+ 'flex touch-none transition-colors select-none',
169
+ orientation === 'vertical' &&
170
+ 'h-full w-2.5 border-l border-l-transparent p-[1px]',
171
+ orientation === 'horizontal' &&
172
+ 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
173
+ className
174
+ )}
175
+ {...props}
176
+ >
177
+ <ScrollAreaPrimitive.ScrollAreaThumb
178
+ data-slot='scroll-area-thumb'
179
+ className='bg-border relative flex-1 rounded-full'
180
+ />
181
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
182
+ );
183
+ }
184
+
185
+ export { ScrollArea, ScrollBar };`,
186
+
187
+ 'components/ui/separator.tsx': `'use client';
188
+
189
+ import * as React from 'react';
190
+ import * as SeparatorPrimitive from '@radix-ui/react-separator';
191
+
192
+ import { cn } from '@/lib/utils';
193
+
194
+ function Separator({
195
+ className,
196
+ orientation = 'horizontal',
197
+ decorative = true,
198
+ ...props
199
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
200
+ return (
201
+ <SeparatorPrimitive.Root
202
+ data-slot='separator-root'
203
+ decorative={decorative}
204
+ orientation={orientation}
205
+ className={cn(
206
+ 'bg-border shrink-0',
207
+ orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
208
+ className
209
+ )}
210
+ {...props}
211
+ />
212
+ );
213
+ }
214
+
215
+ export { Separator };`,
216
+
217
+ 'components/ui/dialog.tsx': `'use client';
218
+
219
+ import * as React from 'react';
220
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
221
+ import { XIcon } from 'lucide-react';
222
+
223
+ import { cn } from '@/lib/utils';
224
+
225
+ function Dialog({
226
+ ...props
227
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
228
+ return <DialogPrimitive.Root data-slot='dialog' {...props} />;
229
+ }
230
+
231
+ function DialogTrigger({
232
+ ...props
233
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
234
+ return <DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />;
235
+ }
236
+
237
+ function DialogPortal({
238
+ ...props
239
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
240
+ return <DialogPrimitive.Portal data-slot='dialog-portal' {...props} />;
241
+ }
242
+
243
+ function DialogClose({
244
+ ...props
245
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
246
+ return <DialogPrimitive.Close data-slot='dialog-close' {...props} />;
247
+ }
248
+
249
+ function DialogOverlay({
250
+ className,
251
+ ...props
252
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
253
+ return (
254
+ <DialogPrimitive.Overlay
255
+ data-slot='dialog-overlay'
256
+ className={cn(
257
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
258
+ className
259
+ )}
260
+ {...props}
261
+ />
262
+ );
263
+ }
264
+
265
+ function DialogContent({
266
+ className,
267
+ children,
268
+ ...props
269
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
270
+ return (
271
+ <DialogPortal>
272
+ <DialogOverlay />
273
+ <DialogPrimitive.Content
274
+ data-slot='dialog-content'
275
+ className={cn(
276
+ 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
277
+ className
278
+ )}
279
+ {...props}
280
+ >
281
+ {children}
282
+ <DialogPrimitive.Close className='ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none [&_svg]:size-4'>
283
+ <XIcon />
284
+ <span className='sr-only'>Close</span>
285
+ </DialogPrimitive.Close>
286
+ </DialogPrimitive.Content>
287
+ </DialogPortal>
288
+ );
289
+ }
290
+
291
+ function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
292
+ return (
293
+ <div
294
+ data-slot='dialog-header'
295
+ className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
296
+ {...props}
297
+ />
298
+ );
299
+ }
300
+
301
+ function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
302
+ return (
303
+ <div
304
+ data-slot='dialog-footer'
305
+ className={cn(
306
+ 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
307
+ className
308
+ )}
309
+ {...props}
310
+ />
311
+ );
312
+ }
313
+
314
+ function DialogTitle({
315
+ className,
316
+ ...props
317
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
318
+ return (
319
+ <DialogPrimitive.Title
320
+ data-slot='dialog-title'
321
+ className={cn('text-lg font-semibold leading-none tracking-tight', className)}
322
+ {...props}
323
+ />
324
+ );
325
+ }
326
+
327
+ function DialogDescription({
328
+ className,
329
+ ...props
330
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
331
+ return (
332
+ <DialogPrimitive.Description
333
+ data-slot='dialog-description'
334
+ className={cn('text-muted-foreground text-sm', className)}
335
+ {...props}
336
+ />
337
+ );
338
+ }
339
+
340
+ export {
341
+ Dialog,
342
+ DialogPortal,
343
+ DialogOverlay,
344
+ DialogTrigger,
345
+ DialogClose,
346
+ DialogContent,
347
+ DialogHeader,
348
+ DialogFooter,
349
+ DialogTitle,
350
+ DialogDescription,
351
+ };`,
352
+
353
+ 'components/ui/confirmation-dialog.tsx': `'use client';
354
+
355
+ import * as React from 'react';
356
+ import {
357
+ AlertTriangle,
358
+ Trash2,
359
+ Edit,
360
+ Info,
361
+ CheckCircle,
362
+ Loader2,
363
+ } from 'lucide-react';
364
+ import {
365
+ Dialog,
366
+ DialogContent,
367
+ DialogDescription,
368
+ DialogFooter,
369
+ DialogHeader,
370
+ DialogTitle,
371
+ } from '@/components/ui/dialog';
372
+ import { Button } from '@/components/ui/button';
373
+ import { Input } from '@/components/ui/input';
374
+ import { Label } from '@/components/ui/label';
375
+ import { cn } from '@/lib/utils';
376
+
377
+ // Confirmation dialog variants
378
+ export type ConfirmVariant = 'danger' | 'warning' | 'info' | 'success';
379
+
380
+ // Variant configurations
381
+ const variantConfig = {
382
+ danger: {
383
+ icon: Trash2,
384
+ iconColor: 'text-red-500',
385
+ iconBg: 'bg-red-50 dark:bg-red-900/20',
386
+ confirmButton: 'destructive',
387
+ },
388
+ warning: {
389
+ icon: AlertTriangle,
390
+ iconColor: 'text-amber-500',
391
+ iconBg: 'bg-amber-50 dark:bg-amber-900/20',
392
+ confirmButton: 'default',
393
+ },
394
+ info: {
395
+ icon: Info,
396
+ iconColor: 'text-blue-500',
397
+ iconBg: 'bg-blue-50 dark:bg-blue-900/20',
398
+ confirmButton: 'default',
399
+ },
400
+ success: {
401
+ icon: CheckCircle,
402
+ iconColor: 'text-green-500',
403
+ iconBg: 'bg-green-50 dark:bg-green-900/20',
404
+ confirmButton: 'default',
405
+ },
406
+ };
407
+
408
+ export interface ConfirmationDialogProps {
409
+ /** Whether the dialog is open */
410
+ open: boolean;
411
+ /** Callback when open state changes */
412
+ onOpenChange: (open: boolean) => void;
413
+ /** Dialog title */
414
+ title: string;
415
+ /** Dialog description/message */
416
+ description: string;
417
+ /** Confirm button text */
418
+ confirmText?: string;
419
+ /** Cancel button text */
420
+ cancelText?: string;
421
+ /** Optional text that must be typed to enable confirmation */
422
+ requiredConfirmationText?: string;
423
+ /** Label for the confirmation input */
424
+ confirmationInputLabel?: string;
425
+ /** Callback when confirmed */
426
+ onConfirm: () => void | Promise<void>;
427
+ /** Callback when cancelled */
428
+ onCancel?: () => void;
429
+ /** Dialog variant - affects styling */
430
+ variant?: ConfirmVariant;
431
+ /** Whether the confirm action is loading */
432
+ isLoading?: boolean;
433
+ /** Custom icon component */
434
+ icon?: React.ComponentType<{ className?: string }>;
435
+ /** Whether to show the icon */
436
+ showIcon?: boolean;
437
+ }
438
+
439
+ export function ConfirmationDialog({
440
+ open,
441
+ onOpenChange,
442
+ title,
443
+ description,
444
+ confirmText = 'Confirm',
445
+ cancelText = 'Cancel',
446
+ requiredConfirmationText,
447
+ confirmationInputLabel,
448
+ onConfirm,
449
+ onCancel,
450
+ variant = 'danger',
451
+ isLoading = false,
452
+ icon,
453
+ showIcon = true,
454
+ }: ConfirmationDialogProps) {
455
+ const [inputValue, setInputValue] = React.useState('');
456
+ const config = variantConfig[variant];
457
+ const IconComponent = icon || config.icon;
458
+
459
+ const isConfirmDisabled =
460
+ isLoading ||
461
+ (!!requiredConfirmationText &&
462
+ inputValue.toLowerCase() !== requiredConfirmationText.toLowerCase());
463
+
464
+ // Reset input when dialog opens/closes
465
+ React.useEffect(() => {
466
+ if (!open) {
467
+ setInputValue('');
468
+ }
469
+ }, [open]);
470
+
471
+ const handleConfirm = async () => {
472
+ if (isConfirmDisabled) return;
473
+ await onConfirm();
474
+ onOpenChange(false);
475
+ };
476
+
477
+ const handleCancel = () => {
478
+ onCancel?.();
479
+ onOpenChange(false);
480
+ };
481
+
482
+ return (
483
+ <Dialog open={open} onOpenChange={onOpenChange}>
484
+ <DialogContent className='sm:max-w-[420px] p-0 overflow-hidden border-zinc-200 dark:border-zinc-800 shadow-2xl'>
485
+ <div className='p-6 pt-8 space-y-6'>
486
+ <DialogHeader className='flex flex-col items-center text-center sm:text-center space-y-4'>
487
+ {showIcon && (
488
+ <div
489
+ className={cn(
490
+ 'rounded-2xl p-4 w-fit mx-auto transition-colors duration-300',
491
+ config.iconBg
492
+ )}
493
+ >
494
+ <IconComponent className={cn('size-8', config.iconColor)} />
495
+ </div>
496
+ )}
497
+ <div className='space-y-2'>
498
+ <DialogTitle className='text-xl font-bold tracking-tight text-center'>
499
+ {title}
500
+ </DialogTitle>
501
+ <DialogDescription className='text-zinc-500 dark:text-zinc-400 text-sm leading-relaxed max-w-[320px] mx-auto text-center'>
502
+ {description}
503
+ </DialogDescription>
504
+ </div>
505
+ </DialogHeader>
506
+
507
+ {requiredConfirmationText && (
508
+ <div className='space-y-3 px-2'>
509
+ <Label className='text-xs font-semibold tracking-wider text-zinc-500 dark:text-zinc-400'>
510
+ {confirmationInputLabel ||
511
+ \`Type "\${requiredConfirmationText}" to confirm\`}
512
+ </Label>
513
+ <Input
514
+ value={inputValue}
515
+ onChange={e => setInputValue(e.target.value)}
516
+ placeholder={requiredConfirmationText}
517
+ className='h-12 text-center font-medium border-2 focus-visible:ring-0 focus-visible:border-black dark:focus-visible:border-white transition-all'
518
+ autoFocus
519
+ />
520
+ </div>
521
+ )}
522
+
523
+ <DialogFooter className='flex flex-col sm:flex-row gap-3 sm:justify-stretch mt-2'>
524
+ <Button
525
+ variant='outline'
526
+ onClick={handleCancel}
527
+ disabled={isLoading}
528
+ className='flex-1 h-11 border-zinc-200 dark:border-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-900 transition-all'
529
+ >
530
+ {cancelText}
531
+ </Button>
532
+ <Button
533
+ variant={variant === 'danger' ? 'destructive' : 'default'}
534
+ onClick={handleConfirm}
535
+ disabled={isConfirmDisabled}
536
+ className={cn(
537
+ 'flex-1 h-11 font-semibold transition-all',
538
+ variant === 'danger' &&
539
+ !isConfirmDisabled &&
540
+ 'bg-red-600 hover:bg-red-700'
541
+ )}
542
+ >
543
+ {isLoading ? (
544
+ <div className='flex items-center gap-2'>
545
+ <Loader2 className='size-4 animate-spin' />
546
+ <span>Processing...</span>
547
+ </div>
548
+ ) : (
549
+ confirmText
550
+ )}
551
+ </Button>
552
+ </DialogFooter>
553
+ </div>
554
+ </DialogContent>
555
+ </Dialog>
556
+ );
557
+ }
558
+
559
+ // ============================================
560
+ // Preset Confirmation Dialogs
561
+ // ============================================
562
+
563
+ export interface DeleteConfirmationProps {
564
+ open: boolean;
565
+ onOpenChange: (open: boolean) => void;
566
+ onConfirm: () => void | Promise<void>;
567
+ itemName?: string;
568
+ isLoading?: boolean;
569
+ /** Optional text that must be typed to enable deletion (e.g. 'DELETE') */
570
+ requiredConfirmationText?: string;
571
+ }
572
+
573
+ /**
574
+ * Delete Confirmation Dialog
575
+ * Pre-configured for delete actions
576
+ */
577
+ export function DeleteConfirmation({
578
+ open,
579
+ onOpenChange,
580
+ onConfirm,
581
+ itemName = 'this item',
582
+ isLoading,
583
+ requiredConfirmationText,
584
+ }: DeleteConfirmationProps) {
585
+ return (
586
+ <ConfirmationDialog
587
+ open={open}
588
+ onOpenChange={onOpenChange}
589
+ title='Delete Confirmation'
590
+ description={\`Are you sure you want to delete \${itemName}? This action cannot be undone.\`}
591
+ confirmText='Delete'
592
+ cancelText='Cancel'
593
+ requiredConfirmationText={requiredConfirmationText}
594
+ onConfirm={onConfirm}
595
+ variant='danger'
596
+ isLoading={isLoading}
597
+ icon={Trash2}
598
+ />
599
+ );
600
+ }
601
+
602
+ export interface EditConfirmationProps {
603
+ open: boolean;
604
+ onOpenChange: (open: boolean) => void;
605
+ onConfirm: () => void | Promise<void>;
606
+ message?: string;
607
+ isLoading?: boolean;
608
+ }
609
+
610
+ /**
611
+ * Edit Confirmation Dialog
612
+ * Pre-configured for confirming edits/changes
613
+ */
614
+ export function EditConfirmation({
615
+ open,
616
+ onOpenChange,
617
+ onConfirm,
618
+ message = 'Are you sure you want to save these changes?',
619
+ isLoading,
620
+ }: EditConfirmationProps) {
621
+ return (
622
+ <ConfirmationDialog
623
+ open={open}
624
+ onOpenChange={onOpenChange}
625
+ title='Confirm Changes'
626
+ description={message}
627
+ confirmText='Save Changes'
628
+ cancelText='Cancel'
629
+ onConfirm={onConfirm}
630
+ variant='warning'
631
+ isLoading={isLoading}
632
+ icon={Edit}
633
+ />
634
+ );
635
+ }
636
+
637
+ export interface UnsavedChangesConfirmationProps {
638
+ open: boolean;
639
+ onOpenChange: (open: boolean) => void;
640
+ onConfirm: () => void | Promise<void>;
641
+ onCancel?: () => void;
642
+ }
643
+
644
+ /**
645
+ * Unsaved Changes Confirmation Dialog
646
+ * Used when navigating away with unsaved changes
647
+ */
648
+ export function UnsavedChangesConfirmation({
649
+ open,
650
+ onOpenChange,
651
+ onConfirm,
652
+ onCancel,
653
+ }: UnsavedChangesConfirmationProps) {
654
+ return (
655
+ <ConfirmationDialog
656
+ open={open}
657
+ onOpenChange={onOpenChange}
658
+ title='Unsaved Changes'
659
+ description='You have unsaved changes. Are you sure you want to leave? Your changes will be lost.'
660
+ confirmText='Leave'
661
+ cancelText='Stay'
662
+ onConfirm={onConfirm}
663
+ onCancel={onCancel}
664
+ variant='warning'
665
+ icon={AlertTriangle}
666
+ />
667
+ );
668
+ }
669
+
670
+ // ============================================
671
+ // Hook for easier usage
672
+ // ============================================
673
+
674
+ export interface UseConfirmationReturn {
675
+ isOpen: boolean;
676
+ open: () => void;
677
+ close: () => void;
678
+ confirm: () => Promise<boolean>;
679
+ ConfirmDialog: React.FC<
680
+ Omit<ConfirmationDialogProps, 'open' | 'onOpenChange' | 'onConfirm'>
681
+ >;
682
+ }
683
+
684
+ /**
685
+ * Hook for managing confirmation dialog state
686
+ *
687
+ * @example
688
+ * \`\`\`tsx
689
+ * const { confirm, ConfirmDialog } = useConfirmation();
690
+ *
691
+ * const handleDelete = async () => {
692
+ * const confirmed = await confirm();
693
+ * if (confirmed) {
694
+ * await deleteItem();
695
+ * }
696
+ * };
697
+ *
698
+ * return (
699
+ * <>
700
+ * <button onClick={handleDelete}>Delete</button>
701
+ * <ConfirmDialog
702
+ * title="Delete Item"
703
+ * description="Are you sure?"
704
+ * variant="danger"
705
+ * />
706
+ * </>
707
+ * );
708
+ * \`\`\`
709
+ */
710
+ export function useConfirmation(): UseConfirmationReturn {
711
+ const [isOpen, setIsOpen] = React.useState(false);
712
+ const resolveRef = React.useRef<(value: boolean) => void>(() => {
713
+ return;
714
+ });
715
+
716
+ const open = React.useCallback(() => setIsOpen(true), []);
717
+ const close = React.useCallback(() => setIsOpen(false), []);
718
+
719
+ const confirm = React.useCallback(() => {
720
+ setIsOpen(true);
721
+ return new Promise<boolean>(resolve => {
722
+ resolveRef.current = resolve;
723
+ });
724
+ }, []);
725
+
726
+ const handleConfirm = React.useCallback(() => {
727
+ resolveRef.current?.(true);
728
+ setIsOpen(false);
729
+ }, []);
730
+
731
+ const handleCancel = React.useCallback(() => {
732
+ resolveRef.current?.(false);
733
+ setIsOpen(false);
734
+ }, []);
735
+
736
+ const ConfirmDialog: React.FC<
737
+ Omit<ConfirmationDialogProps, 'open' | 'onOpenChange' | 'onConfirm'>
738
+ > = React.useCallback(
739
+ props => (
740
+ <ConfirmationDialog
741
+ {...props}
742
+ open={isOpen}
743
+ onOpenChange={open => {
744
+ if (!open) handleCancel();
745
+ }}
746
+ onConfirm={handleConfirm}
747
+ onCancel={handleCancel}
748
+ />
749
+ ),
750
+ [isOpen, handleConfirm, handleCancel]
751
+ );
752
+
753
+ return {
754
+ isOpen,
755
+ open,
756
+ close,
757
+ confirm,
758
+ ConfirmDialog,
759
+ };
760
+ }`,
761
+
762
+
763
+ 'components/ui/dropdown-menu.tsx': `'use client';
764
+
765
+ import * as React from 'react';
766
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
767
+ import {
768
+ CheckIcon,
769
+ ChevronRightIcon,
770
+ CircleIcon,
771
+ } from 'lucide-react';
772
+
773
+ import { cn } from '@/lib/utils';
774
+
775
+ function DropdownMenu({
776
+ ...props
777
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
778
+ return <DropdownMenuPrimitive.Root data-slot='dropdown-menu' {...props} />;
779
+ }
780
+
781
+ function DropdownMenuPortal({
782
+ ...props
783
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
784
+ return (
785
+ <DropdownMenuPrimitive.Portal data-slot='dropdown-menu-portal' {...props} />
786
+ );
787
+ }
788
+
789
+ function DropdownMenuTrigger({
790
+ ...props
791
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
792
+ return (
793
+ <DropdownMenuPrimitive.Trigger
794
+ data-slot='dropdown-menu-trigger'
795
+ {...props}
796
+ />
797
+ );
798
+ }
799
+
800
+ function DropdownMenuContent({
801
+ className,
802
+ sideOffset = 4,
803
+ ...props
804
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
805
+ return (
806
+ <DropdownMenuPrimitive.Portal>
807
+ <DropdownMenuPrimitive.Content
808
+ data-slot='dropdown-menu-content'
809
+ sideOffset={sideOffset}
810
+ className={cn(
811
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 min-w-32 rounded-md border p-1 shadow-md',
812
+ className
813
+ )}
814
+ {...props}
815
+ />
816
+ </DropdownMenuPrimitive.Portal>
817
+ );
818
+ }
819
+
820
+ function DropdownMenuItem({
821
+ className,
822
+ inset,
823
+ variant = 'default',
824
+ ...props
825
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
826
+ inset?: boolean;
827
+ variant?: 'default' | 'destructive';
828
+ }) {
829
+ return (
830
+ <DropdownMenuPrimitive.Item
831
+ data-slot='dropdown-menu-item'
832
+ data-inset={inset}
833
+ data-variant={variant}
834
+ className={cn(
835
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset=true]:pl-8",
836
+ className
837
+ )}
838
+ {...props}
839
+ />
840
+ );
841
+ }
842
+
843
+ function DropdownMenuCheckboxItem({
844
+ className,
845
+ children,
846
+ checked,
847
+ ...props
848
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
849
+ return (
850
+ <DropdownMenuPrimitive.CheckboxItem
851
+ data-slot='dropdown-menu-checkbox-item'
852
+ className={cn(
853
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4",
854
+ className
855
+ )}
856
+ checked={checked}
857
+ {...props}
858
+ >
859
+ <span className='absolute left-2 flex size-3.5 items-center justify-center'>
860
+ <DropdownMenuPrimitive.ItemIndicator>
861
+ <CheckIcon className='size-4' />
862
+ </DropdownMenuPrimitive.ItemIndicator>
863
+ </span>
864
+ {children}
865
+ </DropdownMenuPrimitive.CheckboxItem>
866
+ );
867
+ }
868
+
869
+ function DropdownMenuRadioGroup({
870
+ ...props
871
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
872
+ return (
873
+ <DropdownMenuPrimitive.RadioGroup
874
+ data-slot='dropdown-menu-radio-group'
875
+ {...props}
876
+ />
877
+ );
878
+ }
879
+
880
+ function DropdownMenuRadioItem({
881
+ className,
882
+ children,
883
+ ...props
884
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
885
+ return (
886
+ <DropdownMenuPrimitive.RadioItem
887
+ data-slot='dropdown-menu-radio-item'
888
+ className={cn(
889
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4",
890
+ className
891
+ )}
892
+ {...props}
893
+ >
894
+ <span className='absolute left-2 flex size-3.5 items-center justify-center'>
895
+ <DropdownMenuPrimitive.ItemIndicator>
896
+ <CircleIcon className='fill-current size-2' />
897
+ </DropdownMenuPrimitive.ItemIndicator>
898
+ </span>
899
+ {children}
900
+ </DropdownMenuPrimitive.RadioItem>
901
+ );
902
+ }
903
+
904
+ function DropdownMenuLabel({
905
+ className,
906
+ inset,
907
+ ...props
908
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
909
+ inset?: boolean;
910
+ }) {
911
+ return (
912
+ <DropdownMenuPrimitive.Label
913
+ data-slot='dropdown-menu-label'
914
+ data-inset={inset}
915
+ className={cn(
916
+ 'px-2 py-1.5 text-sm font-semibold data-[inset=true]:pl-8',
917
+ className
918
+ )}
919
+ {...props}
920
+ />
921
+ );
922
+ }
923
+
924
+ function DropdownMenuSeparator({
925
+ className,
926
+ ...props
927
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
928
+ return (
929
+ <DropdownMenuPrimitive.Separator
930
+ data-slot='dropdown-menu-separator'
931
+ className={cn('bg-border -mx-1 my-1 h-px', className)}
932
+ {...props}
933
+ />
934
+ );
935
+ }
936
+
937
+ function DropdownMenuShortcut({
938
+ className,
939
+ ...props
940
+ }: React.ComponentProps<'span'>) {
941
+ return (
942
+ <span
943
+ data-slot='dropdown-menu-shortcut'
944
+ className={cn(
945
+ 'text-muted-foreground ml-auto text-xs tracking-widest opacity-60',
946
+ className
947
+ )}
948
+ {...props}
949
+ />
950
+ );
951
+ }
952
+
953
+ function DropdownMenuSub({
954
+ ...props
955
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
956
+ return <DropdownMenuPrimitive.Sub data-slot='dropdown-menu-sub' {...props} />;
957
+ }
958
+
959
+ function DropdownMenuSubTrigger({
960
+ className,
961
+ inset,
962
+ children,
963
+ ...props
964
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
965
+ inset?: boolean;
966
+ }) {
967
+ return (
968
+ <DropdownMenuPrimitive.SubTrigger
969
+ data-slot='dropdown-menu-sub-trigger'
970
+ data-inset={inset}
971
+ className={cn(
972
+ 'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset=true]:pl-8',
973
+ className
974
+ )}
975
+ {...props}
976
+ >
977
+ {children}
978
+ <ChevronRightIcon className='ml-auto size-4' />
979
+ </DropdownMenuPrimitive.SubTrigger>
980
+ );
981
+ }
982
+
983
+ function DropdownMenuSubContent({
984
+ className,
985
+ ...props
986
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
987
+ return (
988
+ <DropdownMenuPrimitive.SubContent
989
+ data-slot='dropdown-menu-sub-content'
990
+ className={cn(
991
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg',
992
+ className
993
+ )}
994
+ {...props}
995
+ />
996
+ );
997
+ }
998
+
999
+ function DropdownMenuGroup({
1000
+ ...props
1001
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
1002
+ return (
1003
+ <DropdownMenuPrimitive.Group data-slot='dropdown-menu-group' {...props} />
1004
+ );
1005
+ }
1006
+
1007
+ export {
1008
+ DropdownMenu,
1009
+ DropdownMenuPortal,
1010
+ DropdownMenuTrigger,
1011
+ DropdownMenuContent,
1012
+ DropdownMenuGroup,
1013
+ DropdownMenuItem,
1014
+ DropdownMenuCheckboxItem,
1015
+ DropdownMenuRadioGroup,
1016
+ DropdownMenuRadioItem,
1017
+ DropdownMenuLabel,
1018
+ DropdownMenuSeparator,
1019
+ DropdownMenuShortcut,
1020
+ DropdownMenuSub,
1021
+ DropdownMenuSubTrigger,
1022
+ DropdownMenuSubContent,
1023
+ };`,
1024
+
1025
+ };