import React, { useEffect, useState } from "react";
import toaster from "toasted-notes";
import classNames from "classnames";
import { Action } from "../../types";
import { bemHOF } from "../../utilities/bem";
import { Box } from "../Box";
import { CloseButton } from "../CloseButton";
import { Icon, IconProps, ICON_TYPE } from "../Icon";
import { Link } from "../Link";
import { Text } from "../Text";
const cn = bemHOF("Snackbar");
const POSITION = "bottom-left";
interface SharedProps {
/**
* The message shown in the Snackbar; ideally short and succinct
*/
message: string;
/**
* Adds an action next to the message
*/
action?: Action;
/**
* When a loading icon is detected, it will automatically spin.
*
* Sizing is handled automatically, too.
*/
icon?: ICON_TYPE;
/**
* The amount of time (in milliseconds) the snackbar appears on-screen. If
* undefined, then the Snackbar will stay on-screen indefinitely. An undefined
* duration would be used in cases when the Snackbar is used as a loading
* message (e.g., an in progress API call).
*/
duration?: number | null;
/**
* Prevent the close button from rendering. Useful in cases when the Snackbar
* is used as a loading message (e.g., an in progress API call).
*/
disableClose?: boolean;
}
interface ShowSnackbarProps extends SharedProps {
/**
* Classname to apply to the Snackbar root element
*/
className?: string;
}
interface SnackbarProps extends ShowSnackbarProps {
onClose: () => void;
id?: string;
}
interface SnackbarHookProps extends SharedProps {
/**
* Whether or not the Snackar is visible
*/
isVisible: boolean;
}
const hideSnackbar = (id: number) => toaster.close(id, POSITION);
const SnackbarAction = ({ label, onClick, href, external }: Action) => {
const Component = typeof onClick === "function" ? "button" : "a";
const type = Component === "button" ? "button" : undefined;
return (
{label}
);
};
export const Snackbar = ({
message,
action,
icon,
id,
className,
onClose,
disableClose,
}: SnackbarProps) => {
const spin = icon === ICON_TYPE.SPINNER || icon === ICON_TYPE.CIRCLE_NOTCH;
const iconProps: IconProps | undefined = icon
? {
icon,
spin,
}
: undefined;
return (
{iconProps && }
{message}
{action && action.label && (
{
if (action.onClick) {
action.onClick();
}
onClose();
}}
/>
)}
{!disableClose && (
)}
);
};
export const showSnackbar = ({
message,
action,
icon,
duration = null,
className,
disableClose,
}: ShowSnackbarProps) =>
toaster.notify(
({ onClose, id }: { onClose: () => void; id: number }) => {
return (
);
},
{
position: POSITION,
duration,
},
);
export const useSnackbar = ({
message,
action,
isVisible,
icon,
duration,
disableClose,
}: SnackbarHookProps) => {
const [snackbar, setSnackbar] = useState<{ id: number; position: string }>({
id: 0,
position: POSITION,
});
const [wasViewed, setWasViewed] = useState(false);
useEffect(() => {
if (isVisible && !wasViewed) {
setSnackbar(
showSnackbar({
message,
action,
icon,
duration,
disableClose,
}),
);
setWasViewed(true);
}
if (!isVisible && wasViewed) {
hideSnackbar(snackbar.id);
setWasViewed(false);
}
}, [
message,
action,
isVisible,
icon,
duration,
disableClose,
snackbar,
wasViewed,
]);
};