import type { ReactNode } from 'react'; import React, { createContext, useContext, useMemo, useRef, useState } from 'react'; import type { ShowToastRequest } from '../../plugin-runtime-types/src'; import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent'; import type { ToastProps } from './core/Toast'; import { Toast } from './core/Toast'; import { generateId } from '../lib/generateId'; import { Portal } from './Portal'; import { AnimatePresence } from 'framer-motion'; type ToastEntry = { id?: string; message: ReactNode; timeout?: 3000 | 5000 | 8000 | null; onClose?: ToastProps['onClose']; } & Omit; type PrivateToastEntry = ToastEntry & { id: string; timeout: number | null; }; interface State { toasts: PrivateToastEntry[]; actions: Actions; } interface Actions { show: (d: ToastEntry) => void; hide: (id: string) => void; } // eslint-disable-next-line @typescript-eslint/no-explicit-any export const ToastContext = createContext({} as State); export const ToastProvider = ({ children }: { children: React.ReactNode }) => { const [toasts, setToasts] = useState([]); const timeoutRef = useRef(); const actions = useMemo( () => ({ show({ id, timeout = 5000, ...props }: ToastEntry) { id = id ?? generateId(); if (timeout != null) { timeoutRef.current = setTimeout(() => this.hide(id), timeout); } setToasts((a) => { if (a.some((v) => v.id === id)) { // It's already visible with this id return a; } return [...a, { id, timeout, ...props }]; }); return id; }, hide: (id: string) => { setToasts((all) => { const t = all.find((t) => t.id === id); t?.onClose?.(); return all.filter((t) => t.id !== id); }); }, }), [], ); useListenToTauriEvent('show_toast', (event) => { actions.show({ ...event.payload }); }); const state: State = { toasts, actions }; return {children}; }; function ToastInstance({ id, message, timeout, ...props }: PrivateToastEntry) { const { actions } = useContext(ToastContext); return ( actions.hide(id)} > {message} ); } export const useToast = () => useContext(ToastContext).actions; export const Toasts = () => { const { toasts } = useContext(ToastContext); return (
{toasts.map((props: PrivateToastEntry) => ( ))}
); };