A React Native app for the ultimate thinking partner.
1import React from 'react';
2import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
3import { darkTheme } from '../theme';
4import { Ionicons } from '@expo/vector-icons';
5import { LettaAgent } from '../types/letta';
6
7interface AgentCardProps {
8 agent: LettaAgent;
9 isSelected: boolean;
10 onPress: () => void;
11 isFavorited?: boolean;
12 onToggleFavorite?: () => void;
13}
14
15const AgentCard: React.FC<AgentCardProps> = ({ agent, isSelected, onPress, isFavorited, onToggleFavorite }) => {
16 const formatDate = (dateString: string) => {
17 const date = new Date(dateString);
18 return date.toLocaleDateString('en-US', {
19 month: 'short',
20 day: 'numeric',
21 });
22 };
23
24 return (
25 <TouchableOpacity
26 style={[styles.container, isSelected && styles.selectedContainer]}
27 onPress={onPress}
28 activeOpacity={0.7}
29 >
30 <View style={styles.content}>
31 <View style={styles.header}>
32 <View style={styles.avatarContainer}>
33 <Ionicons
34 name="person-outline"
35 size={20}
36 color={isSelected ? darkTheme.colors.text.inverse : darkTheme.colors.interactive.primary}
37 />
38 </View>
39 <View style={styles.textContainer}>
40 <View style={styles.rowHeader}>
41 <Text
42 style={[styles.name, isSelected && styles.selectedText]}
43 numberOfLines={1}
44 >
45 {agent.name}
46 </Text>
47 {onToggleFavorite && (
48 <TouchableOpacity
49 accessibilityLabel={isFavorited ? 'Unfavorite agent' : 'Favorite agent'}
50 onPress={onToggleFavorite}
51 style={styles.starBtn}
52 >
53 <Ionicons name={isFavorited ? 'star' : 'star-outline'} size={16} color={isFavorited ? '#ffd166' : (isSelected ? darkTheme.colors.text.inverse : darkTheme.colors.text.secondary)} />
54 </TouchableOpacity>
55 )}
56 </View>
57 <Text
58 style={[styles.date, isSelected && styles.selectedSubtext]}
59 >
60 Created {formatDate(agent.created_at)}
61 </Text>
62 </View>
63 </View>
64
65 {agent.description && (
66 <Text
67 style={[styles.description, isSelected && styles.selectedSubtext]}
68 numberOfLines={2}
69 >
70 {agent.description}
71 </Text>
72 )}
73
74 {agent.tags && agent.tags.length > 0 && (
75 <View style={styles.tagsContainer}>
76 {agent.tags.slice(0, 3).map((tag, index) => (
77 <View
78 key={index}
79 style={[styles.tag, isSelected && styles.selectedTag]}
80 >
81 <Text
82 style={[styles.tagText, isSelected && styles.selectedTagText]}
83 >
84 {tag}
85 </Text>
86 </View>
87 ))}
88 {agent.tags.length > 3 && (
89 <Text style={[styles.moreText, isSelected && styles.selectedSubtext]}>
90 +{agent.tags.length - 3} more
91 </Text>
92 )}
93 </View>
94 )}
95 </View>
96
97 {isSelected && (
98 <View style={styles.selectedIndicator}>
99 <Ionicons name="checkmark" size={16} color={darkTheme.colors.text.inverse} />
100 </View>
101 )}
102 </TouchableOpacity>
103 );
104};
105
106const styles = StyleSheet.create({
107 container: {
108 marginHorizontal: 16,
109 marginVertical: 6,
110 padding: 16,
111 backgroundColor: darkTheme.colors.background.tertiary,
112 borderRadius: 12,
113 borderWidth: 1,
114 borderColor: darkTheme.colors.border.primary,
115 elevation: 1,
116 shadowColor: '#000',
117 shadowOffset: {
118 width: 0,
119 height: 1,
120 },
121 shadowOpacity: 0.1,
122 shadowRadius: 2,
123 },
124 selectedContainer: {
125 backgroundColor: darkTheme.colors.interactive.primary,
126 borderColor: darkTheme.colors.interactive.primary,
127 },
128 content: {
129 flex: 1,
130 },
131 header: {
132 flexDirection: 'row',
133 alignItems: 'center',
134 marginBottom: 8,
135 },
136 rowHeader: {
137 flexDirection: 'row',
138 alignItems: 'center',
139 justifyContent: 'space-between',
140 },
141 avatarContainer: {
142 width: 40,
143 height: 40,
144 borderRadius: 20,
145 backgroundColor: darkTheme.colors.background.surface,
146 alignItems: 'center',
147 justifyContent: 'center',
148 marginRight: 12,
149 },
150 textContainer: {
151 flex: 1,
152 },
153 name: {
154 fontSize: 16,
155 fontWeight: '600',
156 color: darkTheme.colors.text.primary,
157 marginBottom: 2,
158 },
159 starBtn: {
160 paddingHorizontal: 6,
161 paddingVertical: 4,
162 marginLeft: 8,
163 },
164 selectedText: {
165 color: darkTheme.colors.text.inverse,
166 },
167 date: {
168 fontSize: 12,
169 color: darkTheme.colors.text.secondary,
170 },
171 selectedSubtext: {
172 color: 'rgba(255, 255, 255, 0.8)',
173 },
174 description: {
175 fontSize: 14,
176 color: darkTheme.colors.text.secondary,
177 lineHeight: 18,
178 marginBottom: 8,
179 },
180 tagsContainer: {
181 flexDirection: 'row',
182 alignItems: 'center',
183 flexWrap: 'wrap',
184 },
185 tag: {
186 backgroundColor: darkTheme.colors.background.surface,
187 borderRadius: 12,
188 paddingHorizontal: 8,
189 paddingVertical: 4,
190 marginRight: 6,
191 marginBottom: 4,
192 },
193 selectedTag: {
194 backgroundColor: 'rgba(255, 255, 255, 0.2)',
195 },
196 tagText: {
197 fontSize: 11,
198 color: darkTheme.colors.interactive.primary,
199 fontWeight: '500',
200 },
201 selectedTagText: {
202 color: darkTheme.colors.text.inverse,
203 },
204 moreText: {
205 fontSize: 11,
206 color: darkTheme.colors.text.secondary,
207 fontStyle: 'italic',
208 },
209 selectedIndicator: {
210 position: 'absolute',
211 top: 12,
212 right: 12,
213 width: 24,
214 height: 24,
215 borderRadius: 12,
216 backgroundColor: 'rgba(255, 255, 255, 0.2)',
217 alignItems: 'center',
218 justifyContent: 'center',
219 },
220});
221
222export default AgentCard;