Amba-Agent-App/components/ui/toast.tsx
2026-01-16 00:22:35 +03:00

192 lines
4.4 KiB
TypeScript

import React, { useEffect, useRef } from "react";
import { View, Text, StyleSheet, Animated } from "react-native";
import { CheckCircle2, AlertCircle, Info } from "lucide-react-native";
interface ModalToastProps {
visible: boolean;
title: string;
description?: string;
/**
* Visual style of the toast.
* - success: green
* - error: red
* - warning: yellow
* - info: blue
*/
variant?: "success" | "error" | "warning" | "info";
}
const ModalToast: React.FC<ModalToastProps> = ({
visible,
title,
description,
variant = "error",
}) => {
const config = getVariantConfig(variant);
const Icon = config.icon;
const translateX = useRef(new Animated.Value(-40)).current;
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (visible) {
translateX.setValue(-40);
opacity.setValue(0);
Animated.parallel([
Animated.spring(translateX, {
toValue: 0,
useNativeDriver: true,
speed: 20,
bounciness: 6,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 180,
useNativeDriver: true,
}),
]).start();
}
}, [visible, translateX, opacity]);
if (!visible) return null;
return (
<View
pointerEvents="box-none"
style={[StyleSheet.absoluteFill, styles.absoluteOverlay]}
>
<View style={[styles.wrapper, { paddingTop: 0 }]}>
<Animated.View
style={[
styles.toast,
{
backgroundColor: config.backgroundColor,
borderColor: config.borderColor,
borderWidth: 1,
transform: [{ translateX }],
opacity,
},
]}
>
<View
style={[
styles.iconContainer,
{ backgroundColor: config.iconBackgroundColor },
]}
>
<Icon size={18} color={config.iconColor} />
</View>
<View style={styles.textContainer}>
<Text style={[styles.title, { color: config.titleColor }]}>
{title}
</Text>
{description ? (
<Text
style={[styles.description, { color: config.descriptionColor }]}
numberOfLines={3}
>
{description}
</Text>
) : null}
</View>
</Animated.View>
</View>
</View>
);
};
function getVariantConfig(variant: "success" | "error" | "warning" | "info") {
switch (variant) {
case "success":
return {
backgroundColor: "#f1f9f5",
iconBackgroundColor: "#e1f0e2",
iconColor: "#16a34a",
titleColor: "#000",
descriptionColor: "#000",
icon: CheckCircle2,
borderColor: "#e1f0e2",
} as const;
case "warning":
return {
backgroundColor: "#fef7eb",
iconBackgroundColor: "#ebe3d5",
iconColor: "#eab308",
titleColor: "#000",
descriptionColor: "#000",
icon: AlertCircle,
borderColor: "#ebe3d5",
} as const;
case "info":
return {
backgroundColor: "#e8eefa",
iconBackgroundColor: "#cdd5e2",
iconColor: "#2563eb",
titleColor: "#000",
descriptionColor: "#000",
icon: Info,
borderColor: "#cdd5e2",
} as const;
case "error":
default:
return {
backgroundColor: "#fbf0f1",
iconBackgroundColor: "#ebd8d4",
iconColor: "#dc2000",
titleColor: "#000000",
descriptionColor: "#000",
icon: AlertCircle,
borderColor: "#ebd8d4",
} as const;
}
}
const styles = StyleSheet.create({
absoluteOverlay: {
zIndex: 9999,
elevation: 50,
},
wrapper: {
alignItems: "center",
paddingHorizontal: 16,
marginTop: 16,
},
toast: {
width: "100%",
maxWidth: 360,
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 12,
flexDirection: "row",
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.18,
shadowRadius: 8,
shadowOffset: { width: 0, height: 4 },
},
iconContainer: {
width: 32,
height: 32,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
},
textContainer: {
flex: 1,
marginLeft: 12,
},
title: {
fontSize: 14,
fontWeight: "700",
},
description: {
fontSize: 12,
marginTop: 4,
},
});
export default ModalToast;