import React, { PropsWithChildren, ReactNode, useEffect, useMemo, useRef, } from "react"; import classNames from "classnames"; import isChromatic from "chromatic/isChromatic"; import { Button, ButtonProps } from ":/components/Button"; import { iconFromType, VariantType } from ":/utils/VariantUtils"; export interface ToastProps extends PropsWithChildren { duration: number; type: VariantType; onDelete?: () => void; icon?: ReactNode; primaryLabel?: string; primaryOnClick?: ButtonProps["onClick"]; primaryProps?: ButtonProps; disableAnimate?: boolean; actions?: ReactNode; } export const Toast = (props: ToastProps) => { const [animateDisappear, setAnimateDisappear] = React.useState(false); const container = useRef(null); const disappearTimeout = useRef(null); // Register a timeout to remove the toast after the duration. useEffect(() => { if (props.disableAnimate) { return; } disappearTimeout.current = setTimeout(async () => { setAnimateDisappear(true); disappearTimeout.current = null; }, props.duration); return () => { if (disappearTimeout.current) { clearTimeout(disappearTimeout.current); } }; }, []); const removeAfterAnimation = async () => { await Promise.allSettled( container.current!.getAnimations().map((animation) => animation.finished), ); props.onDelete?.(); }; // Remove the toast after the animation finishes. useEffect(() => { if (animateDisappear) { removeAfterAnimation(); } }, [animateDisappear]); return (
{props.primaryLabel && (
)} {props.actions}
{props.children}
); }; export const ToastIcon = ({ type, ...props }: ToastProps) => { const icon = useMemo(() => iconFromType(type), [type]); if (props.icon) { return props.icon; } if (!icon) { return null; } return (
{icon}
); }; export const ProgressBar = ({ duration }: { duration: number }) => { const content = useRef(null); useEffect(() => { if (isChromatic()) { return; } content.current!.animate([{ width: "0%" }, { width: "100%" }], { duration, easing: "linear", }); }, []); return (
); };