File size: 4,363 Bytes
c9b0eac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from "@headlessui/react";
import { Fragment, ReactNode } from "react";
import { AlertTriangle, X } from "lucide-react";
interface ConfirmModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
title: string;
description: string | ReactNode;
confirmText?: string;
cancelText?: string;
isDestructive?: boolean;
isLoading?: boolean;
}
export default function ConfirmModal({
isOpen,
onClose,
onConfirm,
title,
description,
confirmText = "Confirm",
cancelText = "Cancel",
isDestructive = true,
isLoading = false,
}: ConfirmModalProps) {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={onClose}>
<TransitionChild
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/70 backdrop-blur-xs" />
</TransitionChild>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<TransitionChild
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-150"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<DialogPanel className="w-full max-w-md transform overflow-hidden rounded-xl bg-zinc-900 border border-zinc-800 p-6 text-left align-middle shadow-2xl transition-all">
<div className="flex items-start gap-4">
<div
className={`p-3 rounded-full shrink-0 ${
isDestructive ? "bg-rose-500/10 text-rose-400 border border-rose-500/20" : "bg-indigo-500/10 text-indigo-400 border border-indigo-500/20"
}`}
>
<AlertTriangle className="w-6 h-6" />
</div>
<div className="flex-1 min-w-0">
<DialogTitle as="h3" className="text-lg font-semibold text-zinc-100">
{title}
</DialogTitle>
<div className="mt-2 text-sm text-zinc-400">
{description}
</div>
</div>
<button
onClick={onClose}
className="text-zinc-500 hover:text-zinc-300 transition-colors p-1 rounded-md"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="mt-6 flex justify-end gap-3">
<button
type="button"
onClick={onClose}
disabled={isLoading}
className="px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-sm font-medium rounded-lg transition-colors"
>
{cancelText}
</button>
<button
type="button"
onClick={() => {
onConfirm();
}}
disabled={isLoading}
className={`px-4 py-2 text-sm font-medium rounded-lg text-white transition-colors flex items-center gap-2 ${
isDestructive
? "bg-rose-600 hover:bg-rose-500"
: "bg-indigo-600 hover:bg-indigo-500"
} disabled:opacity-50`}
>
{isLoading && (
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
)}
{confirmText}
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</Transition>
);
}
|