react-native-modern-elements
Version:
A modern, customizable UI component library for React Native
147 lines (146 loc) • 7 kB
JavaScript
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);