108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
import Card from "./Card";
|
|
import { X } from "lucide-react";
|
|
import ReactDOM from "react-dom";
|
|
import Span from "../layout/Span";
|
|
|
|
export const ToastStyles = ["normal", "success", "error"] as const;
|
|
export const ToastColors = ToastStyles;
|
|
|
|
export type TWUIToastProps = DetailedHTMLProps<
|
|
HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
> & {
|
|
open?: boolean;
|
|
setOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
closeDelay?: number;
|
|
color?: (typeof ToastStyles)[number];
|
|
};
|
|
|
|
let interval: any;
|
|
let timeout: any;
|
|
|
|
/**
|
|
* # 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
|
|
}: TWUIToastProps) {
|
|
const [ready, setReady] = React.useState(false);
|
|
const IDName = "twui-toast-root";
|
|
|
|
React.useEffect(() => {
|
|
const toastRoot = document.getElementById(IDName);
|
|
|
|
if (toastRoot) {
|
|
setReady(true);
|
|
} else {
|
|
const newToastRootEl = document.createElement("div");
|
|
newToastRootEl.id = IDName;
|
|
document.body.appendChild(newToastRootEl);
|
|
setReady(true);
|
|
}
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
if (!ready || !open) return;
|
|
|
|
timeout = setTimeout(() => {
|
|
setOpen?.(false);
|
|
}, closeDelay);
|
|
|
|
return function () {
|
|
setOpen?.(false);
|
|
};
|
|
}, [ready, open]);
|
|
|
|
if (!ready) return null;
|
|
if (!open) return null;
|
|
|
|
return ReactDOM.createPortal(
|
|
<Card
|
|
{...props}
|
|
className={twMerge(
|
|
"fixed bottom-4 right-4 z-[250] border-none",
|
|
"pl-6 pr-8 py-4 bg-primary dark:bg-primary-dark",
|
|
color == "success"
|
|
? "bg-success dark:bg-success-dark twui-toast-success"
|
|
: color == "error"
|
|
? "bg-error dark:bg-error-dark twui-toast-error"
|
|
: "",
|
|
props.className,
|
|
"twui-toast"
|
|
)}
|
|
onMouseEnter={() => {
|
|
window.clearTimeout(timeout);
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
timeout = setTimeout(() => {
|
|
setOpen?.(false);
|
|
}, closeDelay);
|
|
}}
|
|
>
|
|
<Span
|
|
className={twMerge(
|
|
"absolute top-2 right-2 z-[100] cursor-pointer",
|
|
"text-white"
|
|
)}
|
|
onClick={(e) => {
|
|
setOpen?.(false);
|
|
}}
|
|
>
|
|
<X size={15} />
|
|
</Span>
|
|
<Span className={twMerge("text-white")}>{props.children}</Span>
|
|
</Card>,
|
|
document.getElementById(IDName) as HTMLElement
|
|
);
|
|
}
|