UNPKG

nudge-components-library

Version:

A library of nudge UI components

121 lines (118 loc) 6.84 kB
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