@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
141 lines (138 loc) • 7.58 kB
JavaScript
"use client";
import { jsx, jsxs } from 'react/jsx-runtime';
import { forwardRef, useRef, useEffect } from 'react';
const Modal = forwardRef(({ children, className = "", open = false, onClose, onOpenChange, size = "md", showCloseButton = true, closeOnOverlayClick = true, closeOnEscape = true, role = "dialog", ariaLabelledBy, ariaDescribedBy, animation = true, ...props }, ref) => {
const previousFocusRef = useRef(null);
const modalRef = useRef(null);
const handleClose = () => {
onClose === null || onClose === void 0 ? void 0 : onClose();
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false);
};
useEffect(() => {
if (open) {
// Store the previously focused element
previousFocusRef.current = document.activeElement;
// Focus the modal content
setTimeout(() => {
const modalElement = modalRef.current;
if (modalElement) {
const focusableElement = modalElement.querySelector('button:not([aria-label="Close"]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (focusableElement) {
focusableElement.focus();
}
else {
modalElement.focus();
}
}
}, 0);
}
return () => {
// Restore focus when modal closes
if (!open && previousFocusRef.current) {
previousFocusRef.current.focus();
}
};
}, [open]);
useEffect(() => {
const handleEscape = (event) => {
if (event.key === "Escape" && closeOnEscape && handleClose) {
handleClose();
}
};
const handleTabTrap = (event) => {
if (event.key === "Tab" && modalRef.current) {
const focusableElements = modalRef.current.querySelectorAll('button:not([aria-label="Close"]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (event.shiftKey) {
if (document.activeElement === firstElement) {
lastElement === null || lastElement === void 0 ? void 0 : lastElement.focus();
event.preventDefault();
}
}
else {
if (document.activeElement === lastElement) {
firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus();
event.preventDefault();
}
}
}
};
if (open) {
document.addEventListener("keydown", handleEscape);
document.addEventListener("keydown", handleTabTrap);
document.body.style.overflow = "hidden";
// Set aria-hidden on all other elements
const rootElements = document.querySelectorAll("body > *:not([aria-live])");
rootElements.forEach((element) => {
var _a;
if (element !== ((_a = modalRef.current) === null || _a === void 0 ? void 0 : _a.closest("[data-modal-root]"))) {
element.setAttribute("aria-hidden", "true");
}
});
}
return () => {
document.removeEventListener("keydown", handleEscape);
document.removeEventListener("keydown", handleTabTrap);
document.body.style.overflow = "unset";
// Remove aria-hidden from all elements
const hiddenElements = document.querySelectorAll('[aria-hidden="true"]');
hiddenElements.forEach((element) => {
element.removeAttribute("aria-hidden");
});
};
}, [open, closeOnEscape, handleClose]);
if (!open)
return null;
const sizeClasses = {
sm: "max-w-md",
md: "max-w-lg",
lg: "max-w-2xl",
xl: "max-w-4xl",
full: "max-w-[95vw] max-h-[95vh]",
};
const handleBackdropClick = (event) => {
if (event.target === event.currentTarget && closeOnOverlayClick) {
handleClose();
}
};
return (jsx("div", { "data-modal-root": true, className: `fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 ${animation ? "animate-in fade-in-0 duration-300" : ""}`, onClick: handleBackdropClick, "aria-modal": "true", role: "presentation", children: jsxs("div", { ref: (node) => {
if (typeof ref === "function") {
ref(node);
}
else if (ref) {
ref.current = node;
}
// modalRef is handled by the callback
modalRef.current = node;
}, className: `relative w-full ${sizeClasses[size]} bg-background rounded-lg shadow-lg border ${animation ? "animate-in zoom-in-95 duration-300" : ""} ${className}`, role: role, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, tabIndex: -1, ...props, children: [showCloseButton && (jsx("button", { onClick: handleClose, className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 z-10", "aria-label": "Close", children: jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })), children] }) }));
});
Modal.displayName = "Modal";
const ModalHeader = forwardRef(({ className = "", children, ...props }, ref) => {
return (jsx("div", { ref: ref, className: `flex flex-col space-y-1.5 text-center sm:text-left p-6 pb-4 ${className}`, ...props, children: children }));
});
ModalHeader.displayName = "ModalHeader";
const ModalTitle = forwardRef(({ className = "", as: Component = "h2", children, ...props }, ref) => {
return (jsx(Component, { ref: ref, className: `text-lg font-semibold leading-none tracking-tight ${className}`, ...props, children: children }));
});
ModalTitle.displayName = "ModalTitle";
const ModalDescription = forwardRef(({ className = "", children, ...props }, ref) => {
return (jsx("p", { ref: ref, className: `text-sm text-muted-foreground leading-relaxed ${className}`, ...props, children: children }));
});
ModalDescription.displayName = "ModalDescription";
const ModalContent = forwardRef(({ className = "", children, ...props }, ref) => {
return (jsx("div", { ref: ref, className: `px-6 pb-4 ${className}`, ...props, children: children }));
});
ModalContent.displayName = "ModalContent";
const ModalFooter = forwardRef(({ className = "", justify = "end", children, ...props }, ref) => {
const justifyClasses = {
start: "justify-start",
end: "justify-end",
center: "justify-center",
between: "justify-between",
};
return (jsx("div", { ref: ref, className: `flex flex-col-reverse sm:flex-row sm:space-x-2 p-6 pt-4 border-t ${justifyClasses[justify]} ${className}`, ...props, children: children }));
});
ModalFooter.displayName = "ModalFooter";
export { Modal, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle };
//# sourceMappingURL=Modal.js.map