nudge-components-library
Version:
A library of nudge UI components
132 lines (129 loc) • 6.49 kB
JavaScript
import { jsxs, jsx } from 'react/jsx-runtime';
import { useContext, useState, useRef, useEffect, useLayoutEffect } from 'react';
import '../../styles/tokens.css.js';
import '../../styles/globals.css.js';
import { ThemeContext } from '../../theme/ThemeContext.js';
import { FiX } from '../../node_modules/react-icons/fi/index.js';
import styles from './Tooltip.module.css.js';
function Tooltip({ id, text, visible, defaultVisible = false, onOpen, onClose, position = "top", autoClose = false, autoCloseDelay = 3000, renderContent, children, ariaLabel, dismissible = false, animationType = "fade", animationDuration = 300, closeOnHover = true, openOnHover = true, closeOutside = false, buttonText, onButtonClick, icon, }) {
const theme = useContext(ThemeContext);
const [isVisible, setIsVisible] = useState(false);
const [shouldRender, setShouldRender] = useState(defaultVisible);
const [finalPosition, setFinalPosition] = useState("top");
const timerRef = useRef(null);
const containerRef = useRef(null);
const tooltipRef = useRef(null);
useEffect(() => {
if (visible !== undefined) {
if (visible) {
setShouldRender(true);
setTimeout(() => {
setIsVisible(true);
}, 100);
onOpen?.();
}
else {
setIsVisible(false);
const timer = setTimeout(() => {
setShouldRender(false);
onClose?.();
}, animationDuration);
return () => clearTimeout(timer);
}
}
}, [visible, animationDuration, onOpen, onClose]);
// Auto-close logic
useEffect(() => {
if (autoClose && isVisible) {
timerRef.current = setTimeout(() => {
setIsVisible(false);
onClose?.();
}, autoCloseDelay);
return () => {
if (timerRef.current)
clearTimeout(timerRef.current);
};
}
}, [autoClose, autoCloseDelay, isVisible, onClose]);
// Close when clicking outside
useEffect(() => {
if (closeOutside && isVisible) {
const handleClickOutside = (event) => {
if (containerRef.current &&
!containerRef.current.contains(event.target)) {
handleClose();
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}
}, [closeOutside, isVisible]);
// Hover/focus handlers
const handleOpen = (event) => {
if (visible === undefined) {
setShouldRender(true);
setTimeout(() => {
setIsVisible(true);
}, 100);
}
onOpen?.();
};
const handleClose = (event) => {
if (visible === undefined) {
setIsVisible(false);
const timer = setTimeout(() => {
setShouldRender(false);
onClose?.();
}, animationDuration);
return () => clearTimeout(timer);
}
onClose?.();
};
useLayoutEffect(() => {
if (position === "dynamic" && containerRef.current && tooltipRef.current) {
const containerRect = containerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
const margin = 8;
if (containerRect.top >= tooltipRect.height + margin) {
setFinalPosition("top");
}
else if (window.innerHeight - containerRect.bottom >=
tooltipRect.height + margin) {
setFinalPosition("bottom");
}
else if (containerRect.left >= tooltipRect.width + margin) {
setFinalPosition("left");
}
else if (window.innerWidth - containerRect.right >=
tooltipRect.width + margin) {
setFinalPosition("right");
}
else {
setFinalPosition("top");
}
}
}, [position, isVisible]);
const pos = position === "dynamic" ? finalPosition : position;
const animationStyle = {
"--animation-duration": `${animationDuration}ms`,
};
const positionClass = (() => {
switch (pos) {
case "top":
return styles.tooltipTop;
case "bottom":
return styles.tooltipBottom;
case "left":
return styles.tooltipLeft;
case "right":
return styles.tooltipRight;
default:
return "";
}
})();
const animationClass = animationType === "fade" ? styles.fade : styles.slide;
const visibilityClass = isVisible ? styles.visible : styles.hidden;
return (jsxs("div", { ref: containerRef, className: styles.tooltipDisplayContainer, id: id, children: [jsx("div", { onMouseEnter: openOnHover ? handleOpen : undefined, onFocus: openOnHover ? handleOpen : undefined, onMouseLeave: closeOnHover ? handleClose : undefined, onBlur: closeOnHover ? handleClose : undefined, "aria-label": ariaLabel, children: children }), shouldRender && (jsx("div", { ref: tooltipRef, className: `${styles.tooltipContainer} ${positionClass} ${animationClass} ${visibilityClass}`, role: "tooltip", style: { ...animationStyle, ...theme.tooltip?.container }, children: jsxs("div", { className: `${styles.tooltipContentContainer} ${icon ? styles.gridWithIcon : ""}`, style: theme.tooltip?.content, children: [icon && (jsx("div", { className: styles.tooltipIconContainer, style: theme.tooltip?.icon, children: icon })), jsxs("div", { className: styles.tooltipTextContainer, style: theme.tooltip?.message, children: [renderContent ? renderContent() : jsx("span", { children: text }), buttonText && !renderContent && (jsx("button", { className: styles.tooltipActionButton, style: theme.tooltip?.actionButton, onClick: onButtonClick, children: buttonText }))] }), dismissible && (jsx("div", { className: styles.tooltipCloseButtonContainer, style: theme.tooltip?.closeButtonContainer, children: jsx("button", { onClick: handleClose, "aria-label": "Close tooltip", style: theme.tooltip?.closeButton, children: jsx(FiX, {}) }) }))] }) }))] }));
}
export { Tooltip };
//# sourceMappingURL=Tooltip.js.map