SPB Git

Repository Public

dialog.tsx 3.2 KB 95 lines tsx

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
Files main
Menu

Theme