UNPKG

react-native-toast-lite

Version:
316 lines (306 loc) 10.1 kB
"use strict"; import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react'; import { View, Text, Image, ActivityIndicator, StyleSheet } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, withTiming, interpolate, runOnJS } from 'react-native-reanimated'; import ErrorSvg from "../ui/ErrorSvg.js"; import SuccessSvg from "../ui/SuccessSvg.js"; import InfoSvg from "../ui/InfoSvg.js"; import WarningSvg from "../ui/WarningSvg.js"; import CustomLoading from "./CustomLoading.js"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; export const RenderIcon = ({ type, toastStyle, iconColor, icon, iconUrl, iconSize, iconStyle, iconResizeMode, iconRounded, iconBorderRadius, loadingDelayMs = 150 }) => { const size = iconSize ?? 25; const radius = typeof iconBorderRadius === 'number' ? Math.max(0, Math.round(iconBorderRadius)) : iconRounded ? Math.round(size / 2) : Math.round(size / 6); // Firma desde props const signature = useMemo(() => { return iconUrl ? `url:${iconUrl}` : icon ? `emoji:${icon}` : `builtin:${type}:${toastStyle}:${iconColor ?? ''}:${iconStyle ?? ''}`; }, [iconUrl, icon, type, toastStyle, iconColor, iconStyle]); // Helpers const kindFromSig = useCallback(sig => { if (sig.startsWith('url:')) return 'url'; if (sig.startsWith('emoji:')) return 'emoji'; return 'builtin'; }, []); const parseBuiltinFromSig = useCallback(sig => { // builtin:<type>:<toastStyle>:<iconColor>:<iconStyle> const parts = sig.split(':'); const typeFromSig = parts[1] || type; const styleFromSig = parts[2] || toastStyle; return { typeFromSig, styleFromSig }; }, [type, toastStyle]); // Estado “actual” y “pendiente” const [currentSig, setCurrentSig] = useState(signature); const [pendingSig, setPendingSig] = useState(null); // Flags y timers const [loadFailed, setLoadFailed] = useState(false); const [showSpinner, setShowSpinner] = useState(false); const spinnerTimeout = useRef(null); const nonUrlTransitionRef = useRef(null); // Opacidades const currentOpacity = useSharedValue(1); const pendingOpacity = useSharedValue(0); const currentKind = useMemo(() => kindFromSig(currentSig), [currentSig, kindFromSig]); const pendingKind = useMemo(() => pendingSig ? kindFromSig(pendingSig) : null, [pendingSig, kindFromSig]); // Para que el spinner quede por encima y el contenido por debajo const DIM_WHEN_SPINNER = 0.35; const currentAnimatedStyle = useAnimatedStyle(() => ({ opacity: (showSpinner ? DIM_WHEN_SPINNER : 1) * interpolate(currentOpacity.value, [0, 1], [0, 1]), zIndex: 1 // debajo del spinner })); const pendingAnimatedStyle = useAnimatedStyle(() => ({ opacity: (showSpinner ? DIM_WHEN_SPINNER : 1) * interpolate(pendingOpacity.value, [0, 1], [0, 1]), zIndex: 1 // debajo del spinner })); const promotePending = useCallback(sig => { currentOpacity.value = withTiming(0, { duration: 200 }, () => { runOnJS(setCurrentSig)(sig); runOnJS(setPendingSig)(null); currentOpacity.value = 1; pendingOpacity.value = 0; runOnJS(setShowSpinner)(false); }); }, [currentOpacity, pendingOpacity]); // Preparar transición al cambiar firma useEffect(() => { if (signature === currentSig || signature === pendingSig) return; if (spinnerTimeout.current) clearTimeout(spinnerTimeout.current); if (nonUrlTransitionRef.current) clearTimeout(nonUrlTransitionRef.current); setLoadFailed(false); setShowSpinner(false); const nextKind = kindFromSig(signature); setPendingSig(signature); pendingOpacity.value = 0; if (nextKind === 'url') { // URL => spinner con delay; el promote ocurre en onLoadEnd spinnerTimeout.current = setTimeout(() => setShowSpinner(true), loadingDelayMs); } else { // emoji/builtin => micro-spinner y crossfade setShowSpinner(true); nonUrlTransitionRef.current = setTimeout(() => { pendingOpacity.value = withTiming(1, { duration: 200 }, () => runOnJS(promotePending)(signature)); runOnJS(setShowSpinner)(false); }, loadingDelayMs); } }, [signature, currentSig, pendingSig, loadingDelayMs, kindFromSig, pendingOpacity, promotePending]); useEffect(() => { return () => { if (spinnerTimeout.current) clearTimeout(spinnerTimeout.current); if (nonUrlTransitionRef.current) clearTimeout(nonUrlTransitionRef.current); }; }, []); // Render de capa const renderChild = (sig, kind) => { if (kind === 'url') { const url = sig.slice(4); if (loadFailed) return renderFallback(); return /*#__PURE__*/_jsx(Image, { source: { uri: url }, resizeMode: iconResizeMode ?? 'contain', onLoadStart: () => {}, onLoadEnd: () => { if (pendingSig === sig) { pendingOpacity.value = withTiming(1, { duration: 160 }, () => runOnJS(promotePending)(sig) // ✅ ahora con argumento ); } else { setShowSpinner(false); } }, onError: () => { setLoadFailed(true); setShowSpinner(false); }, style: StyleSheet.absoluteFill }); } if (kind === 'emoji') { const emoji = sig.slice(6); const node = /*#__PURE__*/_jsx(View, { style: styles.centerFill, children: /*#__PURE__*/_jsx(Text, { style: { fontSize: size }, children: emoji }) }); if (pendingSig === sig) { pendingOpacity.value = withTiming(1, { duration: 160 }, () => runOnJS(promotePending)(sig)); } return node; } // builtin const { typeFromSig, styleFromSig } = sig.startsWith('builtin:') ? parseBuiltinFromSig(sig) : { typeFromSig: type, styleFromSig: toastStyle }; const builtinNode = (() => { switch (typeFromSig) { case 'error': return /*#__PURE__*/_jsx(ErrorSvg, { toastStyle: styleFromSig, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); case 'success': return /*#__PURE__*/_jsx(SuccessSvg, { toastStyle: styleFromSig, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); case 'info': return /*#__PURE__*/_jsx(InfoSvg, { toastStyle: styleFromSig, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); case 'warning': return /*#__PURE__*/_jsx(WarningSvg, { toastStyle: styleFromSig, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); case 'loading': return /*#__PURE__*/_jsx(CustomLoading, { color: iconColor, size: size }); default: return null; } })(); if (pendingSig === sig) { pendingOpacity.value = withTiming(1, { duration: 160 }, () => runOnJS(promotePending)(sig)); } return /*#__PURE__*/_jsx(View, { style: styles.centerFill, children: builtinNode }); }; const renderFallback = () => { if (icon) { return /*#__PURE__*/_jsx(View, { style: styles.centerFill, children: /*#__PURE__*/_jsx(Text, { style: { fontSize: size }, children: icon }) }); } return /*#__PURE__*/_jsx(View, { style: styles.centerFill, children: type === 'loading' ? /*#__PURE__*/_jsx(CustomLoading, { color: iconColor, size: size }) : (() => { switch (type) { case 'error': return /*#__PURE__*/_jsx(ErrorSvg, { toastStyle: toastStyle, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); case 'success': return /*#__PURE__*/_jsx(SuccessSvg, { toastStyle: toastStyle, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); case 'info': return /*#__PURE__*/_jsx(InfoSvg, { toastStyle: toastStyle, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); case 'warning': return /*#__PURE__*/_jsx(WarningSvg, { toastStyle: toastStyle, iconColor: iconColor, iconSize: size, iconStyle: iconStyle }); default: return null; } })() }); }; return /*#__PURE__*/_jsxs(View, { style: { width: size, height: size, borderRadius: radius, overflow: 'hidden', alignItems: 'center', justifyContent: 'center', position: 'relative' }, children: [/*#__PURE__*/_jsx(Animated.View, { style: [StyleSheet.absoluteFill, currentAnimatedStyle], pointerEvents: "none", children: renderChild(currentSig, currentKind) }), pendingSig && /*#__PURE__*/_jsx(Animated.View, { style: [StyleSheet.absoluteFill, pendingAnimatedStyle], pointerEvents: "none", children: renderChild(pendingSig, pendingKind) }), showSpinner && /*#__PURE__*/_jsx(View, { style: styles.spinnerOverlay, pointerEvents: "none", children: /*#__PURE__*/_jsx(ActivityIndicator, { size: "small", color: iconColor ?? '#999' }) })] }); }; const styles = StyleSheet.create({ centerFill: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center' }, spinnerOverlay: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center', zIndex: 2 // <- arriba de todo } });