"use client"; import * as React from "react"; import { AnimatePresence, motion } from "framer-motion"; import { AlertCircle, CheckCircle2, X } from "lucide-react"; import { cn } from "@/lib/utils"; import { useMotionOK, spring } from "@/lib/motion"; type Toast = { id: number; msg: string; err?: boolean; action?: Action }; type Action = { label: string; run: () => void }; type Ctx = (msg: string, err?: boolean, action?: Action) => void; const ToastCtx = React.createContext(() => {}); export const useToast = () => React.useContext(ToastCtx); /** An error you cannot read in time is the same as no error at all. */ const DWELL_OK = 3400; const DWELL_ERR = 7000; /** Beyond this the stack is a wall, and the oldest is the least relevant. */ const MAX = 3; export function ToastProvider({ children }: { children: React.ReactNode }) { const [items, setItems] = React.useState([]); const ok = useMotionOK(); const drop = React.useCallback( (id: number) => setItems((x) => x.filter((t) => t.id !== id)), [], ); const push = React.useCallback( (msg, err, action) => { const id = Date.now() + Math.random(); setItems((x) => [...x, { id, msg, err, action }].slice(-MAX)); setTimeout(() => drop(id), err ? DWELL_ERR : DWELL_OK); }, [drop], ); return ( {children} {/* aria-live so a screen reader hears "Queued" or a failure at all — these were previously silent, which made the only confirmation that a request had been accepted a purely visual one. */}
{items.map((t) => ( {t.err ? ( ) : ( )} {t.msg} {t.action && ( )} ))}
); }