Files
blackcanyontickets/reactrebuild0825/src/components/ui/Modal.tsx
dzinesco aa81eb5adb feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality
- Implement territory management with manager performance tracking
- Add seatmap components for venue layout management
- Create customer management features with modal interface
- Add advanced hooks for dashboard flags and territory data
- Implement seat selection and venue management utilities
- Add type definitions for ticketing and seatmap systems

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 09:25:10 -06:00

124 lines
2.8 KiB
TypeScript

import React, { useEffect } from 'react';
import { clsx } from 'clsx';
import { X } from 'lucide-react';
import { Button } from './Button';
export interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl';
showCloseButton?: boolean;
closeOnOverlayClick?: boolean;
className?: string;
}
export const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
title,
children,
size = 'md',
showCloseButton = true,
closeOnOverlayClick = true,
className
}) => {
// Handle escape key
useEffect(() => {
if (!isOpen) {return;}
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [isOpen, onClose]);
// Prevent body scroll when modal is open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
if (!isOpen) {return null;}
const sizeClasses = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl'
};
const handleOverlayClick = (e: React.MouseEvent) => {
if (closeOnOverlayClick && e.target === e.currentTarget) {
onClose();
}
};
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-lg"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
{/* Backdrop */}
<div
className="absolute inset-0 bg-surface-backdrop backdrop-blur-sm"
onClick={handleOverlayClick}
aria-hidden="true"
/>
{/* Modal */}
<div className={clsx(
'relative w-full bg-glass-bg border border-glass-border',
'backdrop-blur-lg rounded-xl shadow-elevation-xl',
'flex flex-col max-h-[90vh]',
sizeClasses[size],
className
)}>
{/* Header */}
<div className="flex items-center justify-between p-lg border-b border-glass-border">
<h2
id="modal-title"
className="text-lg font-semibold text-text-primary"
>
{title}
</h2>
{showCloseButton && (
<Button
variant="ghost"
size="sm"
onClick={onClose}
className="p-xs hover:bg-elevated-1"
aria-label="Close modal"
>
<X className="h-4 w-4" />
</Button>
)}
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-lg">
{children}
</div>
</div>
</div>
);
};
export default Modal;