diff --git a/webui/src/common/components/Modal/Modal.jsx b/webui/src/common/components/Modal/Modal.jsx new file mode 100644 index 0000000..6e1e62c --- /dev/null +++ b/webui/src/common/components/Modal/Modal.jsx @@ -0,0 +1,65 @@ +import React, { useEffect } from 'react'; +import { XIcon } from '@phosphor-icons/react'; +import Button from '@/common/components/Button'; +import './styles.sass'; + +export const Modal = ({ + isOpen, + onClose, + title, + children, + size = 'md', + closeOnOverlayClick = true, + showCloseButton = true, + className = '' +}) => { + useEffect(() => { + const handleEsc = (event) => { + if (event.keyCode === 27) { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener('keydown', handleEsc, false); + document.body.style.overflow = 'hidden'; + } + + return () => { + document.removeEventListener('keydown', handleEsc, false); + document.body.style.overflow = 'unset'; + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + const handleOverlayClick = (e) => { + if (closeOnOverlayClick && e.target === e.currentTarget) { + onClose(); + } + }; + + return ( +
+
e.stopPropagation()}> + {(title || showCloseButton) && ( +
+ {title &&

{title}

} + {showCloseButton && ( +
+ )} +
+ {children} +
+
+
+ ); +}; \ No newline at end of file diff --git a/webui/src/common/components/Modal/index.js b/webui/src/common/components/Modal/index.js new file mode 100644 index 0000000..8fb160b --- /dev/null +++ b/webui/src/common/components/Modal/index.js @@ -0,0 +1 @@ +export { Modal as default } from './Modal.jsx'; \ No newline at end of file diff --git a/webui/src/common/components/Modal/styles.sass b/webui/src/common/components/Modal/styles.sass new file mode 100644 index 0000000..48787a3 --- /dev/null +++ b/webui/src/common/components/Modal/styles.sass @@ -0,0 +1,80 @@ +.modal-overlay + position: fixed + top: 0 + left: 0 + right: 0 + bottom: 0 + background: rgba(0, 0, 0, 0.5) + display: flex + align-items: center + justify-content: center + z-index: 1000 + padding: 2rem + backdrop-filter: blur(4px) + +.modal + background: var(--bg-alt) + border-radius: var(--radius-lg) + border: 1px solid var(--border) + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2) + width: 100% + max-height: 90vh + overflow: hidden + display: flex + flex-direction: column + animation: modalIn 0.2s ease-out + + &--sm + max-width: 400px + + &--md + max-width: 500px + + &--lg + max-width: 700px + + &--xl + max-width: 900px + +.modal-header + display: flex + align-items: center + justify-content: space-between + padding: 1.5rem 1.5rem 0 + flex-shrink: 0 + +.modal-title + font-size: 1.3rem + font-weight: 600 + color: var(--text) + margin: 0 + +.modal-close + margin-left: auto + +.modal-content + padding: 1.5rem + overflow-y: auto + flex: 1 + +@keyframes modalIn + from + opacity: 0 + transform: scale(0.95) translateY(-10px) + to + opacity: 1 + transform: scale(1) translateY(0) + +@media (max-width: 768px) + .modal-overlay + padding: 1rem + + .modal + max-width: none + margin: 0 + + .modal-header + padding: 1rem 1rem 0 + + .modal-content + padding: 1rem \ No newline at end of file