267 lines
8.9 KiB
TypeScript
267 lines
8.9 KiB
TypeScript
import React, { useEffect, useRef, useMemo } from "react";
|
|
import {
|
|
Modal,
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
Animated,
|
|
} from "react-native";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { spacing, fontSize } from "../../theme";
|
|
import { useTheme } from "../../context/ThemeContext";
|
|
|
|
type AlertType = "success" | "error" | "confirm";
|
|
|
|
interface AlertModalProps {
|
|
visible: boolean;
|
|
type?: AlertType;
|
|
title: string;
|
|
message: string;
|
|
onClose: () => void;
|
|
onConfirm?: () => void;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
}
|
|
|
|
export default function AlertModal({
|
|
visible,
|
|
type = "error",
|
|
title,
|
|
message,
|
|
onClose,
|
|
onConfirm,
|
|
confirmText = "Confirmer",
|
|
cancelText = "Annuler",
|
|
}: AlertModalProps) {
|
|
const { colors } = useTheme();
|
|
const scale = useRef(new Animated.Value(0.85)).current;
|
|
const opacity = useRef(new Animated.Value(0)).current;
|
|
|
|
const CONFIG: Record<
|
|
AlertType,
|
|
{
|
|
icon: keyof typeof Ionicons.glyphMap;
|
|
color: string;
|
|
barColor: string;
|
|
}
|
|
> = useMemo(
|
|
() => ({
|
|
success: {
|
|
icon: "checkmark-circle",
|
|
color: colors.success,
|
|
barColor: colors.success,
|
|
},
|
|
error: {
|
|
icon: "alert-circle",
|
|
color: colors.danger,
|
|
barColor: colors.danger,
|
|
},
|
|
confirm: {
|
|
icon: "help-circle",
|
|
color: colors.warning,
|
|
barColor: colors.warning,
|
|
},
|
|
}),
|
|
[colors],
|
|
);
|
|
|
|
const cfg = CONFIG[type];
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
Animated.parallel([
|
|
Animated.spring(scale, {
|
|
toValue: 1,
|
|
useNativeDriver: true,
|
|
tension: 65,
|
|
friction: 8,
|
|
}),
|
|
Animated.timing(opacity, {
|
|
toValue: 1,
|
|
duration: 200,
|
|
useNativeDriver: true,
|
|
}),
|
|
]).start();
|
|
} else {
|
|
scale.setValue(0.85);
|
|
opacity.setValue(0);
|
|
}
|
|
}, [visible]);
|
|
|
|
const styles = useMemo(
|
|
() =>
|
|
StyleSheet.create({
|
|
overlay: {
|
|
flex: 1,
|
|
backgroundColor: "rgba(0,0,0,0.85)",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
padding: spacing.xl,
|
|
},
|
|
content: {
|
|
backgroundColor: colors.bgSecondary,
|
|
borderRadius: 20,
|
|
width: "100%",
|
|
borderWidth: 1,
|
|
borderColor: "rgba(255,255,255,0.08)",
|
|
overflow: "hidden",
|
|
shadowColor: "#000",
|
|
shadowOffset: { width: 0, height: 0 },
|
|
shadowOpacity: 0.3,
|
|
shadowRadius: 30,
|
|
elevation: 20,
|
|
},
|
|
accentBar: {
|
|
height: 3,
|
|
width: "100%",
|
|
},
|
|
body: {
|
|
alignItems: "center",
|
|
paddingHorizontal: spacing.xl,
|
|
paddingTop: spacing.xl,
|
|
paddingBottom: spacing.xl,
|
|
},
|
|
iconCircle: {
|
|
width: 56,
|
|
height: 56,
|
|
borderRadius: 28,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginBottom: spacing.l,
|
|
},
|
|
title: {
|
|
color: colors.textWhite,
|
|
fontSize: fontSize.lg,
|
|
fontWeight: "700",
|
|
textAlign: "center",
|
|
marginBottom: spacing.s,
|
|
},
|
|
message: {
|
|
color: colors.textSecondary,
|
|
fontSize: fontSize.md,
|
|
textAlign: "center",
|
|
lineHeight: 20,
|
|
marginBottom: spacing.xl,
|
|
},
|
|
buttons: {
|
|
flexDirection: "row",
|
|
gap: spacing.m,
|
|
width: "100%",
|
|
justifyContent: "center",
|
|
},
|
|
btn: {
|
|
flex: 1,
|
|
paddingVertical: spacing.m,
|
|
borderRadius: 12,
|
|
alignItems: "center",
|
|
},
|
|
btnCancel: {
|
|
backgroundColor: "rgba(255,255,255,0.08)",
|
|
},
|
|
btnCancelText: {
|
|
color: colors.textSecondary,
|
|
fontSize: 15,
|
|
fontWeight: "600",
|
|
},
|
|
btnConfirm: {},
|
|
btnConfirmText: {
|
|
color: colors.textWhite,
|
|
fontSize: 15,
|
|
fontWeight: "700",
|
|
},
|
|
btnOk: {
|
|
flex: 0,
|
|
paddingHorizontal: spacing.xxl,
|
|
},
|
|
}),
|
|
[colors],
|
|
);
|
|
|
|
return (
|
|
<Modal
|
|
visible={visible}
|
|
transparent
|
|
animationType="fade"
|
|
onRequestClose={onClose}
|
|
>
|
|
<View style={styles.overlay}>
|
|
<Animated.View
|
|
style={[
|
|
styles.content,
|
|
{ transform: [{ scale }], opacity },
|
|
]}
|
|
>
|
|
<View
|
|
style={[
|
|
styles.accentBar,
|
|
{ backgroundColor: cfg.barColor },
|
|
]}
|
|
/>
|
|
<View style={styles.body}>
|
|
<View
|
|
style={[
|
|
styles.iconCircle,
|
|
{ backgroundColor: cfg.color + "20" },
|
|
]}
|
|
>
|
|
<Ionicons
|
|
name={cfg.icon}
|
|
size={32}
|
|
color={cfg.color}
|
|
/>
|
|
</View>
|
|
<Text style={styles.title}>{title}</Text>
|
|
<Text style={styles.message}>{message}</Text>
|
|
<View style={styles.buttons}>
|
|
{type === "confirm" ? (
|
|
<>
|
|
<TouchableOpacity
|
|
style={[styles.btn, styles.btnCancel]}
|
|
onPress={onClose}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={styles.btnCancelText}>
|
|
{cancelText}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.btn,
|
|
styles.btnConfirm,
|
|
{ backgroundColor: cfg.color },
|
|
]}
|
|
onPress={() => {
|
|
onConfirm?.();
|
|
onClose();
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={styles.btnConfirmText}>
|
|
{confirmText}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</>
|
|
) : (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.btn,
|
|
styles.btnOk,
|
|
{ backgroundColor: cfg.color },
|
|
]}
|
|
onPress={onClose}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={styles.btnConfirmText}>
|
|
OK
|
|
</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</Animated.View>
|
|
</View>
|
|
</Modal>
|
|
);
|
|
}
|