toast messages
This commit is contained in:
111
components/toast/index.tsx
Normal file
111
components/toast/index.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
"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>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user