UNPKG

@pdftron/webviewer-react-toolkit

Version:

A React component library for integrating with PDFTron WebViewer API.

107 lines (106 loc) 4.95 kB
import classnames from 'classnames'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ToastContext, useCurrentRef } from '../../hooks'; import { Overlay } from '../Overlay'; import { Toast } from '../Toast'; let toastIdSequence = 1; export const ToastProvider = ({ defaultTimeout, noTimeout, position = 'top-right', customPadding, className, children, }) => { const [toasts, setToasts] = useState([]); const [closing, setClosing] = useState(false); const [hovered, setHovered] = useState(false); const onHover = useCallback(() => setHovered(true), []); const onBlur = useCallback(() => setHovered(false), []); const _pop = useCallback(() => setClosing(true), []); const toastsRef = useCurrentRef(toasts); const closingRef = useCurrentRef(closing); useEffect(() => { if (closing) { const timeoutId = window.setTimeout(() => { setToasts((prev) => prev.slice(1)); setClosing(false); }, 250); return () => { window.clearTimeout(timeoutId); }; } return; }, [closing]); const { toastId, closable = true, timeout, ...toastProps } = toasts[0] || {}; const noTimeoutTypes = useCurrentRef(noTimeout); const timeoutValue = useMemo(() => { const timeoutNum = timeout ?? defaultTimeout; if (!noTimeoutTypes.current) return timeoutNum; if (Array.isArray(noTimeoutTypes.current)) { if (!noTimeoutTypes.current.length) return timeoutNum; if (!noTimeoutTypes.current.includes(toastProps.message)) return timeoutNum; return 0; } else { if (noTimeoutTypes.current !== toastProps.message) return timeoutNum; return 0; } }, [defaultTimeout, noTimeoutTypes, timeout, toastProps.message]); useEffect(() => { // toastId and toastProps.message are included to reset timer when message // changes. if (!hovered && toastId && timeoutValue && toastProps.message) { const timeoutId = window.setTimeout(_pop, timeoutValue); return () => window.clearTimeout(timeoutId); } return; }, [_pop, hovered, timeoutValue, toastId, toastProps.message]); const add = useCallback((toast) => { const toastId = toastIdSequence++; setToasts((prev) => [...prev, { ...toast, toastId }]); return toastId; }, []); const remove = useCallback((toastId) => { if (!toastsRef.current.length) return; const index = toastId === undefined ? 0 : toastsRef.current.findIndex((toast) => toast.toastId === toastId); if (index === -1) return; if (index === 0) return _pop(); setToasts((prev) => [...prev.slice(0, index), ...prev.slice(index + 1)]); }, [_pop, toastsRef]); const modify = useCallback((id, update) => { setToasts((prev) => { const index = prev.findIndex((t) => t.toastId === id); if (index === -1) return prev; return [...prev.slice(0, index), { ...prev[index], ...update }, ...prev.slice(index + 1)]; }); }, []); const exists = useCallback((id) => { const index = toastsRef.current.findIndex((t) => t.toastId === id); if (index === -1) return false; if (closingRef.current && index === 0) return false; return true; }, [closingRef, toastsRef]); const value = useMemo(() => ({ add, remove, modify, exists }), [add, remove, modify, exists]); const padding = useMemo(() => { if (customPadding === undefined) return undefined; const isTop = ['top-left', 'top', 'top-right'].includes(position); return customPadding !== undefined ? { paddingTop: isTop ? customPadding : undefined, paddingBottom: isTop ? undefined : customPadding, } : undefined; }, [customPadding, position]); const toastProviderClass = classnames('ui__toastProvider', { 'ui__toastProvider--closing': closing }); const toastClass = classnames('ui__toastProvider__toast', `ui__toastProvider__toast--position-${position}`, className); return (React.createElement(ToastContext.Provider, { value: value }, toastId && (React.createElement(Overlay, null, React.createElement("div", { className: toastClass, key: toastId, style: padding }, React.createElement(Toast, Object.assign({}, toastProps, { role: toastProps.message === 'error' ? 'alert' : 'status', "aria-live": toastProps.message === 'error' ? 'assertive' : 'polite', onMouseEnter: onHover, onMouseLeave: onBlur, className: toastProviderClass, onClose: closable ? _pop : undefined }))))), children)); };