UNPKG

sonner-native

Version:

An opinionated toast component for React Native. A port of @emilkowalski's sonner.

235 lines (233 loc) 8.18 kB
"use strict"; import * as React from 'react'; import { Platform } from 'react-native'; import { FullWindowOverlay } from 'react-native-screens'; import { toastDefaultValues } from "./constants.js"; import { ToastContext } from "./context.js"; import { Positioner } from "./positioner.js"; import { Toast } from "./toast.js"; import { areToastsEqual } from "./toast-comparator.js"; import { ANIMATION_DURATION } from "./animations.js"; import { jsx as _jsx } from "react/jsx-runtime"; import { createElement as _createElement } from "react"; let addToastHandler; let dismissToastHandler; let wiggleHandler; export const Toaster = ({ ToasterOverlayWrapper, ...toasterProps }) => { const toastsCounter = React.useRef(1); const toastRefs = React.useRef({}); const [toasts, setToasts] = React.useState([]); const [toastsVisible, setToastsVisible] = React.useState(false); React.useLayoutEffect(() => { if (toasts.length > 0) { setToastsVisible(true); return; } // let the animation finish const timeout = setTimeout(() => { setToastsVisible(false); }, ANIMATION_DURATION); return () => clearTimeout(timeout); }, [toasts.length]); const props = React.useMemo(() => { return { ...toasterProps, toasts, setToasts, toastsCounter, toastRefs }; }, [toasterProps, toasts]); if (!toastsVisible) { return /*#__PURE__*/_jsx(ToasterUI, { ...props }); } if (ToasterOverlayWrapper) { return /*#__PURE__*/_jsx(ToasterOverlayWrapper, { children: /*#__PURE__*/_jsx(ToasterUI, { ...props }) }); } if (Platform.OS === 'ios') { return /*#__PURE__*/_jsx(FullWindowOverlay, { children: /*#__PURE__*/_jsx(ToasterUI, { ...props }) }); } return /*#__PURE__*/_jsx(ToasterUI, { ...props }); }; export const ToasterUI = ({ duration = toastDefaultValues.duration, position = toastDefaultValues.position, offset = toastDefaultValues.offset, visibleToasts = toastDefaultValues.visibleToasts, swipeToDismissDirection = toastDefaultValues.swipeToDismissDirection, closeButton, invert, toastOptions = {}, icons, pauseWhenPageIsHidden, gap, theme, autoWiggleOnUpdate, richColors, toasts, setToasts, toastsCounter, toastRefs, ToastWrapper, ...props }) => { addToastHandler = React.useCallback(options => { const id = typeof options?.id === 'number' || options.id && options.id?.length > 0 ? options.id : toastsCounter.current++; setToasts(currentToasts => { const newToast = { ...options, id: options?.id ?? id, variant: options.variant ?? toastDefaultValues.variant }; const existingToast = currentToasts.find(currentToast => currentToast.id === newToast.id); const shouldUpdate = existingToast && options?.id; if (shouldUpdate) { const shouldWiggle = autoWiggleOnUpdate === 'always' || autoWiggleOnUpdate === 'toast-change' && !areToastsEqual(newToast, existingToast); if (shouldWiggle && options.id) { wiggleHandler(options.id); } return currentToasts.map(currentToast => { if (currentToast.id === options.id) { return { ...currentToast, ...newToast, duration: options.duration ?? duration, id: options.id }; } return currentToast; }); } else { const newToasts = [...currentToasts, newToast]; if (!(newToast.id in toastRefs.current)) { toastRefs.current[newToast.id] = /*#__PURE__*/React.createRef(); } if (newToasts.length > visibleToasts) { newToasts.shift(); } return newToasts; } }); return id; }, [toastsCounter, toastRefs, visibleToasts, duration, autoWiggleOnUpdate, setToasts]); const dismissToast = React.useCallback((id, origin) => { if (!id) { toasts.forEach(currentToast => { if (origin === 'onDismiss') { currentToast.onDismiss?.(currentToast.id); } else { currentToast.onAutoClose?.(currentToast.id); } }); setToasts([]); toastsCounter.current = 1; return; } setToasts(currentToasts => currentToasts.filter(currentToast => currentToast.id !== id)); const toastForCallback = toasts.find(currentToast => currentToast.id === id); if (origin === 'onDismiss') { toastForCallback?.onDismiss?.(id); } else { toastForCallback?.onAutoClose?.(id); } return id; }, [setToasts, toasts, toastsCounter]); dismissToastHandler = React.useCallback(id => { return dismissToast(id); }, [dismissToast]); wiggleHandler = React.useCallback(id => { const toastRef = toastRefs.current[id]; if (toastRef && toastRef.current) { toastRef.current.wiggle(); } }, [toastRefs]); const { unstyled } = toastOptions; const value = React.useMemo(() => ({ duration: duration ?? toastDefaultValues.duration, position: position ?? toastDefaultValues.position, offset: offset ?? toastDefaultValues.offset, swipeToDismissDirection: swipeToDismissDirection ?? toastDefaultValues.swipeToDismissDirection, closeButton: closeButton ?? toastDefaultValues.closeButton, unstyled: unstyled ?? toastDefaultValues.unstyled, addToast: addToastHandler, invert: invert ?? toastDefaultValues.invert, icons: icons ?? {}, pauseWhenPageIsHidden: pauseWhenPageIsHidden ?? toastDefaultValues.pauseWhenPageIsHidden, gap: gap ?? toastDefaultValues.gap, theme: theme ?? toastDefaultValues.theme, toastOptions, autoWiggleOnUpdate: autoWiggleOnUpdate ?? toastDefaultValues.autoWiggleOnUpdate, richColors: richColors ?? toastDefaultValues.richColors }), [duration, position, offset, swipeToDismissDirection, closeButton, unstyled, invert, icons, pauseWhenPageIsHidden, gap, theme, toastOptions, autoWiggleOnUpdate, richColors]); const orderToastsFromPosition = React.useCallback(currentToasts => { return position === 'bottom-center' ? currentToasts : currentToasts.slice().reverse(); }, [position]); const onDismiss = React.useCallback(id => { dismissToast(id, 'onDismiss'); }, [dismissToast]); const onAutoClose = React.useCallback(id => { dismissToast(id, 'onAutoClose'); }, [dismissToast]); const allPositions = React.useMemo(() => { return ['top-center', 'bottom-center', 'center']; }, []); const possiblePositions = React.useMemo(() => { return allPositions.filter(possiblePossition => { return toasts.find(positionedToast => positionedToast.position === possiblePossition) || value.position === possiblePossition; }); }, [allPositions, toasts, value.position]); const orderedToasts = React.useMemo(() => { return orderToastsFromPosition(toasts); }, [toasts, orderToastsFromPosition]); return /*#__PURE__*/_jsx(ToastContext.Provider, { value: value, children: possiblePositions.map((currentPosition, positionIndex) => /*#__PURE__*/_jsx(Positioner, { position: currentPosition, children: orderedToasts.filter(possibleToast => !possibleToast.position && positionIndex === 0 || possibleToast.position === currentPosition).map(toastToRender => { const ToastToRender = /*#__PURE__*/_createElement(Toast, { ...toastToRender, onDismiss: onDismiss, onAutoClose: onAutoClose, ref: toastRefs.current[toastToRender.id], key: toastToRender.id, ...props }); if (ToastWrapper) { return /*#__PURE__*/_jsx(ToastWrapper, { toastId: toastToRender.id, children: ToastToRender }, toastToRender.id); } return ToastToRender; }) }, currentPosition)) }); }; export const getToastContext = () => { if (!addToastHandler || !dismissToastHandler || !wiggleHandler) { throw new Error('ToastContext is not initialized'); } return { addToast: addToastHandler, dismissToast: dismissToastHandler, wiggleToast: wiggleHandler }; }; //# sourceMappingURL=toaster.js.map