Repository Public
1'use client';2import * as React from 'react';3import * as D from '@radix-ui/react-dialog';4import { X } from 'lucide-react';5import { cn } from '@/lib/cn';6import { Button } from './button';78export function Dialog({9 open, onOpenChange, title, description, children, footer, size = 'md', className,10}: {11 open: boolean;12 onOpenChange: (open: boolean) => void;13 title: string;14 description?: React.ReactNode;15 children?: React.ReactNode;16 footer?: React.ReactNode;17 size?: 'sm' | 'md' | 'lg' | 'xl';18 className?: string;19}) {20 const w = { sm: 'sm:max-w-sm', md: 'sm:max-w-md', lg: 'sm:max-w-xl', xl: 'sm:max-w-4xl' }[size];21 return (22 <D.Root open={open} onOpenChange={onOpenChange}>23 <D.Portal>24 <D.Overlay className="anim-fade fixed inset-0 z-50 bg-scrim" />25 <D.Content26 className={cn(27 'anim-pop fixed z-50 flex max-h-[min(88dvh,calc(var(--vvh,100dvh)-16px))] w-[calc(100vw-24px)] flex-col rounded-lg border border-card-line bg-surface shadow-pop focus:outline-none',28 'left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',29 w,30 className,31 )}32 >33 <div className="flex items-start gap-3 px-5 pb-2 pt-4">34 <div className="min-w-0 flex-1">35 <D.Title className="text-base font-semibold tracking-tight text-text">{title}</D.Title>36 {description ? <D.Description className="mt-1 text-[13px] leading-relaxed text-text-2">{description}</D.Description> : <D.Description className="sr-only">{title}</D.Description>}37 </div>38 <D.Close aria-label="Close" className="-mr-1 inline-flex size-7 items-center justify-center rounded-sm text-muted hover:bg-surface-2 hover:text-text pointer-coarse:size-11">39 <X className="size-4" />40 </D.Close>41 </div>42 {children && <div className="min-h-0 flex-1 overflow-y-auto px-5 py-3">{children}</div>}43 {footer && <div className="flex flex-wrap items-center justify-end gap-2 px-5 pb-4 pt-3">{footer}</div>}44 </D.Content>45 </D.Portal>46 </D.Root>47 );48}4950export function ConfirmDialog({51 open, onOpenChange, title, description, confirmLabel = 'Confirm', danger, onConfirm, loading,52}: {53 open: boolean;54 onOpenChange: (open: boolean) => void;55 title: string;56 description: React.ReactNode;57 confirmLabel?: string;58 danger?: boolean;59 onConfirm: () => void | Promise<void>;60 loading?: boolean;61}) {62 const [busy, setBusy] = React.useState(false);63 return (64 <Dialog65 open={open}66 onOpenChange={onOpenChange}67 title={title}68 description={description}69 size="sm"70 footer={71 <>72 <Button variant="ghost" onClick={() => onOpenChange(false)}>73 Cancel74 </Button>75 <Button76 variant={danger ? 'danger' : 'primary'}77 loading={busy || loading}78 onClick={async () => {79 setBusy(true);80 try {81 await onConfirm();82 onOpenChange(false);83 } finally {84 setBusy(false);85 }86 }}87 >88 {confirmLabel}89 </Button>90 </>91 }92 />93 );94}95