new-personal-site/components/lib/elements/Toast.tsx
Benjamin Toby 8762e2da8d Updates
2025-03-27 07:37:16 +01:00

119 lines
3.4 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;
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) {
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"
)}
onMouseEnter={() => {
window.clearTimeout(timeout);
}}
onMouseLeave={(e) => {
const targetEl = e.target as HTMLElement;
const rootWrapperEl = targetEl.closest(
".twui-toast-root"
) as HTMLDivElement | null;
timeout = setTimeout(() => {
closeToast({ wrapperEl: rootWrapperEl });
setOpen?.(false);
}, closeDelay);
}}
>
<Span
className={twMerge(
"absolute top-2 right-2 z-[100] cursor-pointer",
"text-white"
)}
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>
<Span className={twMerge("text-white")}>{props.children}</Span>
</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);
timeout = 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);
}