new-personal-site/components/lib/elements/Toast.tsx

101 lines
2.8 KiB
TypeScript
Raw Normal View History

2025-01-05 06:25:38 +00:00
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);
}