|
@@ -0,0 +1,273 @@
|
|
|
|
|
+import {
|
|
|
|
|
+ useEffect,
|
|
|
|
|
+ type FC,
|
|
|
|
|
+ useRef
|
|
|
|
|
+} from "react";
|
|
|
|
|
+import {
|
|
|
|
|
+ TouchableWithoutFeedback,
|
|
|
|
|
+ Animated,
|
|
|
|
|
+ Easing,
|
|
|
|
|
+ View
|
|
|
|
|
+} from "react-native";
|
|
|
|
|
+import type IToastProps from "./type";
|
|
|
|
|
+import stylesheet, {
|
|
|
|
|
+ getToastType,
|
|
|
|
|
+ useStyles
|
|
|
|
|
+} from "./stylesheet";
|
|
|
|
|
+import {
|
|
|
|
|
+ NCoreUIKitTheme
|
|
|
|
|
+} from "../../core/hooks";
|
|
|
|
|
+import {
|
|
|
|
|
+ useSafeAreaInsets
|
|
|
|
|
+} from "react-native-safe-area-context";
|
|
|
|
|
+import {
|
|
|
|
|
+ X as XIcon
|
|
|
|
|
+} from "lucide-react-native";
|
|
|
|
|
+import {
|
|
|
|
|
+ Portal
|
|
|
|
|
+} from "../../helpers/portalize";
|
|
|
|
|
+import Button from "../button";
|
|
|
|
|
+import Text from "../text";
|
|
|
|
|
+
|
|
|
|
|
+const Toast: FC<IToastProps> = ({
|
|
|
|
|
+ isCloseOnPressActionButton = true,
|
|
|
|
|
+ closeAnimationDelay = 350,
|
|
|
|
|
+ openAnimationDelay = 200,
|
|
|
|
|
+ contentContainerStyle,
|
|
|
|
|
+ autoCloseDelay = 3000,
|
|
|
|
|
+ isCloseOnPress = true,
|
|
|
|
|
+ icon: CustomIcon,
|
|
|
|
|
+ type = "neutral",
|
|
|
|
|
+ isShowAction,
|
|
|
|
|
+ customTheme,
|
|
|
|
|
+ onClosed,
|
|
|
|
|
+ subTitle,
|
|
|
|
|
+ children,
|
|
|
|
|
+ action,
|
|
|
|
|
+ style,
|
|
|
|
|
+ title,
|
|
|
|
|
+ id
|
|
|
|
|
+}) => {
|
|
|
|
|
+ const {
|
|
|
|
|
+ radiuses,
|
|
|
|
|
+ colors,
|
|
|
|
|
+ spaces
|
|
|
|
|
+ } = NCoreUIKitTheme.useContext(customTheme);
|
|
|
|
|
+
|
|
|
|
|
+ const {
|
|
|
|
|
+ bottom
|
|
|
|
|
+ } = useSafeAreaInsets();
|
|
|
|
|
+
|
|
|
|
|
+ const TRANSFORM_DISTANCE = 75;
|
|
|
|
|
+
|
|
|
|
|
+ const transformAnim = useRef(new Animated.Value(TRANSFORM_DISTANCE)).current;
|
|
|
|
|
+ const opacityAnim = useRef(new Animated.Value(0)).current;
|
|
|
|
|
+
|
|
|
|
|
+ const currentType = getToastType({
|
|
|
|
|
+ type
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const {
|
|
|
|
|
+ contentContainer: contentContainerDynamicStyle,
|
|
|
|
|
+ containerObject: containerObjectDynamicStyle,
|
|
|
|
|
+ iconContainer: iconContainerDynamicStyle,
|
|
|
|
|
+ container: containerDynamicStyle,
|
|
|
|
|
+ subTitle: subTitleDynamicStyle,
|
|
|
|
|
+ action: actionDynamicStyle,
|
|
|
|
|
+ title: titleDynamicStyle
|
|
|
|
|
+ } = useStyles({
|
|
|
|
|
+ safeAreaBottom: bottom,
|
|
|
|
|
+ currentType,
|
|
|
|
|
+ radiuses,
|
|
|
|
|
+ subTitle,
|
|
|
|
|
+ spaces,
|
|
|
|
|
+ colors,
|
|
|
|
|
+ type
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ Animated.parallel([
|
|
|
|
|
+ Animated.timing(opacityAnim, {
|
|
|
|
|
+ duration: openAnimationDelay,
|
|
|
|
|
+ useNativeDriver: true,
|
|
|
|
|
+ easing: Easing.linear,
|
|
|
|
|
+ toValue: 1
|
|
|
|
|
+ }),
|
|
|
|
|
+ Animated.timing(transformAnim, {
|
|
|
|
|
+ duration: openAnimationDelay,
|
|
|
|
|
+ useNativeDriver: true,
|
|
|
|
|
+ easing: Easing.linear,
|
|
|
|
|
+ toValue: 0
|
|
|
|
|
+ })
|
|
|
|
|
+ ]).start();
|
|
|
|
|
+
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ closeAnimation();
|
|
|
|
|
+ }, autoCloseDelay);
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ const closeAnimation = (_onClosed?: (props: {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ }) => void) => {
|
|
|
|
|
+ Animated.parallel([
|
|
|
|
|
+ Animated.timing(transformAnim, {
|
|
|
|
|
+ duration: closeAnimationDelay,
|
|
|
|
|
+ toValue: TRANSFORM_DISTANCE,
|
|
|
|
|
+ useNativeDriver: true,
|
|
|
|
|
+ easing: Easing.linear
|
|
|
|
|
+ }),
|
|
|
|
|
+ Animated.timing(opacityAnim, {
|
|
|
|
|
+ duration: closeAnimationDelay,
|
|
|
|
|
+ useNativeDriver: true,
|
|
|
|
|
+ easing: Easing.linear,
|
|
|
|
|
+ toValue: 0
|
|
|
|
|
+ })
|
|
|
|
|
+ ]).start(({
|
|
|
|
|
+ finished
|
|
|
|
|
+ }) => {
|
|
|
|
|
+ if(finished) {
|
|
|
|
|
+ if(onClosed) onClosed({
|
|
|
|
|
+ id
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if(_onClosed) _onClosed({
|
|
|
|
|
+ id
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const renderIcon = () => {
|
|
|
|
|
+ if(!CustomIcon) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return <View
|
|
|
|
|
+ style={[
|
|
|
|
|
+ stylesheet.iconContainer,
|
|
|
|
|
+ iconContainerDynamicStyle
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <CustomIcon
|
|
|
|
|
+ color="default"
|
|
|
|
|
+ size={18}
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const renderContent = () => {
|
|
|
|
|
+ return <View
|
|
|
|
|
+ style={[
|
|
|
|
|
+ contentContainerStyle,
|
|
|
|
|
+ stylesheet.contentContainer,
|
|
|
|
|
+ contentContainerDynamicStyle
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text
|
|
|
|
|
+ numberOfLines={3}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ ...stylesheet.title,
|
|
|
|
|
+ ...titleDynamicStyle
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {title}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ {
|
|
|
|
|
+ subTitle ?
|
|
|
|
|
+ <Text
|
|
|
|
|
+ variant="labelSmallSize"
|
|
|
|
|
+ numberOfLines={2}
|
|
|
|
|
+ color="low"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ ...stylesheet.subTitle,
|
|
|
|
|
+ ...subTitleDynamicStyle
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {subTitle}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ :
|
|
|
|
|
+ null
|
|
|
|
|
+ }
|
|
|
|
|
+ </View>;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const renderAction = () => {
|
|
|
|
|
+ if(!isShowAction) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return <Button
|
|
|
|
|
+ title={action && action.title ? action.title : undefined}
|
|
|
|
|
+ isCustomPadding={true}
|
|
|
|
|
+ spreadBehaviour="free"
|
|
|
|
|
+ onPress={() => {
|
|
|
|
|
+ if(isCloseOnPressActionButton) closeAnimation();
|
|
|
|
|
+
|
|
|
|
|
+ if(action && action.onPress) action.onPress({
|
|
|
|
|
+ closeAnimation: ({
|
|
|
|
|
+ onClosed: _onClosed
|
|
|
|
|
+ }) => closeAnimation(_onClosed)
|
|
|
|
|
+ });
|
|
|
|
|
+ }}
|
|
|
|
|
+ icon={({
|
|
|
|
|
+ color
|
|
|
|
|
+ }) => {
|
|
|
|
|
+ if(action?.title) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return <XIcon
|
|
|
|
|
+ color={colors.content.icon[color ? color : "default"]}
|
|
|
|
|
+ size={18}
|
|
|
|
|
+ />;
|
|
|
|
|
+ }}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ ...action?.style,
|
|
|
|
|
+ ...actionDynamicStyle
|
|
|
|
|
+ }}
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ type="neutral"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ />;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const renderContainer = () => {
|
|
|
|
|
+ return <View
|
|
|
|
|
+ style={[
|
|
|
|
|
+ stylesheet.containerObject,
|
|
|
|
|
+ containerObjectDynamicStyle
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ {renderIcon()}
|
|
|
|
|
+ {renderContent()}
|
|
|
|
|
+ {renderAction()}
|
|
|
|
|
+ </View>;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return <Portal name="toast-system">
|
|
|
|
|
+ <Animated.View
|
|
|
|
|
+ style={[
|
|
|
|
|
+ style,
|
|
|
|
|
+ stylesheet.container,
|
|
|
|
|
+ containerDynamicStyle,
|
|
|
|
|
+ {
|
|
|
|
|
+ opacity: opacityAnim,
|
|
|
|
|
+ transform: [{
|
|
|
|
|
+ translateY: transformAnim
|
|
|
|
|
+ }]
|
|
|
|
|
+ }
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <TouchableWithoutFeedback
|
|
|
|
|
+ onPress={() => {
|
|
|
|
|
+ if(isCloseOnPress) {
|
|
|
|
|
+ closeAnimation();
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {children ? children : renderContainer()}
|
|
|
|
|
+ </TouchableWithoutFeedback>
|
|
|
|
|
+ </Animated.View>
|
|
|
|
|
+ </Portal>;
|
|
|
|
|
+};
|
|
|
|
|
+export default Toast;
|