UNPKG

sonner-native

Version:

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

398 lines (394 loc) 14.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ToastIcon = exports.Toast = void 0; var React = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated")); var _animations = require("./animations.js"); var _constants = require("./constants.js"); var _context = require("./context.js"); var _gestures = require("./gestures.js"); var _icons = require("./icons.js"); var _types = require("./types.js"); var _useAppState = require("./use-app-state.js"); var _useDefaultStyles = require("./use-default-styles.js"); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const Toast = exports.Toast = /*#__PURE__*/React.forwardRef(({ id, title, jsx, description, icon, duration: durationProps, variant, action, cancel, close, onDismiss, onAutoClose, dismissible = _constants.toastDefaultValues.dismissible, closeButton: closeButtonProps, actionButtonStyle, actionButtonTextStyle, cancelButtonStyle, cancelButtonTextStyle, style, styles, promiseOptions, position, unstyled: unstyledProps, important, invert: invertProps, richColors: richColorsProps, onPress, backgroundComponent: backgroundComponentProps }, ref) => { const { duration: durationCtx, addToast, closeButton: closeButtonCtx, icons, pauseWhenPageIsHidden, invert: invertCtx, richColors: richColorsCtx, toastOptions: { unstyled: unstyledCtx, toastContainerStyle: toastContainerStyleCtx, actionButtonStyle: actionButtonStyleCtx, actionButtonTextStyle: actionButtonTextStyleCtx, cancelButtonStyle: cancelButtonStyleCtx, cancelButtonTextStyle: cancelButtonTextStyleCtx, style: toastStyleCtx, toastContentStyle: toastContentStyleCtx, titleStyle: titleStyleCtx, descriptionStyle: descriptionStyleCtx, buttonsStyle: buttonsStyleCtx, closeButtonStyle: closeButtonStyleCtx, closeButtonIconStyle: closeButtonIconStyleCtx, backgroundComponent: backgroundComponentCtx, success: successStyleCtx, error: errorStyleCtx, warning: warningStyleCtx, info: infoStyleCtx, loading: loadingStyleCtx } } = (0, _context.useToastContext)(); const invert = invertProps ?? invertCtx; const richColors = richColorsProps ?? richColorsCtx; const unstyled = unstyledProps ?? unstyledCtx; const duration = durationProps ?? durationCtx; const closeButton = closeButtonProps ?? closeButtonCtx; const backgroundComponent = backgroundComponentProps ?? backgroundComponentCtx; const { entering, exiting } = (0, _animations.useToastLayoutAnimations)(position); const isDragging = React.useRef(false); const timer = React.useRef(); const timerStart = React.useRef(); const timeLeftOnceBackgrounded = React.useRef(); const isResolvingPromise = React.useRef(false); const wiggleSharedValue = (0, _reactNativeReanimated.useSharedValue)(1); const wiggleAnimationStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => { return { transform: [{ scale: wiggleSharedValue.value }] }; }, [wiggleSharedValue]); const startTimer = React.useCallback(() => { clearTimeout(timer.current); timer.current = setTimeout(() => { if (!isDragging.current) { onAutoClose?.(id); } }, _animations.ANIMATION_DURATION + duration); }, [duration, id, onAutoClose]); const wiggle = React.useCallback(() => { 'worklet'; wiggleSharedValue.value = (0, _reactNativeReanimated.withRepeat)((0, _reactNativeReanimated.withTiming)(Math.min(wiggleSharedValue.value * 1.035, 1.035), { duration: 150 }), 4, true); }, [wiggleSharedValue]); const wiggleHandler = React.useCallback(() => { // we can't send Infinity over to the native layer. if (duration === Infinity) { return; } // reset the duration timerStart.current = Date.now(); startTimer(); if (wiggleSharedValue.value !== 1) { // we should animate back to 1 and then wiggle wiggleSharedValue.value = (0, _reactNativeReanimated.withTiming)(1, { duration: 150 }, wiggle); } else { wiggle(); } }, [wiggle, wiggleSharedValue, startTimer, duration]); React.useImperativeHandle(ref, () => ({ wiggle: wiggleHandler })); const onBackground = React.useCallback(() => { if (!pauseWhenPageIsHidden) { return; } if (timer.current) { timeLeftOnceBackgrounded.current = duration - (Date.now() - timerStart.current); clearTimeout(timer.current); timer.current = undefined; timerStart.current = undefined; } }, [duration, pauseWhenPageIsHidden]); const onForeground = React.useCallback(() => { if (!pauseWhenPageIsHidden) { return; } if (timeLeftOnceBackgrounded.current && timeLeftOnceBackgrounded.current > 0) { timer.current = setTimeout(() => { if (!isDragging.current) { onAutoClose?.(id); } }, Math.max(timeLeftOnceBackgrounded.current, 1000) // minimum 1 second to avoid weird behavior ); } else { onAutoClose?.(id); } }, [id, onAutoClose, pauseWhenPageIsHidden]); (0, _useAppState.useAppStateListener)(React.useMemo(() => ({ onBackground, onForeground }), [onBackground, onForeground])); React.useEffect(() => { const handlePromise = async () => { if (isResolvingPromise.current) { return; } if (promiseOptions?.promise) { isResolvingPromise.current = true; try { const data = await promiseOptions.promise; addToast({ title: promiseOptions.success(data) ?? 'Success', id, variant: 'success', promiseOptions: undefined }); } catch (error) { addToast({ title: typeof promiseOptions.error === 'function' ? promiseOptions.error(error) : promiseOptions.error ?? 'Error', id, variant: 'error', promiseOptions: undefined }); } finally { isResolvingPromise.current = false; } return; } if (duration === Infinity) { return; } // Start the timer only if it hasn't been started yet if (!timerStart.current) { timerStart.current = Date.now(); startTimer(); } }; handlePromise(); }, [duration, id, onDismiss, promiseOptions, addToast, onAutoClose, startTimer]); React.useEffect(() => { // Cleanup function to clear the timer if it's still the same timer return () => { clearTimeout(timer.current); timer.current = undefined; timerStart.current = undefined; }; }, [id]); const defaultStyles = (0, _useDefaultStyles.useDefaultStyles)({ invert, richColors, unstyled, description, variant }); const variantStyles = { success: successStyleCtx, error: errorStyleCtx, warning: warningStyleCtx, info: infoStyleCtx, loading: loadingStyleCtx }; const variantStyle = variantStyles[variant]; const renderCloseButton = React.useMemo(() => { if (!dismissible) { return null; } if (close) { return close; } if (closeButton) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, { onPress: () => onDismiss?.(id), hitSlop: 10, style: [closeButtonStyleCtx, styles?.closeButton], children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.X, { size: 20, color: defaultStyles.closeButtonColor, style: [closeButtonIconStyleCtx, styles?.closeButtonIcon] }) }); } return null; }, [close, closeButton, closeButtonIconStyleCtx, closeButtonStyleCtx, defaultStyles.closeButtonColor, dismissible, id, onDismiss, styles?.closeButton, styles?.closeButtonIcon]); const toastSwipeHandlerProps = React.useMemo(() => ({ onRemove: () => { onDismiss?.(id); }, onBegin: () => { isDragging.current = true; }, onFinalize: () => { isDragging.current = false; const timeElapsed = Date.now() - timerStart.current; if (timeElapsed < duration) { timer.current = setTimeout(() => { onDismiss?.(id); }, duration - timeElapsed); } else { onDismiss?.(id); } }, onPress: () => onPress?.(), enabled: !promiseOptions && dismissible, style: [toastContainerStyleCtx, styles?.toastContainer], unstyled: unstyled, important: important, position: position }), [onDismiss, id, duration, dismissible, promiseOptions, onPress, toastContainerStyleCtx, styles?.toastContainer, unstyled, important, position]); if (jsx) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_gestures.ToastSwipeHandler, { ...toastSwipeHandlerProps, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, { entering: entering, exiting: exiting, children: jsx }) }); } const backgroundComponentStyle = backgroundComponent ? { overflow: 'hidden', backgroundColor: 'transparent' } : undefined; const contentContainerStyle = backgroundComponent ? { position: 'relative', zIndex: 1 } : undefined; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_gestures.ToastSwipeHandler, { ...toastSwipeHandlerProps, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, { style: wiggleAnimationStyle, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, { style: [unstyled ? undefined : elevationStyle, defaultStyles.toast, toastStyleCtx, variantStyle, styles?.toast, style, backgroundComponentStyle], entering: entering, exiting: exiting, children: [backgroundComponent, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: [defaultStyles.toastContent, toastContentStyleCtx, styles?.toastContent, contentContainerStyle], children: [promiseOptions || variant === 'loading' ? 'loading' in icons ? icons.loading : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {}) : icon ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { children: icon }) : variant in icons ? icons[variant] : /*#__PURE__*/(0, _jsxRuntime.jsx)(ToastIcon, { variant: variant, invert: invert, richColors: richColors }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: { flex: 1 }, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [defaultStyles.title, titleStyleCtx, styles?.title], children: title }), description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [defaultStyles.description, descriptionStyleCtx, styles?.description], children: description }) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: [unstyled || !action && !cancel ? undefined : defaultStyles.buttons, buttonsStyleCtx, styles?.buttons], children: [(0, _types.isToastAction)(action) ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, { onPress: action.onClick, style: [defaultStyles.actionButton, actionButtonStyleCtx, actionButtonStyle], children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { numberOfLines: 1, style: [defaultStyles.actionButtonText, actionButtonTextStyleCtx, actionButtonTextStyle], children: action.label }) }) : action || undefined, (0, _types.isToastAction)(cancel) ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, { onPress: () => { cancel.onClick(); onDismiss?.(id); }, style: [defaultStyles.cancelButton, cancelButtonStyleCtx, cancelButtonStyle], children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { numberOfLines: 1, style: [defaultStyles.cancelButtonText, cancelButtonTextStyleCtx, cancelButtonTextStyle], children: cancel.label }) }) : cancel || undefined] })] }), renderCloseButton] })] }) }) }); }); Toast.displayName = 'Toast'; const ToastIcon = ({ variant, invert, richColors }) => { const color = (0, _useDefaultStyles.useDefaultStyles)({ variant, invert, richColors, unstyled: false, description: undefined }).iconColor; switch (variant) { case 'success': return /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.CircleCheck, { size: 20, color: color }); case 'error': return /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.CircleX, { size: 20, color: color }); case 'warning': return /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.TriangleAlert, { size: 20, color: color }); default: case 'info': return /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.Info, { size: 20, color: color }); } }; exports.ToastIcon = ToastIcon; const elevationStyle = { shadowOpacity: 0.0015 * 4 + 0.1, shadowRadius: 3 * 4, shadowOffset: { height: 4, width: 0 }, elevation: 4 }; //# sourceMappingURL=toast.js.map