116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
import { AlertTriangle, Info } from 'lucide-react';
|
|
import type { ReactNode } from 'react';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
type ConfirmationDialogTone = 'danger' | 'warning' | 'info';
|
|
|
|
interface ConfirmationDialogProps {
|
|
readonly open: boolean;
|
|
readonly title: string;
|
|
readonly description: ReactNode;
|
|
readonly confirmLabel: string;
|
|
readonly cancelLabel?: string;
|
|
readonly loading?: boolean;
|
|
readonly loadingLabel?: string;
|
|
readonly disabled?: boolean;
|
|
readonly tone?: ConfirmationDialogTone;
|
|
readonly icon?: ReactNode;
|
|
readonly onCancel: () => void;
|
|
readonly onConfirm: () => void;
|
|
}
|
|
|
|
const toneClasses: Record<ConfirmationDialogTone, {
|
|
readonly panelAccent: string;
|
|
readonly iconWrap: string;
|
|
readonly confirmButton: string;
|
|
}> = {
|
|
danger: {
|
|
panelAccent: 'from-red-500/12 via-rose-500/8 to-transparent border-red-500/20',
|
|
iconWrap: 'border-red-500/30 bg-gradient-to-br from-red-500/25 to-rose-600/15 text-red-200 shadow-red-500/20',
|
|
confirmButton: 'bg-gradient-to-r from-red-500 to-rose-600 text-white hover:from-red-400 hover:to-rose-500 shadow-lg shadow-red-500/20',
|
|
},
|
|
warning: {
|
|
panelAccent: 'from-amber-500/12 via-orange-500/8 to-transparent border-amber-500/20',
|
|
iconWrap: 'border-amber-500/30 bg-gradient-to-br from-amber-400/25 to-orange-500/15 text-amber-200 shadow-amber-500/20',
|
|
confirmButton: 'bg-gradient-to-r from-amber-400 to-orange-500 text-slate-950 hover:from-amber-300 hover:to-orange-400 shadow-lg shadow-amber-500/20',
|
|
},
|
|
info: {
|
|
panelAccent: 'from-blue-500/12 via-cyan-500/8 to-transparent border-blue-500/20',
|
|
iconWrap: 'border-blue-500/30 bg-gradient-to-br from-blue-500/25 to-cyan-500/15 text-blue-200 shadow-blue-500/20',
|
|
confirmButton: 'bg-gradient-to-r from-blue-500 to-cyan-500 text-white hover:from-blue-400 hover:to-cyan-400 shadow-lg shadow-blue-500/20',
|
|
},
|
|
};
|
|
|
|
export function ConfirmationDialog({
|
|
open,
|
|
title,
|
|
description,
|
|
confirmLabel,
|
|
cancelLabel = 'Cancel',
|
|
loading = false,
|
|
loadingLabel,
|
|
disabled = false,
|
|
tone = 'danger',
|
|
icon,
|
|
onCancel,
|
|
onConfirm,
|
|
}: ConfirmationDialogProps) {
|
|
const fallbackIcon = tone === 'info' ? <Info size={22} /> : <AlertTriangle size={22} />;
|
|
const classes = toneClasses[tone];
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={(nextOpen) => {
|
|
if (!nextOpen && !loading) {
|
|
onCancel();
|
|
}
|
|
}}
|
|
>
|
|
<DialogContent className="overflow-hidden rounded-2xl border-slate-700/60 bg-slate-900/95 p-0 text-white shadow-2xl shadow-black/40 backdrop-blur-xl sm:max-w-lg">
|
|
<DialogHeader className={cn('gap-0 border-b bg-gradient-to-br p-6 text-left', classes.panelAccent)}>
|
|
<div className="flex items-start gap-4">
|
|
<div className={cn('flex h-12 w-12 shrink-0 items-center justify-center rounded-xl border shadow-lg', classes.iconWrap)}>
|
|
{icon ?? fallbackIcon}
|
|
</div>
|
|
<div className="space-y-2 pt-0.5">
|
|
<DialogTitle className="text-xl font-semibold leading-tight text-white">{title}</DialogTitle>
|
|
<DialogDescription className="text-sm leading-6 text-slate-300">
|
|
{description}
|
|
</DialogDescription>
|
|
</div>
|
|
</div>
|
|
</DialogHeader>
|
|
<DialogFooter className="gap-3 border-t border-slate-800/80 bg-slate-950/35 p-5 sm:space-x-0">
|
|
<Button
|
|
type="button"
|
|
onClick={onCancel}
|
|
disabled={loading}
|
|
className="h-11 rounded-xl border border-slate-700/70 bg-slate-800/70 px-5 text-slate-200 hover:bg-slate-700/80"
|
|
>
|
|
{cancelLabel}
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
onClick={onConfirm}
|
|
disabled={disabled || loading}
|
|
loading={loading}
|
|
loadingLabel={loadingLabel ?? confirmLabel}
|
|
className={cn(classes.confirmButton, 'h-11 rounded-xl px-5 disabled:cursor-not-allowed disabled:opacity-60')}
|
|
>
|
|
{confirmLabel}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|