import { camelCase } from "lodash-es"; import { ComponentDefaultTestId, getTestId } from "../../tests/test-ids-utils"; import cx from "classnames"; import React, { FC, ReactElement, useCallback, useEffect, useMemo, useRef } from "react"; import { CSSTransition } from "react-transition-group"; import Button from "../../components/Button/Button"; import { IconSubComponentProps } from "../Icon/Icon"; import Text from "../Text/Text"; import Loader from "../Loader/Loader"; import Flex from "../Flex/Flex"; import CloseSmall from "../Icon/Icons/components/CloseSmall"; import ToastLink from "./ToastLink/ToastLink"; import ToastButton from "./ToastButton/ToastButton"; import { ToastAction, ToastActionType, ToastType } from "./ToastConstants"; import { getIcon } from "./ToastHelpers"; import { NOOP } from "../../utils/function-utils"; import { getStyle } from "../../helpers/typesciptCssModulesHelper"; import { withStaticProps, VibeComponentProps } from "../../types"; import styles from "./Toast.module.scss"; import IconButton from "../IconButton/IconButton"; import usePrevious from "../../hooks/usePrevious"; export interface ToastProps extends VibeComponentProps { actions?: ToastAction[]; /** If true, Toast is open (visible) */ open?: boolean; loading?: boolean; type?: ToastType; /** Possible to override the default icon */ icon?: string | React.FC | null; /** If true, won't show the icon */ hideIcon?: boolean; /** The action to display */ action?: JSX.Element; /** If false, won't show the close button */ closeable?: boolean; onClose?: () => void; /** The number of milliseconds to wait before * automatically closing the Toast * (0 or null cancels this behaviour) */ autoHideDuration?: number; children?: ReactElement | ReactElement[] | string; closeButtonAriaLabel?: string; } const Toast: FC & { types?: typeof ToastType; actionTypes?: typeof ToastActionType } = ({ open = false, loading = false, autoHideDuration = null, type = ToastType.NORMAL, icon, hideIcon = false, action: deprecatedAction, actions, children, closeable = true, onClose = NOOP, className, id, closeButtonAriaLabel = "Close", "data-testid": dataTestId }) => { const ref = useRef(null); const prevActions = usePrevious(actions?.length); const toastLinks = useMemo(() => { return actions ? actions .filter(action => action.type === ToastActionType.LINK) .map(({ type: _type, ...otherProps }) => ( )) : null; }, [actions]); const shouldShowButtonTransition = useMemo(() => { return prevActions !== undefined && actions?.length !== prevActions; }, [actions, prevActions]); const toastButtons: JSX.Element[] | null = useMemo(() => { return actions ? actions .filter(action => action.type === ToastActionType.BUTTON) .map(({ type: _type, content, ...otherProps }, index) => ( {content} )) : null; }, [actions, shouldShowButtonTransition]); const classNames = useMemo( () => cx(styles.toast, getStyle(styles, camelCase("type-" + type)), className), [type, className] ); const handleClose = useCallback(() => { if (onClose) { onClose(); } }, [onClose]); /* Timer */ const timerAutoHide = useRef(); const setAutoHideTimer = useCallback( (duration: number) => { if (!onClose || duration == null) { return; } clearTimeout(timerAutoHide.current); timerAutoHide.current = setTimeout(() => { handleClose(); }, duration); }, [handleClose, onClose] ); useEffect(() => { if (open && autoHideDuration > 0) { setAutoHideTimer(autoHideDuration); } return () => { clearTimeout(timerAutoHide.current); }; }, [open, autoHideDuration, setAutoHideTimer]); const iconElement = !hideIcon && getIcon(type, icon); // https://n12v.com/css-transition-to-from-auto/ const recalculateElementWidth = useCallback((element: HTMLElement) => { const prevWidth = element.style.width; element.style.width = "auto"; const endWidth = getComputedStyle(element).width; element.style.width = prevWidth; element.offsetWidth; // force repaint element.style.width = endWidth; }, []); useEffect(() => { if (ref.current) { recalculateElementWidth(ref.current); } }, [children, recalculateElementWidth]); return ( {iconElement &&
{iconElement}
} {children} {toastLinks} {(toastButtons || deprecatedAction) && (toastButtons || deprecatedAction)} {loading && } {closeable && ( )}
); }; export default withStaticProps(Toast, { types: ToastType, actionTypes: ToastActionType });