nudge-components-library
Version:
A library of nudge UI components
121 lines (118 loc) • 6.84 kB
JavaScript
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { useContext, useState, useRef, useEffect, useLayoutEffect } from 'react';
import '../../styles/tokens.css.js';
import '../../styles/globals.css.js';
import { FiX } from '../../node_modules/react-icons/fi/index.js';
import styles from './Dialog.module.css.js';
import { ThemeContext } from '../../theme/ThemeContext.js';
import { TextBox } from '../anchoring/TextBox.js';
function Dialog({ id, title, message, visible = false, onClose, onOpen, autoClose = false, autoCloseDelay = 5000, animationType = "fade", animationDuration = 300, dismissible = true, closeOutside = false, confirmButtonText = "Confirm", cancelButtonText = "Cancel", onConfirm, onCancel, requiresInput = false, inputPlaceholder = "Type to confirm", confirmationValue = "", expectedInput, onInputChange, ariaLabel = "Dialog", inputProps, confirmationPrompt, }) {
const theme = useContext(ThemeContext);
const [isMobile, setIsMobile] = useState(false);
const [shouldRender, setShouldRender] = useState(visible);
const [isVisible, setIsVisible] = useState(false);
const dialogRef = 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, animationDuration, onClose, onOpen]);
// Auto-close logic
useEffect(() => {
let timer;
if (autoClose && shouldRender) {
timer = setTimeout(() => {
setIsVisible(false);
setTimeout(() => {
setShouldRender(false);
onClose?.();
}, animationDuration);
}, autoCloseDelay);
}
return () => clearTimeout(timer);
}, [autoClose, autoCloseDelay, shouldRender, animationDuration, onClose]);
if (!shouldRender)
return null;
const containerClasses = [
styles.dialogContainer,
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
: "",
isMobile ? styles.mobileDialog : "",
].join(" ");
const containerStyle = {
...theme.dialog?.container,
"--animation-duration": `${animationDuration}ms`,
};
const overlayStyle = {
...(theme.dialog?.overlay || { background: "rgba(0, 0, 0, 0.5)" }),
transition: `opacity ${animationDuration}ms ease`,
};
const handleOverlayClick = () => {
if (closeOutside) {
setIsVisible(false);
setTimeout(() => {
setShouldRender(false);
onClose?.();
}, animationDuration);
}
};
const handleConfirmClick = () => {
if (requiresInput && expectedInput && confirmationValue !== expectedInput) {
return;
}
onConfirm?.();
};
const isConfirmDisabled = requiresInput && expectedInput
? confirmationValue !== expectedInput
: false;
return (jsx("div", { className: `${styles.overlay} ${isVisible ? styles.overlayVisible : styles.overlayHidden}`, onClick: handleOverlayClick, style: overlayStyle, children: jsx("div", { id: id, className: containerClasses, ref: dialogRef, style: containerStyle, "aria-label": ariaLabel, onClick: (e) => e.stopPropagation(), children: jsxs("div", { className: styles.contentContainer, style: theme.dialog?.content, children: [(title || dismissible) && (jsxs("div", { style: theme.dialog?.header, className: styles.headerRow, children: [title && jsx("h3", { style: theme.dialog?.title, children: title }), dismissible && (jsx("button", { onClick: () => {
setIsVisible(false);
setTimeout(() => {
setShouldRender(false);
onClose?.();
}, animationDuration);
}, role: "button", style: theme.dialog?.closeButton, "aria-label": "Close dialog", children: jsx(FiX, {}) }))] })), jsxs("div", { style: theme.dialog?.textContainer, children: [message && jsx("div", { style: theme.dialog?.message, children: message }), requiresInput && (jsxs(Fragment, { children: [jsx("p", { style: theme.dialog?.promptText, children: confirmationPrompt }), jsx("div", { style: theme.dialog?.input, children: jsx(TextBox, { id: "dialog-confirmation-input", textBoxLabel: "", value: confirmationValue, onChange: (value) => {
onInputChange?.(value);
}, placeholder: inputPlaceholder, ariaLabel: ariaLabel, ...inputProps }) })] }))] }), requiresInput && (jsx("div", { style: theme.dialog?.inputContainer, className: styles.inputSection, children: jsxs("div", { className: styles.buttonSection, children: [jsx("button", { onClick: onCancel, style: theme.dialog?.cancelButton, children: cancelButtonText }), jsx("button", { onClick: handleConfirmClick, style: {
...theme.dialog?.confirmButton,
...(isConfirmDisabled ? theme.dialog?.disabled : {}),
}, disabled: isConfirmDisabled, children: confirmButtonText })] }) })), !requiresInput && (jsxs("div", { style: theme.dialog?.buttonContainer, className: styles.buttonSection, children: [jsx("button", { onClick: onCancel, style: theme.dialog?.cancelButton, children: cancelButtonText }), jsx("button", { onClick: onConfirm, style: theme.dialog?.confirmButton, children: confirmButtonText })] }))] }) }) }));
}
export { Dialog };
//# sourceMappingURL=Dialog.js.map