Files
projet_gestion_commande/frontend-admin/src/components/ui/Modal.tsx
T
2026-04-22 09:59:33 +02:00

192 lines
6.3 KiB
TypeScript

import React, { type ReactNode, useEffect, useRef, useMemo } from "react";
import {
Modal as RNModal,
View,
ScrollView,
TouchableOpacity,
Text,
StyleSheet,
Animated,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { spacing, borderRadius, fontSize } from "../../theme";
import { useTheme } from "../../context/ThemeContext";
interface ModalProps {
visible: boolean;
onClose: () => void;
title?: string;
children: ReactNode;
icon?: keyof typeof Ionicons.glyphMap;
iconColor?: string;
}
export default function Modal({
visible,
onClose,
title,
children,
icon,
iconColor,
}: ModalProps) {
const { colors } = useTheme();
const scale = useRef(new Animated.Value(0.85)).current;
const opacity = useRef(new Animated.Value(0)).current;
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%",
maxHeight: "80%",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.08)",
overflow: "hidden",
shadowColor: colors.accent,
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.15,
shadowRadius: 30,
elevation: 20,
},
accentBar: {
height: 3,
backgroundColor: colors.accent,
width: "100%",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: spacing.xl,
paddingTop: spacing.l,
paddingBottom: spacing.m,
},
titleRow: {
flexDirection: "row",
alignItems: "center",
gap: 10,
flex: 1,
},
iconCircle: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: colors.accent + "20",
justifyContent: "center",
alignItems: "center",
},
title: {
color: colors.textWhite,
fontSize: fontSize.lg,
fontWeight: "700",
flex: 1,
},
closeBtn: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: "rgba(255,255,255,0.06)",
justifyContent: "center",
alignItems: "center",
},
body: {
paddingHorizontal: spacing.xl,
},
}),
[colors],
);
return (
<RNModal
visible={visible}
transparent
animationType="fade"
onRequestClose={onClose}
>
<View style={styles.overlay}>
<Animated.View
style={[
styles.content,
{ transform: [{ scale }], opacity },
]}
>
<View style={styles.accentBar} />
<View style={styles.header}>
<View style={styles.titleRow}>
{icon && (
<View
style={[
styles.iconCircle,
iconColor
? {
backgroundColor:
iconColor + "20",
}
: null,
]}
>
<Ionicons
name={icon}
size={20}
color={iconColor || colors.accent}
/>
</View>
)}
{title && <Text style={styles.title}>{title}</Text>}
</View>
<TouchableOpacity
onPress={onClose}
style={styles.closeBtn}
activeOpacity={0.7}
>
<Ionicons
name="close"
size={20}
color={colors.textMuted}
/>
</TouchableOpacity>
</View>
<ScrollView
style={styles.body}
contentContainerStyle={{ paddingBottom: spacing.xl }}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{children}
</ScrollView>
</Animated.View>
</View>
</RNModal>
);
}