2024-08-15 04:11:57 -06:00

112 lines
2.4 KiB
TypeScript

"use client";
import { Portal } from "@/lib/portal/components";
import { atom, useAtom } from "jotai";
import React, { useCallback, useEffect, useState } from "react";
import { ReactNode } from "react";
type toastMessage = {
msg: ReactNode;
type?: "error" | "default";
fading: boolean;
duration?: number;
};
type IDToastMessage = toastMessage & {
id: string;
};
const toastAtom = atom<IDToastMessage[]>([]);
export function useToast() {
const [_, setToasts] = useAtom(toastAtom);
const createToast = useCallback(
(t: toastMessage) => {
const idd = { ...t, id: crypto.randomUUID() };
setToasts((toasts) => {
return [...toasts, idd];
});
return idd;
},
[setToasts]
);
const clearToast = useCallback(
(t: toastMessage) => setToasts((toasts) => toasts.filter((to) => to != t)),
[setToasts]
);
return {
createToast,
clearToast,
};
}
export function Toaster() {
const [toasts, setToasts] = useAtom(toastAtom);
const clearToast = useCallback(
(t: toastMessage) => {
setToasts((toasts) => {
return toasts.filter((to) => to !== t);
});
},
[setToasts]
);
if (!toasts.length) return <></>;
return (
<Portal>
<div className="fixed bottom-12 left-1/2 -translate-x-1/2 max-w-[95vw] flex flex-col gap-4">
{toasts.map((t) => (
<Toast key={"toast " + t.id} toast={t} clearToast={clearToast} />
))}
</div>
</Portal>
);
}
function Toast(props: {
toast: toastMessage;
clearToast: (t: toastMessage) => void;
}) {
const { toast, clearToast } = props;
const [fading, setFading] = useState(false);
const clear = useCallback(() => {
setFading(true);
setTimeout(() => {
clearToast(toast);
}, 300);
}, [clearToast, toast]);
const fadeOut = useCallback(() => {
setTimeout(clear, toast.duration ?? 3000);
}, [clear, toast]);
useEffect(() => {
if (!toast.fading) return;
fadeOut();
}, [fadeOut, toast]);
return (
<div
data-fading={fading}
data-type={toast.type}
className="relative p-6 px-16 toast data-[fading=true]:fade-toast rounded-md bg-mixed-300 data-[type=error]:bg-red-900 border-2 border-mixed-400 data-[type=error]:border-red-700"
>
{toast.msg}
{!toast.fading && (
<button
className="top-2 right-2 text-xs absolute"
onClick={() => clear()}
>
Dismiss
</button>
)}
</div>
);
}