'use client'; import * as React from 'react'; import { AppState } from 'react-native'; import { useOnMount } from '../../hooks/useOnMount'; import { useRefWithInit } from '../../hooks/useRefWithInit'; import { useIsoLayoutEffect } from '../../hooks/useIsoLayoutEffect'; import { ToastContext } from './ToastProviderContext'; import type { ToastManager } from '../createToastManager'; import { ToastStore } from '../store'; /** * Provides a context for creating and managing toasts. */ export const ToastProvider: React.FC = function ToastProvider(props) { const { children, timeout = 5000, limit = 3, toastManager } = props; const store = useRefWithInit( () => new ToastStore({ timeout, limit, toasts: [], pressed: false, focused: false, isAppActive: AppState.currentState !== 'background', viewportOrigin: { x: 0, y: 0 }, }), ).current; useOnMount(store.disposeEffect); // Upstream pauses toast timers while the window is unfocused. The React Native // counterpart is the app being backgrounded — a toast must not spend its five // seconds where nobody can see it. React.useEffect(() => { const subscription = AppState.addEventListener('change', (nextAppState) => { const isAppActive = nextAppState !== 'background'; store.set('isAppActive', isAppActive); if (isAppActive) { if (!store.select('expanded')) { store.resumeTimers(); } } else { store.pauseTimers(); } }); return () => subscription.remove(); }, [store]); React.useEffect( function subscribeToToastManager() { if (!toastManager) { return undefined; } return toastManager[' subscribe'](({ action, options }) => { const id = options.id; if (action === 'promise' && options.promise) { store.promiseToast(options.promise, options); } else if (action === 'update' && id) { store.updateToast(id, options); } else if (action === 'close') { store.closeToast(id); } else { store.addToast(options); } }); }, [store, toastManager], ); // `limit` needs custom syncing because changing it must also recompute each // toast's `limited` flag; `useSyncedValues` would only update the raw value. useIsoLayoutEffect(() => { store.syncProviderProps(timeout, limit); }, [store, timeout, limit]); return {children}; }; export interface ToastProviderState {} export interface ToastProviderProps { children?: React.ReactNode; /** * The default amount of time (in ms) before a toast is auto dismissed. * A value of `0` will prevent the toast from being dismissed automatically. * @default 5000 */ timeout?: number | undefined; /** * The maximum number of toasts that can be displayed at once. * When the limit is exceeded, the oldest toasts are marked `limited` on their * state rather than removed, so they can be hidden or animated out. * @default 3 */ limit?: number | undefined; /** * A global manager for toasts to use outside of a React component. */ toastManager?: ToastManager | undefined; } export namespace ToastProvider { export type State = ToastProviderState; export type Props = ToastProviderProps; }