forked from baron/baron-sso
refactor(adminfront): replace sonner with custom use-toast matching devfront UX policy
This commit is contained in:
35
adminfront/src/components/ui/toaster.tsx
Normal file
35
adminfront/src/components/ui/toaster.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { AlertCircle, CheckCircle2, Info } from "lucide-react";
|
||||
import { cn } from "../../lib/utils";
|
||||
import { useToastState } from "./use-toast";
|
||||
|
||||
export function Toaster() {
|
||||
const toasts = useToastState();
|
||||
|
||||
if (toasts.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-4 z-[100] flex flex-col gap-2 w-full max-w-[320px]">
|
||||
{toasts.map((t) => (
|
||||
<div
|
||||
key={t.id}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-lg border p-4 shadow-lg animate-in slide-in-from-right-full duration-300",
|
||||
t.type === "success" &&
|
||||
"bg-emerald-50 border-emerald-200 text-emerald-800 dark:bg-emerald-950 dark:border-emerald-800 dark:text-emerald-200",
|
||||
t.type === "error" &&
|
||||
"bg-rose-50 border-rose-200 text-rose-800 dark:bg-rose-950 dark:border-rose-800 dark:text-rose-200",
|
||||
t.type === "info" &&
|
||||
"bg-blue-50 border-blue-200 text-blue-800 dark:bg-blue-950 dark:border-blue-800 dark:text-blue-200",
|
||||
)}
|
||||
>
|
||||
{t.type === "success" && (
|
||||
<CheckCircle2 className="h-5 w-5 shrink-0" />
|
||||
)}
|
||||
{t.type === "error" && <AlertCircle className="h-5 w-5 shrink-0" />}
|
||||
{t.type === "info" && <Info className="h-5 w-5 shrink-0" />}
|
||||
<p className="text-sm font-medium leading-none">{t.message}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
60
adminfront/src/components/ui/use-toast.ts
Normal file
60
adminfront/src/components/ui/use-toast.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as React from "react";
|
||||
|
||||
type ToastType = "success" | "error" | "info";
|
||||
|
||||
interface Toast {
|
||||
id: string;
|
||||
message: string;
|
||||
type: ToastType;
|
||||
}
|
||||
|
||||
let subscribers: ((toasts: Toast[]) => void)[] = [];
|
||||
let toasts: Toast[] = [];
|
||||
|
||||
const notify = () => {
|
||||
for (const sub of subscribers) {
|
||||
sub(toasts);
|
||||
}
|
||||
};
|
||||
|
||||
const toastBase = (message: string, type: ToastType = "success") => {
|
||||
const id = Math.random().toString(36).substring(2, 9);
|
||||
toasts = [...toasts, { id, message, type }];
|
||||
notify();
|
||||
|
||||
setTimeout(() => {
|
||||
toasts = toasts.filter((t) => t.id !== id);
|
||||
notify();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
export const toast = Object.assign(toastBase, {
|
||||
success: (message: string, options?: { description?: string }) => {
|
||||
const finalMessage = options?.description ? `${message}
|
||||
${options.description}` : message;
|
||||
toastBase(finalMessage, "success");
|
||||
},
|
||||
error: (message: string, options?: { description?: string }) => {
|
||||
const finalMessage = options?.description ? `${message}
|
||||
${options.description}` : message;
|
||||
toastBase(finalMessage, "error");
|
||||
},
|
||||
info: (message: string, options?: { description?: string }) => {
|
||||
const finalMessage = options?.description ? `${message}
|
||||
${options.description}` : message;
|
||||
toastBase(finalMessage, "info");
|
||||
}
|
||||
});
|
||||
|
||||
export const useToastState = () => {
|
||||
const [state, setState] = React.useState<Toast[]>(toasts);
|
||||
|
||||
React.useEffect(() => {
|
||||
subscribers.push(setState);
|
||||
return () => {
|
||||
subscribers = subscribers.filter((sub) => sub !== setState);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
};
|
||||
Reference in New Issue
Block a user