import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Alert, Button, GlobalStyles, Snackbar } from "@mui/material"; type Severity = "success" | "info" | "warning" | "error"; type Toast = { open: boolean; message: string; severity: Severity; actionLabel?: string; onAction?: () => void; }; type ToastApi = { toast: ( message: string, severity?: Severity, action?: { label: string; onClick: () => void }, ) => void; }; const ToastContext = createContext(null); export function ToastProvider({ children }: { children: React.ReactNode }) { const [t, setT] = useState({ open: false, message: "", severity: "info" }); const [element, setElement] = useState(null); const [clearance, setClearance] = useState(0); useEffect(() => { if (!element) { setClearance(0); return; } // Reserve the actual wrapped message height, bottom offset and a 24px gap. const measure = () => setClearance(Math.ceil(element.getBoundingClientRect().height) + 48); measure(); if (typeof ResizeObserver === "undefined") return; const observer = new ResizeObserver(measure); observer.observe(element); return () => observer.disconnect(); }, [element]); const toast = useCallback( (message: string, severity: Severity = "info", action?: { label: string; onClick: () => void }) => { setT({ open: true, message, severity, actionLabel: action?.label, onAction: action?.onClick, }); }, [], ); const value = useMemo(() => ({ toast }), [toast]); return ( {children} setT((p) => ({ ...p, open: false }))} anchorOrigin={{ vertical: "bottom", horizontal: "right" }} sx={{ bottom: { xs: "calc(8px + env(safe-area-inset-bottom, 0px))", sm: "calc(24px + env(safe-area-inset-bottom, 0px))" } }} > setT((p) => ({ ...p, open: false }))} severity={t.severity} variant="filled" sx={{ maxWidth: 420, borderRadius: 3, fontWeight: 600 }} action={ t.actionLabel && t.onAction ? ( ) : undefined } > {t.message} ); } export function useToast(): ToastApi { const v = useContext(ToastContext); if (!v) throw new Error("useToast must be used within ToastProvider"); return v; } export function useOptionalToast() { return useContext(ToastContext); }