UNPKG

@backpackapp-io/react-native-toast

Version:

A toasting library for React Native. Built in features such as swipe to dismiss, multiple toasts, & no context power this library.

233 lines (231 loc) 9.37 kB
"use strict"; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Platform, Pressable, Text, useColorScheme, useWindowDimensions, View } from 'react-native'; import Animated, { Easing, ReduceMotion, runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Directions, Gesture, GestureDetector } from 'react-native-gesture-handler'; import { DismissReason } from '../core/types'; import { resolveValue, ToastPosition } from '../core/types'; import { colors, ConstructShadow, useVisibilityChange } from '../utils'; import { toast as toasting } from '../headless'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); const DEFAULT_TOAST_HEIGHT = 50; const MAX_WIDTH = 360; export const Toast = ({ toast, updateHeight, offset, startPause, endPause, overrideDarkMode, onToastHide, onToastPress, onToastShow, extraInsets, defaultStyle, keyboardVisible, keyboardHeight }) => { const insets = useSafeAreaInsets(); const { width, height } = useWindowDimensions(); const isSystemDarkMode = useColorScheme() === 'dark'; const [toastHeight, setToastHeight] = useState(toast?.height ? toast.height : DEFAULT_TOAST_HEIGHT); const maxWidth = toast?.maxWidth ? toast.maxWidth : MAX_WIDTH; const [toastWidth, setToastWidth] = useState(toast?.width ? toast.width : width - 32 > maxWidth ? maxWidth : width - 32); const isDarkMode = overrideDarkMode !== undefined ? overrideDarkMode : isSystemDarkMode; const getStartingPosition = useMemo(() => { let leftPosition = (width - toastWidth) / 2; // Default to center if (toast.position === ToastPosition.TOP_LEFT || toast.position === ToastPosition.BOTTOM_LEFT) { leftPosition = insets.left + 16 + (extraInsets?.left ?? 0); } if (toast.position === ToastPosition.TOP_RIGHT || toast.position === ToastPosition.BOTTOM_RIGHT) { leftPosition = width - toastWidth - insets.right - 16 - (extraInsets?.right ?? 0); } let startY = 0; if (toast.position === ToastPosition.TOP || toast.position === ToastPosition.TOP_LEFT || toast.position === ToastPosition.TOP_RIGHT) { startY = -(toast.height || DEFAULT_TOAST_HEIGHT) - insets.top - 50; } else { startY = height - insets.bottom - Platform.select({ ios: 16, default: 32 }); } return { startY, leftPosition }; }, [height, width, toastWidth, toast.position, toast.height, insets, extraInsets]); const { startY, leftPosition } = getStartingPosition; const opacity = useSharedValue(0); const position = useSharedValue(startY); const offsetY = useSharedValue(startY); const onPress = useCallback(() => { if (toast.onPress) { toast.onPress(toast); } if (onToastPress) { onToastPress(toast); } }, [toast, onToastPress]); const dismiss = useCallback((id, reason) => { toasting.dismiss(id, reason); }, []); const getSwipeDirection = useCallback(() => { if (toast.position === ToastPosition.TOP || toast.position === ToastPosition.TOP_LEFT || toast.position === ToastPosition.TOP_RIGHT) { return Directions.UP; } else { return Directions.DOWN; } }, [toast.position]); const setPosition = useCallback(() => { let timingConfig = { duration: 300 }; let springConfig = { stiffness: 80 }; if (toast.animationConfig) { const { duration = 300, easing = Easing.inOut(Easing.quad), reduceMotion = ReduceMotion.System, ...spring } = toast.animationConfig; timingConfig = { duration, easing, reduceMotion }; springConfig = spring; } const useSpringAnimation = toast.animationType === 'spring'; const animation = useSpringAnimation ? withSpring : withTiming; if (toast.position === ToastPosition.TOP || toast.position === ToastPosition.TOP_LEFT || toast.position === ToastPosition.TOP_RIGHT) { offsetY.value = animation(toast.visible ? offset : startY, useSpringAnimation ? springConfig : timingConfig); position.value = animation(toast.visible ? offset : startY, useSpringAnimation ? springConfig : timingConfig); } else { let kbHeight = keyboardVisible ? keyboardHeight : 0; const val = toast.visible ? startY - toastHeight - offset - kbHeight - insets.bottom - (extraInsets?.bottom ?? 0) - Platform.select({ ios: 32, default: 24 }) : startY; offsetY.value = animation(val, useSpringAnimation ? springConfig : timingConfig); position.value = animation(val, useSpringAnimation ? springConfig : timingConfig); } }, [offset, toast.visible, keyboardVisible, keyboardHeight, toastHeight, insets.bottom, position, startY, toast.position, offsetY, extraInsets, toast.animationConfig, toast.animationType]); const composedGesture = useMemo(() => { const panGesture = Gesture.Pan().onUpdate(e => { offsetY.value = e.translationY / 4 + position.value; }).onEnd(() => { runOnJS(setPosition)(); }); const flingGesture = Gesture.Fling().direction(getSwipeDirection()).onEnd(() => { offsetY.value = withTiming(startY, { duration: toast?.animationConfig?.flingPositionReturnDuration ?? 40 }); runOnJS(dismiss)(toast.id, DismissReason.SWIPE); }); return toast.isSwipeable ? Gesture.Simultaneous(flingGesture, panGesture) : panGesture; }, [offsetY, startY, position, setPosition, toast.id, dismiss, toast.isSwipeable, toast.animationConfig, getSwipeDirection]); useVisibilityChange(() => { if (toast.onShow) { toast.onShow(toast); } onToastShow?.(toast); }, reason => { if (toast.onHide) { toast.onHide(toast, reason || DismissReason.PROGRAMMATIC); } onToastHide?.(toast, reason || DismissReason.PROGRAMMATIC); }, toast.visible, toast.dismissReason); useEffect(() => { setToastHeight(toast?.height ? toast.height : DEFAULT_TOAST_HEIGHT); }, [toast.height]); useEffect(() => { setToastWidth(toast?.width ? toast.width : width - 32 > maxWidth ? maxWidth : width - 32); }, [toast.width, maxWidth, width]); useEffect(() => { opacity.value = withTiming(toast.visible ? 1 : 0, { duration: toast?.animationConfig?.duration ?? 300 }); }, [toast.visible, opacity, toast.animationConfig]); useEffect(() => { setPosition(); }, [offset, toast.visible, keyboardVisible, keyboardHeight, toastHeight, setPosition]); const style = useAnimatedStyle(() => { //Control opacity and translation of toast return { opacity: opacity.value, transform: [{ translateY: offsetY.value }] }; }); const resolvedValue = resolveValue(toast.message, toast); return /*#__PURE__*/_jsx(GestureDetector, { gesture: composedGesture, children: /*#__PURE__*/_jsx(AnimatedPressable, { testID: "toast-pressable", onPressIn: startPause, onPressOut: () => { endPause(); }, onPress: onPress, style: [{ backgroundColor: !toast.customToast ? isDarkMode ? colors.backgroundDark : colors.backgroundLight : undefined, borderRadius: 8, position: 'absolute', left: leftPosition, zIndex: toast.visible ? 9999 : undefined, alignItems: 'center', justifyContent: 'center' }, style, !toast.disableShadow && ConstructShadow('#181821', 0.15, false), defaultStyle?.pressable, toast.styles?.pressable], children: toast.customToast ? /*#__PURE__*/_jsx(View, { testID: "toast-view", onLayout: event => updateHeight(toast.id, event.nativeEvent.layout.height), children: toast.customToast({ ...toast, height: toastHeight, width: toastWidth }) }, toast.id) : /*#__PURE__*/_jsxs(View, { testID: "toast-view", onLayout: event => updateHeight(toast.id, event.nativeEvent.layout.height), style: [{ minHeight: toastHeight, width: toastWidth, flexDirection: 'row', alignItems: 'center', paddingVertical: 12, paddingHorizontal: 16 }, defaultStyle?.view, toast.styles?.view], children: [(toast.type === 'error' || toast.type === 'success') && /*#__PURE__*/_jsx(View, { style: [{ backgroundColor: toast.type === 'error' ? colors.error : toast.type === 'success' ? colors.success : isDarkMode ? colors.backgroundDark : colors.backgroundLight, width: 3, height: '100%', borderRadius: 12, marginRight: 12 }, defaultStyle?.indicator, toast?.styles?.indicator] }), typeof toast.icon === 'string' ? /*#__PURE__*/_jsx(Text, { children: toast.icon }) : toast.icon, typeof resolvedValue === 'string' ? /*#__PURE__*/_jsx(Text, { style: [{ color: isDarkMode ? colors.textLight : colors.textDark, padding: 4, flex: 1 }, defaultStyle?.text, toast?.styles?.text], children: resolvedValue }) : resolvedValue] }, toast.id) }) }, toast.id); }; //# sourceMappingURL=Toast.js.map