| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- import { useEffect, useRef } from "react";
- import { createPortal } from "react-dom";
- import { AlertTriangle, X } from "lucide-react";
- interface ConfirmDialogProps {
- readonly open: boolean;
- readonly title: string;
- readonly message: string;
- readonly confirmLabel: string;
- readonly cancelLabel: string;
- readonly variant?: "danger" | "default";
- readonly onConfirm: () => void;
- readonly onCancel: () => void;
- }
- export function ConfirmDialog({
- open,
- title,
- message,
- confirmLabel,
- cancelLabel,
- variant = "default",
- onConfirm,
- onCancel,
- }: ConfirmDialogProps) {
- const overlayRef = useRef<HTMLDivElement>(null);
- useEffect(() => {
- if (!open) return;
- const handleKey = (e: KeyboardEvent) => {
- if (e.key === "Escape") onCancel();
- };
- document.addEventListener("keydown", handleKey);
- return () => document.removeEventListener("keydown", handleKey);
- }, [open, onCancel]);
- if (!open) return null;
- if (typeof document === "undefined") return null;
- const isDanger = variant === "danger";
- return createPortal(
- <div
- ref={overlayRef}
- className="fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm fade-in"
- onClick={(e) => { if (e.target === overlayRef.current) onCancel(); }}
- >
- <div className="bg-card border border-border rounded-2xl shadow-2xl shadow-primary/10 w-full max-w-md mx-4 overflow-hidden chat-msg-assistant">
- {/* Header */}
- <div className="flex items-center justify-between px-6 pt-6 pb-2">
- <div className="flex items-center gap-3">
- {isDanger && (
- <div className="w-10 h-10 rounded-xl bg-destructive/10 flex items-center justify-center">
- <AlertTriangle size={20} className="text-destructive" />
- </div>
- )}
- <h3 className="text-lg font-semibold">{title}</h3>
- </div>
- <button
- onClick={onCancel}
- className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors"
- >
- <X size={16} />
- </button>
- </div>
- {/* Body */}
- <div className="px-6 py-4">
- <p className="text-sm text-muted-foreground leading-relaxed">{message}</p>
- </div>
- {/* Footer */}
- <div className="flex justify-end gap-3 px-6 pb-6">
- <button
- onClick={onCancel}
- className="px-4 py-2.5 text-sm font-medium rounded-xl bg-secondary text-foreground hover:bg-secondary/80 transition-all border border-border/50"
- >
- {cancelLabel}
- </button>
- <button
- onClick={onConfirm}
- className={`px-4 py-2.5 text-sm font-bold rounded-xl transition-all hover:scale-105 active:scale-95 shadow-sm ${
- isDanger
- ? "bg-destructive text-white hover:shadow-destructive/20"
- : "bg-primary text-primary-foreground hover:shadow-primary/20"
- }`}
- >
- {confirmLabel}
- </button>
- </div>
- </div>
- </div>,
- document.body,
- );
- }
|