/** @jsxImportSource react */ import { useCallback, useMemo, useRef, useState, type ReactNode } from "react"; import { useActionShortcut } from "../../../ui"; import { ToastHostProvider, type ToastHost, type ToastOptions } from "../../../ui/toast"; import { WebButton } from "./desktop/controls"; import { WebIconButton } from "./desktop/icons"; type WebToastType = "info" | "success" | "error"; interface ToastEntry { id: number; body: string; type: WebToastType; title?: string; subtitle?: string; action?: ToastOptions["action"]; secondaryAction?: ToastOptions["secondaryAction"]; } let nextToastId = 1; function ToastViewport({ toasts, dismiss, }: { toasts: ToastEntry[]; dismiss: (id: number) => void; }) { const actionShortcut = useActionShortcut("notification-action"); const dismissShortcut = useActionShortcut("notification-dismiss"); const newestActionId = [...toasts].reverse().find((toast) => toast.action)?.id; return (
{toasts.map((toast) => { const activate = () => { if (!toast.action) return; try { toast.action.onClick(); } finally { dismiss(toast.id); } }; return (
); })}
); } export function WebToastHostProvider({ children }: { children: ReactNode }) { const [toasts, setToasts] = useState([]); const toastsRef = useRef(toasts); toastsRef.current = toasts; const dismiss = useCallback((id: string | number) => { setToasts((current) => current.filter((toast) => toast.id !== id)); }, []); const push = useCallback((type: ToastEntry["type"], body: string, options?: ToastOptions) => { const id = nextToastId++; setToasts((current) => [...current, { id, type, body, title: options?.title, subtitle: options?.subtitle, action: options?.action, secondaryAction: options?.secondaryAction, }]); if (options?.duration !== 0) { setTimeout(() => dismiss(id), options?.duration ?? 4500); } return id; }, [dismiss]); const host = useMemo(() => ({ Viewport() { return ; }, success: (body, options) => push("success", body, options), error: (body, options) => push("error", body, options), info: (body, options) => push("info", body, options), dismiss, activateNewest() { const toast = [...toastsRef.current].reverse().find((entry) => entry.action); if (!toast?.action) return false; try { toast.action.onClick(); } finally { dismiss(toast.id); } return true; }, dismissNewest() { const toast = toastsRef.current.at(-1); if (!toast) return false; dismiss(toast.id); return true; }, }), [dismiss, push, toasts]); return ( {children} ); }