feat: initial

This commit is contained in:
2026-06-03 03:08:57 +03:30
parent 3d9585ac5c
commit 6fa30eb29a
45 changed files with 7053 additions and 86 deletions
+71
View File
@@ -0,0 +1,71 @@
"use client";
import {
createContext,
useCallback,
useContext,
useRef,
useState,
} from "react";
import { cn } from "@/lib/utils";
type ToastKind = "success" | "error" | "info";
interface Toast {
id: number;
kind: ToastKind;
message: string;
}
interface ToastApi {
push: (message: string, kind?: ToastKind) => void;
success: (message: string) => void;
error: (message: string) => void;
}
const ToastContext = createContext<ToastApi | null>(null);
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const idRef = useRef(0);
const push = useCallback((message: string, kind: ToastKind = "info") => {
const id = ++idRef.current;
setToasts((prev) => [...prev, { id, kind, message }]);
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, 4000);
}, []);
const api: ToastApi = {
push,
success: (m) => push(m, "success"),
error: (m) => push(m, "error"),
};
return (
<ToastContext.Provider value={api}>
{children}
<div className="fixed bottom-4 left-4 z-[100] flex flex-col gap-2">
{toasts.map((t) => (
<div
key={t.id}
className={cn(
"min-w-64 rounded-xl px-4 py-3 text-sm text-white shadow-lg",
t.kind === "success" && "bg-success",
t.kind === "error" && "bg-danger",
t.kind === "info" && "bg-foreground",
)}
>
{t.message}
</div>
))}
</div>
</ToastContext.Provider>
);
}
export function useToast(): ToastApi {
const ctx = useContext(ToastContext);
if (!ctx) throw new Error("useToast must be used within <ToastProvider>");
return ctx;
}