ConfirmDialog.tsx 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { useEffect, useRef } from "react";
  2. import { AlertTriangle, X } from "lucide-react";
  3. interface ConfirmDialogProps {
  4. readonly open: boolean;
  5. readonly title: string;
  6. readonly message: string;
  7. readonly confirmLabel: string;
  8. readonly cancelLabel: string;
  9. readonly variant?: "danger" | "default";
  10. readonly onConfirm: () => void;
  11. readonly onCancel: () => void;
  12. }
  13. export function ConfirmDialog({
  14. open,
  15. title,
  16. message,
  17. confirmLabel,
  18. cancelLabel,
  19. variant = "default",
  20. onConfirm,
  21. onCancel,
  22. }: ConfirmDialogProps) {
  23. const overlayRef = useRef<HTMLDivElement>(null);
  24. useEffect(() => {
  25. if (!open) return;
  26. const handleKey = (e: KeyboardEvent) => {
  27. if (e.key === "Escape") onCancel();
  28. };
  29. document.addEventListener("keydown", handleKey);
  30. return () => document.removeEventListener("keydown", handleKey);
  31. }, [open, onCancel]);
  32. if (!open) return null;
  33. const isDanger = variant === "danger";
  34. return (
  35. <div
  36. ref={overlayRef}
  37. className="fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm fade-in"
  38. onClick={(e) => { if (e.target === overlayRef.current) onCancel(); }}
  39. >
  40. <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">
  41. {/* Header */}
  42. <div className="flex items-center justify-between px-6 pt-6 pb-2">
  43. <div className="flex items-center gap-3">
  44. {isDanger && (
  45. <div className="w-10 h-10 rounded-xl bg-destructive/10 flex items-center justify-center">
  46. <AlertTriangle size={20} className="text-destructive" />
  47. </div>
  48. )}
  49. <h3 className="text-lg font-semibold">{title}</h3>
  50. </div>
  51. <button
  52. onClick={onCancel}
  53. className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors"
  54. >
  55. <X size={16} />
  56. </button>
  57. </div>
  58. {/* Body */}
  59. <div className="px-6 py-4">
  60. <p className="text-sm text-muted-foreground leading-relaxed">{message}</p>
  61. </div>
  62. {/* Footer */}
  63. <div className="flex justify-end gap-3 px-6 pb-6">
  64. <button
  65. onClick={onCancel}
  66. 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"
  67. >
  68. {cancelLabel}
  69. </button>
  70. <button
  71. onClick={onConfirm}
  72. className={`px-4 py-2.5 text-sm font-bold rounded-xl transition-all hover:scale-105 active:scale-95 shadow-sm ${
  73. isDanger
  74. ? "bg-destructive text-white hover:shadow-destructive/20"
  75. : "bg-primary text-primary-foreground hover:shadow-primary/20"
  76. }`}
  77. >
  78. {confirmLabel}
  79. </button>
  80. </div>
  81. </div>
  82. </div>
  83. );
  84. }