57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useState, useCallback, type ReactNode } from "react";
|
|
import { Check, X, AlertTriangle } from "lucide-react";
|
|
|
|
interface Toast {
|
|
id: number;
|
|
message: string;
|
|
type: "success" | "error";
|
|
}
|
|
|
|
const ToastContext = createContext<{
|
|
toast: (message: string, type?: "success" | "error") => void;
|
|
}>({ toast: () => {} });
|
|
|
|
export function useToast() {
|
|
return useContext(ToastContext);
|
|
}
|
|
|
|
export function ToastProvider({ children }: { children: ReactNode }) {
|
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
let nextId = 0;
|
|
|
|
const toast = useCallback((message: string, type: "success" | "error" = "success") => {
|
|
const id = ++nextId;
|
|
setToasts((t) => [...t, { id, message, type }]);
|
|
setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2500);
|
|
}, []);
|
|
|
|
return (
|
|
<ToastContext.Provider value={{ toast }}>
|
|
{children}
|
|
<div className="fixed top-4 left-4 right-4 z-[100] flex flex-col items-center gap-2 pointer-events-none max-w-lg mx-auto">
|
|
{toasts.map((t) => (
|
|
<div
|
|
key={t.id}
|
|
className="card px-4 py-2.5 flex items-center gap-2 pointer-events-auto animate-slide-in"
|
|
style={{
|
|
background: t.type === "error" ? "rgba(255,59,48,0.15)" : "rgba(52,199,89,0.15)",
|
|
border: `1px solid ${t.type === "error" ? "rgba(255,59,48,0.3)" : "rgba(52,199,89,0.3)"}`,
|
|
}}
|
|
>
|
|
{t.type === "error" ? (
|
|
<AlertTriangle size={14} style={{ color: "#ff3b30" }} />
|
|
) : (
|
|
<Check size={14} style={{ color: "#34c759" }} />
|
|
)}
|
|
<span className="text-[13px]" style={{ color: t.type === "error" ? "#ff3b30" : "#34c759" }}>
|
|
{t.message}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ToastContext.Provider>
|
|
);
|
|
}
|