fiddy/apps/web/hooks/notifications-context.tsx

84 lines
2.6 KiB
TypeScript

"use client";
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
import NotificationsToaster, { type NotificationItem, type NotificationTone } from "@/shared/components/feedback/notifications-toaster";
type NotifyInput = {
title: string;
message?: string;
tone?: NotificationTone;
durationMs?: number;
};
type NotificationsContextValue = {
notify: (input: NotifyInput) => void;
};
const NotificationsContext = createContext<NotificationsContextValue | null>(null);
function createId() {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
export function NotificationsProvider({ children }: { children: React.ReactNode }) {
const [items, setItems] = useState<NotificationItem[]>([]);
const timersRef = useRef<number[]>([]);
function dismiss(id: string) {
setItems(prev => prev.map(item => item.id === id ? { ...item, closing: true } : item));
const t = window.setTimeout(() => {
setItems(prev => prev.filter(item => item.id !== id));
}, 250);
timersRef.current.push(t);
}
function notify(input: NotifyInput) {
const id = createId();
const durationMs = Math.max(1200, input.durationMs ?? 4200);
const tone = input.tone ?? "info";
setItems(prev => [
...prev,
{
id,
title: input.title,
message: input.message,
tone,
closing: false
}
]);
const fadeMs = 250;
const t1 = window.setTimeout(() => {
setItems(prev => prev.map(item => item.id === id ? { ...item, closing: true } : item));
}, Math.max(0, durationMs - fadeMs));
const t2 = window.setTimeout(() => {
setItems(prev => prev.filter(item => item.id !== id));
}, durationMs);
timersRef.current.push(t1, t2);
}
useEffect(() => {
return () => {
timersRef.current.forEach(timer => window.clearTimeout(timer));
timersRef.current = [];
};
}, []);
const value = useMemo(() => ({ notify }), []);
return (
<NotificationsContext.Provider value={value}>
{children}
<NotificationsToaster items={items} onDismiss={dismiss} />
</NotificationsContext.Provider>
);
}
export function useNotificationsContext() {
const ctx = useContext(NotificationsContext);
if (!ctx) throw new Error("NotificationsProvider is missing");
return ctx;
}