glass-app-manager
Version:
Informatica's Glass Framework CLI for bootstrapping
180 lines (151 loc) • 5.2 kB
JavaScript
// @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;