A React Native app for the ultimate thinking partner.
1import React, { useState, useCallback, useRef } from 'react';
2import { View, Text, TouchableOpacity, StyleSheet, Platform, UIManager } from 'react-native';
3import MessageContent from './MessageContent';
4import { darkTheme } from '../theme';
5
6// Enable LayoutAnimation on Android
7if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
8 UIManager.setLayoutAnimationEnabledExperimental(true);
9}
10
11interface ExpandableMessageContentProps {
12 content: string;
13 isUser: boolean;
14 isDark?: boolean;
15 lineLimit?: number;
16 onToggle?: (expanding: boolean) => void;
17}
18
19const ExpandableMessageContent: React.FC<ExpandableMessageContentProps> = ({
20 content,
21 isUser,
22 isDark = true,
23 lineLimit = 3,
24 onToggle
25}) => {
26 const [isExpanded, setIsExpanded] = useState(false);
27 const [shouldShowToggle, setShouldShowToggle] = useState(false);
28
29 // Only apply expandable behavior to user messages
30 if (!isUser) {
31 return <MessageContent content={content} isUser={isUser} isDark={isDark} />;
32 }
33
34 // Simple heuristic: estimate if content would exceed line limit
35 // Assuming average ~60 chars per line in the chat bubble
36 const estimatedLines = content.length / 60;
37 const hasLineBreaks = (content.match(/\n/g) || []).length;
38 const totalEstimatedLines = Math.max(estimatedLines, hasLineBreaks + 1);
39
40 // Show toggle if content is likely to exceed limit
41 const showToggle = totalEstimatedLines > lineLimit;
42
43 if (!showToggle) {
44 return <MessageContent content={content} isUser={isUser} isDark={isDark} />;
45 }
46
47 const handleToggle = useCallback(() => {
48 const nextExpanded = !isExpanded;
49 // Notify parent before state change
50 onToggle?.(nextExpanded);
51 // Avoid global LayoutAnimation to prevent list flicker; just toggle state
52 setIsExpanded(nextExpanded);
53 }, [isExpanded, onToggle]);
54
55 return (
56 <View>
57 <View style={isExpanded ? undefined : styles.collapsedContainer}>
58 <MessageContent
59 content={isExpanded ? content : content.slice(0, 180) + '...'}
60 isUser={isUser}
61 isDark={isDark}
62 />
63 </View>
64 <TouchableOpacity
65 onPress={handleToggle}
66 style={styles.toggleButton}
67 activeOpacity={0.7}
68 >
69 <Text style={[styles.toggleText, { color: isDark ? '#000000' : '#FFFFFF' }]}>
70 {isExpanded ? 'See less' : 'See more'}
71 </Text>
72 </TouchableOpacity>
73 </View>
74 );
75};
76
77const styles = StyleSheet.create({
78 collapsedContainer: {
79 maxHeight: 72, // Approximately 3 lines with standard font size
80 overflow: 'hidden',
81 },
82 toggleButton: {
83 marginTop: 4,
84 paddingVertical: 2,
85 },
86 toggleText: {
87 fontSize: 13,
88 fontWeight: '500',
89 opacity: 0.6,
90 },
91});
92
93export default React.memo(ExpandableMessageContent);