import { createContext, useContext, useMemo, useRef, useSyncExternalStore, type ReactNode } from "react"; import { ToastNotification } from "@carbon/react"; import { createToastStore, type ToastStore } from "../lib/toastStore"; export interface ToastApi { success: (title: string, subtitle?: string) => number; info: (title: string, subtitle?: string) => number; warning: (title: string, subtitle?: string) => number; /** Errors stay until the user closes them. */ error: (title: string, subtitle?: string) => number; dismiss: (id: number) => void; } const ToastContext = createContext(null); function apiFor(store: ToastStore): ToastApi { return { success: (t, s) => store.add("success", t, s), info: (t, s) => store.add("info", t, s), warning: (t, s) => store.add("warning", t, s), error: (t, s) => store.add("error", t, s), dismiss: store.dismiss, }; } /** Stacked top-right toasts. Place it inside the Carbon so the tokens apply. */ export function ToastProvider({ children }: { children: ReactNode }) { const storeRef = useRef(null); if (!storeRef.current) storeRef.current = createToastStore(); const store = storeRef.current; const toasts = useSyncExternalStore(store.subscribe, store.getSnapshot); const api = useMemo(() => apiFor(store), [store]); return ( {children} {/* The live region stays mounted so screen readers announce toasts as they are added. */}
{toasts.map((t) => ( { store.dismiss(t.id); return false; }} onCloseButtonClick={() => store.dismiss(t.id)} /> ))}
); } export function useToast(): ToastApi { const api = useContext(ToastContext); if (!api) throw new Error("useToast must be used inside "); return api; }