UNPKG

react-native-modern-elements

Version:

A modern, customizable UI component library for React Native

147 lines (146 loc) 7 kB
import { MaterialIcons } from "@expo/vector-icons"; import React, { forwardRef, memo, useImperativeHandle, useState } from "react"; import { Dimensions, StyleSheet, View, } from "react-native"; import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming, } from "react-native-reanimated"; const { width: screenWidth } = Dimensions.get("window"); export const toastRef = React.createRef(); const defaultIcons = { success: "check-circle", error: "cancel", info: "info" }; const defaultColors = { success: "#4BB543", error: "#FF3B30", info: "#007AFF" }; const Toast = forwardRef((props, ref) => { const { defaultAnimation = "fromTop", top = 150, width = screenWidth * 0.8, iconSize = 20, iconColor = "#fff", backgroundColors = defaultColors, textStyle, contentStyle, duration = 2000, customIcons, maxHeight = 200, containerStyle, } = props; const [message, setMessage] = useState(""); const [type, setType] = useState("info"); const [visible, setVisible] = useState(false); const translateY = useSharedValue(0); const translateX = useSharedValue(0); const scale = useSharedValue(1); const opacity = useSharedValue(0); const iconTranslate = useSharedValue(0); const textTranslate = useSharedValue(0); const iconOpacity = useSharedValue(0); const textOpacity = useSharedValue(0); const springConfig = { damping: 13, stiffness: 120, mass: 1 }; useImperativeHandle(ref, () => ({ show(msg, toastType = "info", animation, customDuration) { requestAnimationFrame(() => { setMessage(msg); setType(toastType); setVisible(true); const anim = animation || defaultAnimation; // Initial positions translateX.value = anim === "leftToCenterCloseRight" || anim === "leftToCenterCloseLeft" ? -screenWidth : anim === "rightToCenterCloseLeft" || anim === "rightToCenterCloseRight" ? screenWidth : 0; translateY.value = anim === "fromTop" ? -100 : anim === "fromBottom" ? 100 : 0; scale.value = anim.includes("zoom") || anim === "center" ? 0.5 : 1; opacity.value = anim === "fade" ? 0 : 1; iconTranslate.value = -10; textTranslate.value = 10; iconOpacity.value = 0; textOpacity.value = 0; // Animate in translateX.value = withSpring(0, springConfig); translateY.value = withSpring(0, springConfig); scale.value = withSpring(1, springConfig); opacity.value = withTiming(1, { duration: 300 }); iconTranslate.value = withSpring(0, springConfig); textTranslate.value = withSpring(0, springConfig); iconOpacity.value = withTiming(1, { duration: 300 }); textOpacity.value = withTiming(1, { duration: 300 }); // Hide after duration setTimeout(() => { // Animate out switch (anim) { case "leftToCenterCloseRight": translateX.value = withSpring(screenWidth, springConfig); break; case "rightToCenterCloseLeft": translateX.value = withSpring(-screenWidth, springConfig); break; case "leftToCenterCloseLeft": translateX.value = withSpring(-screenWidth, springConfig); break; case "rightToCenterCloseRight": translateX.value = withSpring(screenWidth, springConfig); break; case "fromTop": translateY.value = withSpring(-100, springConfig); break; case "fromBottom": translateY.value = withSpring(100, springConfig); break; case "zoomIn": case "zoomOut": case "center": scale.value = withSpring(0.8, springConfig); break; } opacity.value = withTiming(0, { duration: 300 }); iconTranslate.value = withSpring(-10, springConfig); textTranslate.value = withSpring(10, springConfig); iconOpacity.value = withTiming(0, { duration: 300 }); textOpacity.value = withTiming(0, { duration: 300 }, () => runOnJS(setVisible)(false)); }, customDuration || duration); }); }, })); const toastStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, { scale: scale.value }, ], opacity: opacity.value, width, maxHeight, overflow: "hidden", })); const iconStyle = useAnimatedStyle(() => ({ transform: [{ translateY: iconTranslate.value }], opacity: iconOpacity.value, })); const textAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ translateY: textTranslate.value }], opacity: textOpacity.value, })); if (!visible) return null; return (React.createElement(Animated.View, { style: [ styles.toast, containerStyle, { top, backgroundColor: backgroundColors[type] }, toastStyle, ] }, React.createElement(View, { style: [styles.content, contentStyle] }, React.createElement(Animated.View, { style: iconStyle }, (customIcons === null || customIcons === void 0 ? void 0 : customIcons[type]) || (React.createElement(MaterialIcons, { name: defaultIcons[type], size: iconSize, color: iconColor, style: { marginRight: 8 } }))), React.createElement(Animated.Text, { style: [styles.text, textStyle, textAnimatedStyle], numberOfLines: 6, ellipsizeMode: "tail" }, message)))); }); Toast.displayName = "Toast"; const styles = StyleSheet.create({ toast: { position: "absolute", alignSelf: "center", overflow: "hidden", borderRadius: 8, zIndex: 9999, shadowColor: "#000", shadowOffset: { width: 0, height: 3 }, shadowOpacity: 0.3, shadowRadius: 4, elevation: 5, }, content: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingVertical: 12, flexWrap: "wrap", }, text: { color: "#fff", fontSize: 16 }, }); export default memo(Toast);