UNPKG

expo-react-native-toastify

Version:

expo-react-native-toastify allows you to add notifications to your expo react-native app (ios, android, web) with ease.

355 lines (326 loc) 9.44 kB
import Icon from "react-native-vector-icons/Ionicons"; import Modal from "react-native-modal"; import React, { Component } from "react"; import { RFPercentage } from "react-native-responsive-fontsize"; import { View, Text, Animated, Dimensions, TouchableOpacity, Platform, } from "react-native"; import defaultProps from "../utils/defaultProps"; import { Colors } from "../config/theme"; import styles from "./styles"; const { height } = Dimensions.get("window"); class ToastManager extends Component { constructor(props) { super(props); ToastManager.__singletonRef = this; } state = { isShow: false, content: "", opacityValue: new Animated.Value(1), barWidth: new Animated.Value(RFPercentage(32)), barColor: Colors.default, icon: this.props.icon || "checkmark-circle", position: this.props.position, theme: 'light', width: this.props.width, modalWidth: this.props.modalWidth, modalMaxWidth: this.props.modalMaxWidth, height: this.props.height, zIndex: this.props.zIndex, animationStyle: { upInUpOut: { animationIn: "slideInDown", animationOut: "slideOutUp", }, rightInOut: { animationIn: "slideInRight", animationOut: "slideOutRight", }, zoomInOut: { animationIn: "zoomInDown", animationOut: "zoomOutUp", }, }, }; static info = (content, props = {}) => { if (!ToastManager.__singletonRef) return; const theme = props.theme || 'light'; ToastManager.__singletonRef.show( content, Colors[theme].progressBar.info, "information-circle", props.position, props.duration, { theme } ); }; static success = (content, props = {}) => { if (!ToastManager.__singletonRef) return; const theme = props.theme || 'light'; ToastManager.__singletonRef.show( content, Colors[theme].progressBar.success, "checkmark-circle", props.position, props.duration, { theme } ); }; static warn = (content, props = {}) => { if (!ToastManager.__singletonRef) return; const theme = props.theme || 'light'; ToastManager.__singletonRef.show( content, Colors[theme].progressBar.warn, "warning", props.position, props.duration, { theme } ); }; static error = (content, props = {}) => { if (!ToastManager.__singletonRef) return; const theme = props.theme || 'light'; ToastManager.__singletonRef.show( content, Colors[theme].progressBar.error, "alert-circle", props.position, props.duration, { theme } ); }; static custom = (content, props = {}) => { if (!ToastManager.__singletonRef) return; const theme = props.theme || 'light'; ToastManager.__singletonRef.show( content, props.barColor || Colors[theme].progressBar.default, props.icon, props.position, props.duration, { theme, style: { backgroundColor: props.style?.backgroundColor || Colors[theme].background, ...props.style, }, textStyle: props.textStyle, width: props.width, height: props.height, } ); }; show = (content = "", barColor = Colors.default, icon, position, duration = this.props.duration, customProps = {}) => { const isComponent = typeof content !== 'string'; const width = customProps.width || (isComponent ? 350 : 300); const modalWidth = customProps.modalWidth || (isComponent ? 400 : 350); this.state.barWidth.setValue(width); this.setState({ isShow: true, duration, content, barColor, icon, width, modalWidth, height: customProps.height || (isComponent ? 80 : 68), ...customProps }); if (position) this.setState({ position }); this.isShow = true; if (duration !== 0) { this.close(duration); this.startProgressBar(duration); } }; startProgressBar = (duration) => { Animated.timing(this.state.barWidth, { toValue: 0, duration: duration, useNativeDriver: false, }).start(); }; close = (duration) => { if (!this.isShow && !this.state.isShow) return; this.resetAll(); this.timer = setTimeout(() => { this.setState({ isShow: false }); }, duration || this.state.duration); }; position = () => { const { position } = this.state; if (position === "top") return this.props.positionValue; if (position === "center") return height / 2 - RFPercentage(9); return height - this.props.positionValue - RFPercentage(10); }; handleBar = () => { // Remove this since we don't want to restart animation on every render // Animated.timing(this.state.barWidth, { // toValue: 0, // duration: this.state.duration, // useNativeDriver: false, // }).start(); }; pause = () => { if (this.state.duration === 0) return; // Don't pause if it's a static toast clearTimeout(this.timer); // Clear the close timer Animated.timing(this.state.barWidth).stop(); // Stop the progress bar animation this.setState({ oldDuration: this.state.duration, pausedWidth: this.state.barWidth.__getValue() // Store current width }); }; resume = () => { if (this.state.duration === 0) return; // Don't resume if it's a static toast const remainingTime = (this.state.pausedWidth / this.state.width) * this.state.oldDuration; // Restart the close timer this.close(remainingTime); // Resume the progress bar animation from current position Animated.timing(this.state.barWidth, { toValue: 0, duration: remainingTime, useNativeDriver: false, }).start(); }; hideToast = () => { this.resetAll(); this.setState({ isShow: false }); this.isShow = false; if (!this.isShow && !this.state.isShow) return; }; resetAll = () => { clearTimeout(this.timer); }; render() { const { animationIn, animationStyle, animationOut, backdropTransitionOutTiming, backdropTransitionInTiming, animationInTiming, animationOutTiming, backdropColor, backdropOpacity, hasBackdrop, } = this.props; const { isShow, animationStyle: stateAnimationStyle, barColor, icon, content, barWidth, theme, width, modalWidth, modalMaxWidth, height, zIndex, } = this.state; // Get mouse event props only for web platform const mouseEventProps = Platform.OS === 'web' ? { onMouseEnter: this.pause, onMouseLeave: this.resume, } : {}; return ( <Modal animationIn={ animationIn || stateAnimationStyle[animationStyle].animationIn } animationOut={ animationOut || stateAnimationStyle[animationStyle].animationOut } backdropTransitionOutTiming={backdropTransitionOutTiming} backdropTransitionInTiming={backdropTransitionInTiming} animationInTiming={animationInTiming} animationOutTiming={animationOutTiming} onTouchStart={this.pause} onTouchEnd={this.resume} {...mouseEventProps} swipeDirection={["up", "down", "left", "right"]} onSwipeComplete={this.hideToast} onModalHide={this.resetAll} isVisible={isShow} coverScreen={false} backdropColor={backdropColor} backdropOpacity={backdropOpacity} hasBackdrop={hasBackdrop} style={[ styles.modalContainer, { zIndex: zIndex || 20000, width: typeof content === 'string' ? 300 : (modalWidth || 400), alignSelf: 'center' } ]} > <View style={[ styles.mainContainer, { width: width || 350, height: height || 80, backgroundColor: Colors[theme].back, top: this.position(), }, ]} > <TouchableOpacity onPress={this.hideToast} activeOpacity={0.9} style={styles.hideButton} > <Icon name="close-outline" size={22} color={Colors[theme].text} /> </TouchableOpacity> <View style={styles.content}> {typeof content === 'string' ? ( <> {icon && ( <Icon name={icon} size={24} color={barColor} style={styles.iconWrapper} /> )} <Text style={[styles.textStyle, { color: Colors[theme].text }]}> {content} </Text> </> ) : ( content )} </View> <View style={styles.progressBarContainer}> <Animated.View style={[ styles.progressBar, { width: barWidth, backgroundColor: barColor }, ]} /> </View> </View> </Modal> ); } } ToastManager.defaultProps = { ...defaultProps, zIndex: 20000, width: 300, modalWidth: 300, height: 80, }; export default ToastManager;