import PropTypes from "prop-types"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react"; const DEFAULT_AUTO_HIDE_MS = 5000; const MessageContext = createContext({ isVisible: false, message: null, icon: null, showMessage: () => {}, hideMessage: () => {}, }); export const MessageProvider = ({ children, defaultAutoHideDelay = DEFAULT_AUTO_HIDE_MS, }) => { const [state, setState] = useState({ message: null, icon: null, isVisible: false, }); const hideTimeoutRef = useRef(null); const clearHideTimeout = useCallback(() => { if (hideTimeoutRef.current) { window.clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } }, []); const hideMessage = useCallback(() => { clearHideTimeout(); setState((prev) => ({ ...prev, isVisible: false, })); }, [clearHideTimeout]); const showMessage = useCallback( (messageText, icon = null, autoHide = true) => { if (!messageText) { console.warn("showMessage called without message text"); return; } clearHideTimeout(); setState({ message: messageText, icon, isVisible: true, }); const resolvedDelay = typeof autoHide === "number" && autoHide > 0 ? autoHide : defaultAutoHideDelay; if (autoHide && typeof window !== "undefined") { hideTimeoutRef.current = window.setTimeout(() => { setState((prev) => ({ ...prev, isVisible: false, })); hideTimeoutRef.current = null; }, resolvedDelay); } }, [clearHideTimeout, defaultAutoHideDelay] ); useEffect(() => { return () => { clearHideTimeout(); }; }, [clearHideTimeout]); const contextValue = useMemo( () => ({ isVisible: state.isVisible, message: state.message, icon: state.icon, showMessage, hideMessage, }), [state.isVisible, state.message, state.icon, showMessage, hideMessage] ); return ( {children} ); }; MessageProvider.propTypes = { children: PropTypes.node.isRequired, defaultAutoHideDelay: PropTypes.number, }; export const useMessage = () => useContext(MessageContext); const MessageOverlay = ({ message, icon, isVisible, onDismiss }) => { const shouldRender = Boolean(message); if (!shouldRender) { return null; } const className = [ "tb-message-popup", isVisible ? "tb-message-popup-visible" : "tb-message-popup-hidden", ] .filter(Boolean) .join(" "); return (
{icon ? {icon} : null} {message}
); }; MessageOverlay.propTypes = { message: PropTypes.string, icon: PropTypes.node, isVisible: PropTypes.bool.isRequired, onDismiss: PropTypes.func.isRequired, };