import { useEffect, useState } from 'react'; import { Animated, Dimensions, Easing, Modal, Pressable, Text, View, } from 'react-native'; import { initialWindowMetrics, SafeAreaProvider, SafeAreaView, } from 'react-native-safe-area-context'; import { useAuthStore } from '@/stores/auth-store'; const SCREEN_WIDTH = Dimensions.get('window').width; const DRAWER_WIDTH = Math.min(320, Math.round(SCREEN_WIDTH * 0.82)); const ANIM_MS = 220; interface DrawerProps { open: boolean; onClose: () => void; onNavigateAccount: () => void; onNavigateSettings: () => void; } export function Drawer({ open, onClose, onNavigateAccount }: DrawerProps) { // Lazy-init so each Animated.Value is created once; the setters are never // called — the values are mutated internally by the native driver. const [translateX] = useState(() => new Animated.Value(-DRAWER_WIDTH)); const [backdropOpacity] = useState(() => new Animated.Value(0)); useEffect(() => { Animated.parallel([ Animated.timing(translateX, { toValue: open ? 0 : -DRAWER_WIDTH, duration: ANIM_MS, easing: Easing.out(Easing.cubic), useNativeDriver: true, }), Animated.timing(backdropOpacity, { toValue: open ? 0.4 : 0, duration: ANIM_MS, easing: Easing.out(Easing.cubic), useNativeDriver: true, }), ]).start(); }, [open, translateX, backdropOpacity]); const user = useAuthStore((s) => s.user); const signOut = useAuthStore((s) => s.signOut); const isSigningOut = useAuthStore((s) => s.isSigningOut); const initials = user?.email_prefix.slice(0, 2).toUpperCase() ?? '??'; return ( {/* Modal mounts a separate native view tree on iOS — without a fresh SafeAreaProvider seeded with initialWindowMetrics, useSafeAreaInsets inside reports {0,0,0,0} on the first frame and content snaps from the status bar down to the safe area once metrics resolve. */} {initials} {user?.email_prefix ?? ''} {user?.email ?? ''} { onClose(); onNavigateAccount(); }} /> { void signOut(); }} tone="destructive" /> ); } function DrawerRow({ label, onPress, disabled, tone = 'default', }: { label: string; onPress: () => void; disabled?: boolean; tone?: 'default' | 'destructive'; }) { return ( {label} ); }