UNPKG

nudge-components-library

Version:

A library of nudge UI components

145 lines (142 loc) 6.36 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import { useContext, useState, useRef, useEffect, useLayoutEffect } from 'react'; import { FiX } from '../../node_modules/react-icons/fi/index.js'; import styles from './Popup.module.css.js'; import '../../styles/tokens.css.js'; import '../../styles/globals.css.js'; import { ThemeContext } from '../../theme/ThemeContext.js'; import defaultImage from '../../assets/message.png.js'; function Popup({ id, title, message, visible = false, onClose, onOpen, autoClose = false, autoCloseDelay, position = "bottom-right", renderContent, ariaLabel, animationType = "fade", animationDuration = 300, dismissible = true, closeOutside = false, image: providedImage, buttonText, onButtonClick, }) { const theme = useContext(ThemeContext); const image = providedImage === undefined ? defaultImage : providedImage; const imageUrl = image != null && typeof image === "object" ? image.src : image; const hasImage = !!(imageUrl && imageUrl !== ""); const [isMobile, setIsMobile] = useState(false); const [shouldRender, setShouldRender] = useState(visible); const [isVisible, setIsVisible] = useState(false); const popupRef = useRef(null); // Mobile detection useEffect(() => { const handleResize = () => setIsMobile(window.innerWidth < 570); handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); // Mounting logic with animation useLayoutEffect(() => { if (visible) { setShouldRender(true); requestAnimationFrame(() => { requestAnimationFrame(() => { setIsVisible(true); }); }); onOpen?.(); } else { setIsVisible(false); const timeout = setTimeout(() => { setShouldRender(false); onClose?.(); }, animationDuration); return () => clearTimeout(timeout); } }, [visible]); // Auto-close useEffect(() => { let timer; if (autoClose && shouldRender) { timer = setTimeout(() => { setIsVisible(false); setTimeout(() => { setShouldRender(false); onClose?.(); }, animationDuration); }, autoCloseDelay); } return () => clearTimeout(timer); }, [autoClose, autoCloseDelay, shouldRender]); // Outside click to close useEffect(() => { if (!closeOutside) return; const handleClickOutside = (event) => { const path = event.composedPath?.() || []; if (popupRef.current && !path.includes(popupRef.current)) { setIsVisible(false); setTimeout(() => { setShouldRender(false); onClose?.(); }, animationDuration); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [closeOutside]); if (!shouldRender) return null; // Position class const positionClass = isMobile ? styles.mobilePopup : (() => { switch (position) { case "top-left": return styles.topLeft; case "top-right": return styles.topRight; case "bottom-left": return styles.bottomLeft; case "bottom-right": default: return styles.bottomRight; } })(); // Final class const containerClasses = [ styles.popupContainer, positionClass, animationType === "fade" ? styles.fade : animationType === "slide" ? styles.slide : "", isVisible ? animationType === "fade" ? styles.fadeVisible : animationType === "slide" ? styles.slideVisible : "" : animationType === "fade" ? styles.fadeHidden : animationType === "slide" ? styles.slideHidden : "", ].join(" "); // Inline styles const containerStyle = { ...theme.popup?.container, "--animation-duration": `${animationDuration}ms`, }; if (isMobile) { containerStyle.minWidth = "100%"; containerStyle.maxWidth = "100%"; containerStyle.width = "100%"; containerStyle.borderRadius = "16px 16px 0px 0px"; } const gridClass = dismissible ? hasImage ? styles.gridDismissibleImage : styles.gridDismissibleNoImage : hasImage ? styles.gridNonDismissibleImage : styles.gridNonDismissibleNoImage; return (jsx("div", { id: id, className: containerClasses, ref: popupRef, style: containerStyle, "aria-label": ariaLabel, children: jsxs("div", { className: `${styles.contentContainer} ${gridClass}`, style: theme.popup?.content, children: [hasImage && (jsx("div", { children: jsx("img", { src: imageUrl, alt: "Popup", style: theme.popup?.image, loading: "lazy" }) })), jsxs("div", { className: styles.textContainer, children: [title && jsx("h3", { style: theme.popup?.title, children: title }), jsx("div", { style: theme.popup?.message, children: renderContent ? renderContent() : jsx("span", { children: message }) }), buttonText && !renderContent && (jsx("button", { style: theme.popup?.actionButton, onClick: onButtonClick, children: buttonText }))] }), dismissible && (jsx("div", { className: styles.closeButtonContainer, children: jsx("button", { onClick: () => { setIsVisible(false); setTimeout(() => { setShouldRender(false); onClose?.(); }, animationDuration); }, role: "button", style: theme.popup?.closeButton, "aria-label": "Close reminder", children: jsx(FiX, {}) }) }))] }) })); } export { Popup }; //# sourceMappingURL=Popup.js.map