ConfirmModal.tsx 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { useEffect } from 'react';
  2. import { AlertTriangle, Loader2 } from 'lucide-react';
  3. import { Card, CardContent } from './Card';
  4. import { Button } from './Button';
  5. interface ConfirmModalProps {
  6. title: string;
  7. message: string;
  8. confirmText?: string;
  9. cancelText?: string;
  10. variant?: 'danger' | 'warning' | 'default';
  11. isLoading?: boolean;
  12. loadingText?: string;
  13. onConfirm: () => void;
  14. onCancel: () => void;
  15. }
  16. export function ConfirmModal({
  17. title,
  18. message,
  19. confirmText = 'Confirm',
  20. cancelText = 'Cancel',
  21. variant = 'default',
  22. isLoading = false,
  23. loadingText,
  24. onConfirm,
  25. onCancel,
  26. }: ConfirmModalProps) {
  27. // Close on Escape key (but not while loading)
  28. useEffect(() => {
  29. const handleKeyDown = (e: KeyboardEvent) => {
  30. if (e.key === 'Escape' && !isLoading) onCancel();
  31. };
  32. window.addEventListener('keydown', handleKeyDown);
  33. return () => window.removeEventListener('keydown', handleKeyDown);
  34. }, [onCancel, isLoading]);
  35. const variantStyles = {
  36. danger: {
  37. icon: 'text-red-400',
  38. button: 'bg-red-500 hover:bg-red-600',
  39. },
  40. warning: {
  41. icon: 'text-yellow-400',
  42. button: 'bg-yellow-500 hover:bg-yellow-600',
  43. },
  44. default: {
  45. icon: 'text-bambu-green',
  46. button: 'bg-bambu-green hover:bg-bambu-green-dark',
  47. },
  48. };
  49. const styles = variantStyles[variant];
  50. return (
  51. <div
  52. className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
  53. onClick={isLoading ? undefined : onCancel}
  54. >
  55. <Card className="w-full max-w-md" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
  56. <CardContent className="p-6">
  57. <div className="flex items-start gap-4">
  58. <div className={`p-2 rounded-full bg-bambu-dark ${styles.icon}`}>
  59. <AlertTriangle className="w-6 h-6" />
  60. </div>
  61. <div className="flex-1">
  62. <h3 className="text-lg font-semibold text-white mb-2">{title}</h3>
  63. <p className="text-bambu-gray text-sm">{message}</p>
  64. </div>
  65. </div>
  66. <div className="flex gap-3 mt-6">
  67. <Button variant="secondary" onClick={onCancel} className="flex-1" disabled={isLoading}>
  68. {cancelText}
  69. </Button>
  70. <Button
  71. onClick={onConfirm}
  72. className={`flex-1 ${styles.button}`}
  73. disabled={isLoading}
  74. >
  75. {isLoading ? (
  76. <>
  77. <Loader2 className="w-4 h-4 mr-2 animate-spin" />
  78. {loadingText || 'Processing...'}
  79. </>
  80. ) : (
  81. confirmText
  82. )}
  83. </Button>
  84. </div>
  85. </CardContent>
  86. </Card>
  87. </div>
  88. );
  89. }