UNPKG

glass-app-manager

Version:

Informatica's Glass Framework CLI for bootstrapping

180 lines (151 loc) 5.2 kB
// @flow import * as React from "react"; import classNames from "classnames"; import { Transition } from "react-transition-group"; import IconButton from "../button/IconButton"; import { t } from "../withTranslation/withTranslation"; const __DEV__ = process.env.NODE_ENV === "development"; type Props = { /** * Content to render inside the message bubble. */ children: React.ChildrenArray<string | React.Element<any>>, /** * The type of message bubble to show. */ type: "success" | "error" | "warning" | "info", /** * Specify the time in **milliseconds** to wait before * the bubble disappears. */ timeout?: number, /** * The duration of the animation. */ duration?: number, /** * Set true to pause auto-dismiss on hover. */ pauseOnHover?: boolean, /** * Set true to add a "close" icon on the bubble. */ dismissible?: boolean, /** * Execute a callback when the bubble is closed. * Return false to prevent the bubble from being * closed. * @deprecated */ onDismiss?: () => boolean | void, /** * Execute a callback when the bubble is closed. * Return false to prevent the bubble from being * closed. */ onClose?: () => boolean | void, }; const messageBubbleIconMap = { error: "close-solid", success: "check", info: "info", warning: "warning", }; function MessageBubble(props: Props) { const { type, pauseOnHover, className, dismissible, onDismiss, onClose, children, timeout, duration, ...rest } = props; const timer = React.useRef(null); const [closed, setClosed] = React.useState(false); /** * Handles closing the message bubble. * @param {boolean} [force] Used by the timer to forcefully close the bubble. */ const handleClose = React.useCallback( (force?: boolean) => { let canClose = true; if (typeof onClose === "function") { canClose = onClose(); } else if (typeof onDismiss === "function") { canClose = onDismiss(); if (__DEV__) { console.warn( "MessageBubble: onDismiss is deprecated, use onClose instead. This will be removed in 0.5.0" ); } } if (typeof force === "boolean") { canClose = force; } if (canClose !== false) { setClosed(true); } }, [onClose, onDismiss, dismissible] ); const handleMouseEnter = React.useCallback(() => { clearTimeout(timer.current); }, [timer]); const handleMouseLeave = React.useCallback(() => { timer.current = setTimeout(() => handleClose(true), timeout); }, [timer, setClosed, timeout]); React.useEffect(() => { const t = parseInt(timeout); if (isNaN(t)) { throw new Error("timeout prop must be numeric"); } timer.current = setTimeout(() => handleClose(true), t); // used when the bubble is closed manually return () => clearTimeout(timer.current); }, [timeout]); return ( <Transition unmountOnExit mountOnEnter timeout={duration} in={!closed}> {status => ( <div className={classNames( "messagebubble", `messagebubble--type-${type}`, `messagebubble__animation--${status}`, className )} onMouseEnter={pauseOnHover ? handleMouseEnter : null} onMouseLeave={pauseOnHover ? handleMouseLeave : null} role={type === "error" ? "alert" : "status"} aria-live={type === "error" ? "assertive" : "polite"} aria-atomic="true" {...rest}> <div className={classNames( "messagebubble__icon", `aicon aicon__${messageBubbleIconMap[type] || messageBubbleIconMap["info"]}`, `messagebubble__icon--${type}` )} /> <div className="messagebubble__text">{children}</div> {dismissible ? ( <IconButton onClick={handleClose} data-testid="message-bubble-close" aria-label={t("Close")}> <i className="messagebubble__close" /> </IconButton> ) : null} </div> )} </Transition> ); } MessageBubble.defaultProps = { timeout: 8000, pauseOnHover: true, dismissible: false, duration: 750, type: "info", }; export default MessageBubble;