114 lines
2.5 KiB
TypeScript
114 lines
2.5 KiB
TypeScript
import React, { ReactNode, useEffect, useRef } from 'react';
|
|
import './Modal.css';
|
|
|
|
export interface ModalProps {
|
|
/**
|
|
* Title displayed at the top of the modal
|
|
*/
|
|
title?: string;
|
|
|
|
/**
|
|
* Content of the modal
|
|
*/
|
|
children: ReactNode;
|
|
|
|
/**
|
|
* Whether the modal is visible or not
|
|
*/
|
|
isOpen: boolean;
|
|
|
|
/**
|
|
* Function to call when the modal close button is clicked
|
|
*/
|
|
onClose: () => void;
|
|
|
|
/**
|
|
* Optional CSS class name to add to the modal for custom styling
|
|
*/
|
|
className?: string;
|
|
|
|
/**
|
|
* Whether to show the close button in the header
|
|
* @default true
|
|
*/
|
|
showCloseButton?: boolean;
|
|
|
|
/**
|
|
* Whether to close the modal when clicking outside of it
|
|
* @default true
|
|
*/
|
|
closeOnOutsideClick?: boolean;
|
|
}
|
|
|
|
/**
|
|
* A reusable modal component that can be used for various purposes
|
|
*/
|
|
const Modal: React.FC<ModalProps> = ({
|
|
title,
|
|
children,
|
|
isOpen,
|
|
onClose,
|
|
className = '',
|
|
showCloseButton = true,
|
|
closeOnOutsideClick = true
|
|
}) => {
|
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Close modal when escape key is pressed
|
|
useEffect(() => {
|
|
const handleEscKey = (event: KeyboardEvent) => {
|
|
if (isOpen && event.key === 'Escape') {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleEscKey);
|
|
return () => {
|
|
document.removeEventListener('keydown', handleEscKey);
|
|
};
|
|
}, [isOpen, onClose]);
|
|
|
|
// Handle outside click
|
|
const handleBackdropClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
if (closeOnOutsideClick && modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
if (!isOpen) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="modal-backdrop" onClick={handleBackdropClick}>
|
|
<div
|
|
className={`modal-container ${className}`}
|
|
ref={modalRef}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby={title ? "modal-title" : undefined}
|
|
aria-describedby="modal-content"
|
|
>
|
|
{(title || showCloseButton) && (
|
|
<div className="modal-header">
|
|
{title && <h3 className="modal-title" id="modal-title">{title}</h3>}
|
|
{showCloseButton && (
|
|
<button
|
|
className="modal-close-button"
|
|
onClick={onClose}
|
|
aria-label="Close"
|
|
>
|
|
×
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="modal-content" id="modal-content">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Modal; |