101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
import { createRoot } from "react-dom/client";
|
|
import Card from "./Card";
|
|
import { X } from "lucide-react";
|
|
import Span from "../layout/Span";
|
|
|
|
export const ToastStyles = ["normal", "success", "error"] as const;
|
|
export const ToastColors = ToastStyles;
|
|
|
|
type Props = DetailedHTMLProps<
|
|
HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
> & {
|
|
open?: boolean;
|
|
setOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
closeDelay?: number;
|
|
color?: (typeof ToastStyles)[number];
|
|
};
|
|
|
|
/**
|
|
* # Toast Component
|
|
* @className twui-toast-root
|
|
* @className twui-toast
|
|
* @className twui-toast-success
|
|
* @className twui-toast-error
|
|
*/
|
|
export default function Toast({
|
|
open,
|
|
setOpen,
|
|
closeDelay = 4000,
|
|
color,
|
|
...props
|
|
}: Props) {
|
|
if (!open) return null;
|
|
|
|
const toastEl = (
|
|
<Card
|
|
{...props}
|
|
className={twMerge(
|
|
"pl-6 pr-8 py-4 bg-blue-700 dark:bg-blue-800",
|
|
color == "success"
|
|
? "bg-emerald-600 dark:bg-emerald-700 twui-toast-success"
|
|
: color == "error"
|
|
? "bg-orange-600 dark:bg-orange-700 twui-toast-error"
|
|
: "",
|
|
props.className,
|
|
"twui-toast"
|
|
)}
|
|
>
|
|
<Span
|
|
className={twMerge(
|
|
"absolute top-2 right-2 z-[100] cursor-pointer"
|
|
)}
|
|
onClick={(e) => {
|
|
const targetEl = e.target as HTMLElement;
|
|
const rootWrapperEl = targetEl.closest(".twui-toast-root");
|
|
|
|
if (rootWrapperEl) {
|
|
rootWrapperEl.parentElement?.removeChild(rootWrapperEl);
|
|
setOpen?.(false);
|
|
}
|
|
}}
|
|
>
|
|
<X size={15} />
|
|
</Span>
|
|
{props.children}
|
|
</Card>
|
|
);
|
|
|
|
React.useEffect(() => {
|
|
const wrapperEl = document.createElement("div");
|
|
|
|
wrapperEl.className = twMerge(
|
|
"fixed z-[200000] bottom-10 right-10",
|
|
"flex flex-col items-center justify-center",
|
|
"twui-toast-root"
|
|
);
|
|
|
|
document.body.appendChild(wrapperEl);
|
|
const root = createRoot(wrapperEl);
|
|
root.render(toastEl);
|
|
|
|
setTimeout(() => {
|
|
closeToast({ wrapperEl });
|
|
setOpen?.(false);
|
|
}, closeDelay);
|
|
|
|
return function () {
|
|
closeToast({ wrapperEl });
|
|
};
|
|
}, []);
|
|
|
|
return null;
|
|
}
|
|
|
|
function closeToast({ wrapperEl }: { wrapperEl: HTMLDivElement | null }) {
|
|
if (!wrapperEl) return;
|
|
wrapperEl.parentElement?.removeChild(wrapperEl);
|
|
}
|