/** * BottomSheet — modal bas d'écran respectant le design Memento * Usage: * setV(false)} title="Titre"> * ...children * */ import { useEffect, useRef } from 'react' import { View, Text, Modal, TouchableOpacity, Animated, Pressable, StyleSheet, } from 'react-native' import { X } from 'lucide-react-native' import { C } from '@/lib/theme' interface Props { visible: boolean onClose: () => void title?: string children: React.ReactNode } export function BottomSheet({ visible, onClose, title, children }: Props) { const translateY = useRef(new Animated.Value(400)).current const opacity = useRef(new Animated.Value(0)).current useEffect(() => { if (visible) { Animated.parallel([ Animated.spring(translateY, { toValue: 0, useNativeDriver: true, damping: 20, stiffness: 200 }), Animated.timing(opacity, { toValue: 1, duration: 200, useNativeDriver: true }), ]).start() } else { Animated.parallel([ Animated.timing(translateY, { toValue: 400, duration: 220, useNativeDriver: true }), Animated.timing(opacity, { toValue: 0, duration: 200, useNativeDriver: true }), ]).start() } }, [visible]) return ( {/* Handle bar */} {title && ( {title} )} {children} ) } const s = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(26,26,24,0.5)', justifyContent: 'flex-end' }, sheet: { backgroundColor: C.paper, borderTopLeftRadius: 24, borderTopRightRadius: 24, paddingBottom: 32, shadowColor: '#000', shadowOffset: { width: 0, height: -4 }, shadowOpacity: 0.12, shadowRadius: 16, elevation: 16, }, handle: { width: 36, height: 4, backgroundColor: C.border, borderRadius: 2, alignSelf: 'center', marginTop: 12, marginBottom: 4 }, titleRow: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: C.border }, title: { flex: 1, fontSize: 15, fontWeight: '700', color: C.ink }, closeBtn: { padding: 2 }, })