import React, { useEffect, useRef, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
FlatList,
StyleSheet,
Animated,
Dimensions,
Pressable,
} from 'react-native';
import type { Conversation } from '../../store/chatTypes';
import { colors, spacing, typography, borderRadius } from '../../theme/lightTheme';
const DRAWER_WIDTH = Math.min(Dimensions.get('window').width * 0.80, 310);
interface Props {
visible: boolean;
conversations: Conversation[];
activeConversationId: string | null;
onClose: () => void;
onNewChat: () => void;
onSelectConversation: (id: string) => void;
onDeleteConversation: (id: string) => void;
}
function formatDate(ts: number): string {
const diffDays = Math.floor((Date.now() - ts) / 86400000);
if (diffDays === 0) return "Aujourd'hui";
if (diffDays === 1) return 'Hier';
if (diffDays < 7) return `Il y a ${diffDays} j`;
return new Date(ts).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
}
export default function ChatDrawer({
visible,
conversations,
activeConversationId,
onClose,
onNewChat,
onSelectConversation,
onDeleteConversation,
}: Props) {
const translateX = useRef(new Animated.Value(-DRAWER_WIDTH)).current;
const backdropOpacity = useRef(new Animated.Value(0)).current;
// Keep mounted during the close animation
const [mounted, setMounted] = useState(false);
useEffect(() => {
if (visible) {
setMounted(true);
Animated.parallel([
Animated.timing(translateX, {
toValue: 0,
duration: 250,
useNativeDriver: true,
}),
Animated.timing(backdropOpacity, {
toValue: 0.5,
duration: 250,
useNativeDriver: true,
}),
]).start();
} else {
Animated.parallel([
Animated.timing(translateX, {
toValue: -DRAWER_WIDTH,
duration: 210,
useNativeDriver: true,
}),
Animated.timing(backdropOpacity, {
toValue: 0,
duration: 210,
useNativeDriver: true,
}),
]).start(() => setMounted(false));
}
}, [visible, translateX, backdropOpacity]);
if (!mounted) return null;
const sorted = [...conversations].sort((a, b) => b.updatedAt - a.updatedAt);
const renderItem = ({ item }: { item: Conversation }) => {
const isActive = item.id === activeConversationId;
const lastMsg = item.messages[item.messages.length - 1];
return (
{ onSelectConversation(item.id); onClose(); }}
activeOpacity={0.75}
>
{item.title}
{item.messages.length} msg ยท {formatDate(item.updatedAt)}
{lastMsg ? (
{lastMsg.role === 'user' ? '๐ค ' : '๐ค '}
{lastMsg.content}
) : (
Vide
)}
onDeleteConversation(item.id)}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
โ
);
};
return (
{/* Backdrop */}
{/* Drawer panel */}
{/* Header */}
Conversations
โ
{/* New chat button */}
{ onNewChat(); onClose(); }}
activeOpacity={0.85}
>
๏ผ Nouveau chat
{/* Conversations list */}
item.id}
renderItem={renderItem}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
Aucune conversation
}
/>
);
}
const styles = StyleSheet.create({
backdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: colors.textPrimary,
},
drawer: {
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
width: DRAWER_WIDTH,
backgroundColor: colors.surface,
shadowColor: colors.textPrimary,
shadowOffset: { width: 4, height: 0 },
shadowOpacity: 0.25,
shadowRadius: 12,
elevation: 16,
},
drawerHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.lg,
paddingTop: spacing.xxl,
paddingBottom: spacing.md,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.border,
},
drawerTitle: {
fontSize: typography.sizes.lg,
fontWeight: typography.weights.semibold,
color: colors.textPrimary,
},
closeBtnText: {
fontSize: 18,
color: colors.textSecondary,
},
newChatBtn: {
margin: spacing.md,
paddingVertical: spacing.md,
backgroundColor: colors.primary,
borderRadius: borderRadius.lg,
alignItems: 'center',
},
newChatBtnText: {
color: colors.surface,
fontSize: typography.sizes.md,
fontWeight: typography.weights.semibold,
},
listContent: {
paddingHorizontal: spacing.sm,
paddingBottom: spacing.xxl,
},
convItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.sm,
paddingHorizontal: spacing.md,
borderRadius: borderRadius.lg,
marginBottom: 2,
},
convItemActive: {
backgroundColor: colors.surfaceSecondary,
borderLeftWidth: 3,
borderLeftColor: colors.primary,
},
convItemContent: {
flex: 1,
marginRight: spacing.sm,
},
convTitle: {
fontSize: typography.sizes.sm,
fontWeight: typography.weights.medium,
color: colors.textPrimary,
marginBottom: 2,
},
convTitleActive: {
color: colors.primary,
fontWeight: typography.weights.semibold,
},
convMeta: {
fontSize: 11,
color: colors.textTertiary,
marginBottom: 2,
},
convPreview: {
fontSize: 12,
color: colors.textSecondary,
},
convPreviewEmpty: {
fontSize: 12,
color: colors.textTertiary,
fontStyle: 'italic',
},
deleteBtn: {
padding: 4,
},
deleteBtnText: {
fontSize: 14,
color: colors.textTertiary,
},
emptyText: {
textAlign: 'center',
color: colors.textTertiary,
marginTop: spacing.xl,
fontSize: typography.sizes.sm,
},
});