UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

141 lines (138 loc) 7.58 kB
"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