I create a custom Modal component in react + typescript.
Modal.tsx
// src/components/ui/Modal.tsx
import React, { useState, useEffect, useRef } from 'react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
dismissable?: boolean;
closeOnEsc?: boolean;
closeButtonLabel?: string;
size?: 'small' | 'medium' | 'large';
position?: 'center' | 'top' | 'bottom';
}
const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
children,
dismissable = true,
closeOnEsc = true,
closeButtonLabel = '×',
size = 'medium',
position = 'center',
}) => {
const [isModalOpen, setIsModalOpen] = useState(isOpen);
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setIsModalOpen(isOpen);
}, [isOpen]);
useEffect(() => {
const handleOutsideClick = (event: MouseEvent) => {
if (dismissable && modalRef.current && !modalRef.current.contains(event.target as Node)) {
onClose();
}
};
const handleEscKey = (event: KeyboardEvent) => {
if (closeOnEsc && event.key === 'Escape') {
onClose();
}
};
if (isModalOpen) {
document.addEventListener('mousedown', handleOutsideClick);
document.addEventListener('keydown', handleEscKey);
}
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
document.removeEventListener('keydown', handleEscKey);
};
}, [isModalOpen, onClose, dismissable, closeOnEsc]);
if (!isModalOpen) {
return null;
}
const getSizeStyle = () => {
switch (size) {
case 'small':
return { width: '300px', maxWidth: '100%' };
case 'large':
return { width: '800px', maxWidth: '100%' };
default:
return { width: '500px', maxWidth: '100%' };
}
};
const getPositionStyle = () => {
switch (position) {
case 'top':
return { alignItems: 'flex-start' };
case 'bottom':
return { alignItems: 'flex-end' };
default:
return { alignItems: 'center' };
}
};
return (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
zIndex: 1000,
...getPositionStyle(),
}}>
<div
ref={modalRef}
style={{
backgroundColor: 'white',
padding: '20px',
borderRadius: '8px',
maxHeight: '80vh',
overflow: 'auto',
position: 'relative',
...getSizeStyle(),
}}
>
<button
onClick={onClose}
style={{
position: 'absolute',
top: '10px',
right: '10px',
background: 'none',
border: 'none',
fontSize: '18px',
cursor: 'pointer',
}}
>
{closeButtonLabel}
</button>
{children}
</div>
</div>
);
};
export default Modal;
App.tsx
import Modal from "@/components/ui/Modal";
import { useState } from "react";
export default function Home() {
const [visible, setVisible] = useState(false);
return (
<main>
<button onClick={() => setVisible(true)}>isOpen</button>
<Modal position="bottom" isOpen={visible} onClose={() => setVisible(false)}>
<h2>Modal Content</h2>
<p>This is a simple modal component.</p>
</Modal>
</main>
);
}
Top comments (0)