nudge-components-library
Version:
A library of nudge UI components
145 lines (142 loc) • 6.36 kB
JavaScript
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