A React Native app for the ultimate thinking partner.

feat: Extract KnowledgeView component - most complex view

KnowledgeView (✅ Complete):
- Extracted from App.tsx.monolithic lines 2490-2789
- Knowledge management with 3 tabs
- Most complex component (~700 lines)

Features:

**Core Memory Tab**:
- List memory blocks (human, persona, system)
- Search by label or value
- Click to view details
- Shows character count
- 2-column grid on desktop

**Archival Memory Tab**:
- Search passages with query
- Create new passages
- Edit/delete existing passages
- Shows timestamps and tags
- Load more pagination
- Clear search button

**Files Tab**:
- Upload files button
- List uploaded files with dates
- Delete files
- Upload progress indicator
- Empty states

UI Features:
- Tab switcher with active states
- Search bars with icons
- Empty states for each tab
- Loading states (ActivityIndicator)
- Error states
- Responsive layouts (desktop vs mobile)
- Full theme support

Props: 20+ props for complete state management
- Tab state and callbacks
- Core memory state and callbacks
- Archival memory state and callbacks
- Files state and callbacks
- Layout preferences

All UI/logic extracted, ready for integration

Next: Create App.new.tsx to wire all components together

+781
+781
src/views/KnowledgeView.tsx
··· 1 + /** 2 + * KnowledgeView Component 3 + * 4 + * MIGRATION STATUS: ✅ EXTRACTED - Ready for use 5 + * 6 + * REPLACES: App.tsx.monolithic lines 2490-2789 7 + * - Knowledge management interface with 3 tabs 8 + * - Core Memory: View and search memory blocks 9 + * - Archival Memory: Search, create, edit, delete passages 10 + * - Files: Upload, list, delete files 11 + * 12 + * FEATURES: 13 + * - Tab switcher (Core Memory / Archival Memory / Files) 14 + * - Search functionality for Core and Archival 15 + * - File upload/delete 16 + * - Passage creation/editing/deletion 17 + * - Load more pagination for passages 18 + * - Empty states for each tab 19 + * - Loading states 20 + * - Error states 21 + * - Desktop vs mobile layouts (2 columns vs 1) 22 + * 23 + * TAB DETAILS: 24 + * 25 + * **Core Memory Tab**: 26 + * - Lists memory blocks (human, persona, system, etc.) 27 + * - Search by label or value 28 + * - Click to view details 29 + * - Shows character count 30 + * - 2-column grid on desktop 31 + * 32 + * **Archival Memory Tab**: 33 + * - Search passages with query 34 + * - Create new passages (button in search bar) 35 + * - Edit/delete existing passages 36 + * - Shows timestamps and tags 37 + * - Load more pagination 38 + * - Clear search button 39 + * 40 + * **Files Tab**: 41 + * - Upload files button 42 + * - List uploaded files with dates 43 + * - Delete files 44 + * - Upload progress indicator 45 + * - Empty state 46 + * 47 + * CALLBACKS NEEDED: 48 + * - onSelectBlock: (block) => void 49 + * - onFileUpload: () => void 50 + * - onFileDelete: (id, name) => void 51 + * - onPassageCreate: () => void 52 + * - onPassageEdit: (passage) => void 53 + * - onPassageDelete: (id) => void 54 + * - onLoadMorePassages: () => void 55 + * 56 + * STATE NEEDED FROM PARENT: 57 + * - knowledgeTab: 'core' | 'archival' | 'files' 58 + * - memoryBlocks, isLoadingBlocks, blocksError 59 + * - passages, isLoadingPassages, passagesError, hasMorePassages 60 + * - folderFiles, isLoadingFiles, filesError 61 + * - memorySearchQuery, passageSearchQuery 62 + * - isUploadingFile, uploadProgress 63 + * - isDesktop (for 2-column layout) 64 + * 65 + * USED BY: (not yet integrated) 66 + * - [ ] App.new.tsx (planned) 67 + * 68 + * RELATED COMPONENTS: 69 + * - MemoryBlockViewer.tsx (shows block details) 70 + * - PassageModal.tsx (create/edit passages) 71 + */ 72 + 73 + import React from 'react'; 74 + import { 75 + View, 76 + Text, 77 + TouchableOpacity, 78 + TextInput, 79 + FlatList, 80 + ActivityIndicator, 81 + StyleSheet, 82 + } from 'react-native'; 83 + import { Ionicons } from '@expo/vector-icons'; 84 + import type { Theme } from '../theme'; 85 + import type { MemoryBlock, Passage } from '../types/letta'; 86 + 87 + type KnowledgeTab = 'core' | 'archival' | 'files'; 88 + 89 + interface FileItem { 90 + id: string; 91 + fileName?: string; 92 + name?: string; 93 + createdAt?: string; 94 + created_at?: string; 95 + } 96 + 97 + interface KnowledgeViewProps { 98 + theme: Theme; 99 + 100 + // Tab state 101 + knowledgeTab: KnowledgeTab; 102 + onTabChange: (tab: KnowledgeTab) => void; 103 + 104 + // Core Memory 105 + memoryBlocks: MemoryBlock[]; 106 + memorySearchQuery: string; 107 + onMemorySearchChange: (query: string) => void; 108 + isLoadingBlocks: boolean; 109 + blocksError: string | null; 110 + onSelectBlock: (block: MemoryBlock) => void; 111 + 112 + // Archival Memory 113 + passages: Passage[]; 114 + passageSearchQuery: string; 115 + onPassageSearchChange: (query: string) => void; 116 + onPassageSearchSubmit: () => void; 117 + isLoadingPassages: boolean; 118 + passagesError: string | null; 119 + hasMorePassages: boolean; 120 + onLoadMorePassages: () => void; 121 + onPassageCreate: () => void; 122 + onPassageEdit: (passage: Passage) => void; 123 + onPassageDelete: (id: string) => void; 124 + 125 + // Files 126 + folderFiles: FileItem[]; 127 + isLoadingFiles: boolean; 128 + filesError: string | null; 129 + isUploadingFile: boolean; 130 + uploadProgress: string | null; 131 + onFileUpload: () => void; 132 + onFileDelete: (id: string, name: string) => void; 133 + 134 + // Layout 135 + isDesktop: boolean; 136 + } 137 + 138 + export function KnowledgeView(props: KnowledgeViewProps) { 139 + const { 140 + theme, 141 + knowledgeTab, 142 + onTabChange, 143 + memoryBlocks, 144 + memorySearchQuery, 145 + onMemorySearchChange, 146 + isLoadingBlocks, 147 + blocksError, 148 + onSelectBlock, 149 + passages, 150 + passageSearchQuery, 151 + onPassageSearchChange, 152 + onPassageSearchSubmit, 153 + isLoadingPassages, 154 + passagesError, 155 + hasMorePassages, 156 + onLoadMorePassages, 157 + onPassageCreate, 158 + onPassageEdit, 159 + onPassageDelete, 160 + folderFiles, 161 + isLoadingFiles, 162 + filesError, 163 + isUploadingFile, 164 + uploadProgress, 165 + onFileUpload, 166 + onFileDelete, 167 + isDesktop, 168 + } = props; 169 + 170 + return ( 171 + <View style={[styles.container, { backgroundColor: theme.colors.background.primary }]}> 172 + {/* Tab Switcher */} 173 + <View 174 + style={[ 175 + styles.tabsContainer, 176 + { 177 + backgroundColor: theme.colors.background.secondary, 178 + borderBottomColor: theme.colors.border.primary, 179 + }, 180 + ]} 181 + > 182 + <TouchableOpacity 183 + style={[ 184 + styles.tab, 185 + knowledgeTab === 'core' && { 186 + borderBottomColor: theme.colors.text.primary, 187 + borderBottomWidth: 2, 188 + }, 189 + ]} 190 + onPress={() => onTabChange('core')} 191 + > 192 + <Text 193 + style={[ 194 + styles.tabText, 195 + { 196 + color: 197 + knowledgeTab === 'core' 198 + ? theme.colors.text.primary 199 + : theme.colors.text.tertiary, 200 + }, 201 + ]} 202 + > 203 + Core Memory 204 + </Text> 205 + </TouchableOpacity> 206 + 207 + <TouchableOpacity 208 + style={[ 209 + styles.tab, 210 + knowledgeTab === 'archival' && { 211 + borderBottomColor: theme.colors.text.primary, 212 + borderBottomWidth: 2, 213 + }, 214 + ]} 215 + onPress={() => onTabChange('archival')} 216 + > 217 + <Text 218 + style={[ 219 + styles.tabText, 220 + { 221 + color: 222 + knowledgeTab === 'archival' 223 + ? theme.colors.text.primary 224 + : theme.colors.text.tertiary, 225 + }, 226 + ]} 227 + > 228 + Archival Memory 229 + </Text> 230 + </TouchableOpacity> 231 + 232 + <TouchableOpacity 233 + style={[ 234 + styles.tab, 235 + knowledgeTab === 'files' && { 236 + borderBottomColor: theme.colors.text.primary, 237 + borderBottomWidth: 2, 238 + }, 239 + ]} 240 + onPress={() => onTabChange('files')} 241 + > 242 + <Text 243 + style={[ 244 + styles.tabText, 245 + { 246 + color: 247 + knowledgeTab === 'files' 248 + ? theme.colors.text.primary 249 + : theme.colors.text.tertiary, 250 + }, 251 + ]} 252 + > 253 + Files 254 + </Text> 255 + </TouchableOpacity> 256 + </View> 257 + 258 + {/* Search Bar for Core Memory */} 259 + {knowledgeTab === 'core' && ( 260 + <View style={styles.searchContainer}> 261 + <Ionicons 262 + name="search" 263 + size={20} 264 + color={theme.colors.text.tertiary} 265 + style={styles.searchIcon} 266 + /> 267 + <TextInput 268 + style={[ 269 + styles.searchInput, 270 + { 271 + color: theme.colors.text.primary, 272 + backgroundColor: theme.colors.background.tertiary, 273 + borderColor: theme.colors.border.primary, 274 + }, 275 + ]} 276 + placeholder="Search memory blocks..." 277 + placeholderTextColor={theme.colors.text.tertiary} 278 + value={memorySearchQuery} 279 + onChangeText={onMemorySearchChange} 280 + /> 281 + </View> 282 + )} 283 + 284 + {/* Search Bar for Archival Memory */} 285 + {knowledgeTab === 'archival' && ( 286 + <View style={styles.searchContainer}> 287 + <Ionicons 288 + name="search" 289 + size={20} 290 + color={theme.colors.text.tertiary} 291 + style={styles.searchIcon} 292 + /> 293 + <TextInput 294 + style={[ 295 + styles.searchInput, 296 + { 297 + color: theme.colors.text.primary, 298 + backgroundColor: theme.colors.background.tertiary, 299 + borderColor: theme.colors.border.primary, 300 + paddingRight: passageSearchQuery ? 96 : 60, 301 + }, 302 + ]} 303 + placeholder="Search archival memory..." 304 + placeholderTextColor={theme.colors.text.tertiary} 305 + value={passageSearchQuery} 306 + onChangeText={onPassageSearchChange} 307 + onSubmitEditing={onPassageSearchSubmit} 308 + /> 309 + {passageSearchQuery && ( 310 + <TouchableOpacity 311 + style={styles.clearSearchButton} 312 + onPress={() => { 313 + onPassageSearchChange(''); 314 + onPassageSearchSubmit(); 315 + }} 316 + > 317 + <Ionicons name="close-circle" size={20} color={theme.colors.text.tertiary} /> 318 + </TouchableOpacity> 319 + )} 320 + <TouchableOpacity style={styles.createButton} onPress={onPassageCreate}> 321 + <Ionicons name="add-circle-outline" size={24} color={theme.colors.text.primary} /> 322 + </TouchableOpacity> 323 + </View> 324 + )} 325 + 326 + {/* Content Grid */} 327 + <View style={styles.contentGrid}> 328 + {knowledgeTab === 'files' ? ( 329 + // FILES TAB 330 + <> 331 + <View style={styles.filesHeader}> 332 + <Text 333 + style={[ 334 + styles.sectionTitle, 335 + { color: theme.colors.text.secondary, marginBottom: 0 }, 336 + ]} 337 + > 338 + Uploaded Files 339 + </Text> 340 + <TouchableOpacity 341 + onPress={onFileUpload} 342 + disabled={isUploadingFile} 343 + style={styles.fileUploadButton} 344 + > 345 + {isUploadingFile ? ( 346 + <ActivityIndicator size="small" color={theme.colors.text.secondary} /> 347 + ) : ( 348 + <Ionicons name="add-circle-outline" size={24} color={theme.colors.text.primary} /> 349 + )} 350 + </TouchableOpacity> 351 + </View> 352 + 353 + {uploadProgress && ( 354 + <View 355 + style={[ 356 + styles.uploadProgress, 357 + { backgroundColor: theme.colors.background.tertiary }, 358 + ]} 359 + > 360 + <Text style={{ color: theme.colors.text.secondary, fontSize: 14 }}> 361 + {uploadProgress} 362 + </Text> 363 + </View> 364 + )} 365 + 366 + {isLoadingFiles ? ( 367 + <View style={styles.loadingContainer}> 368 + <ActivityIndicator size="large" color={theme.colors.text.secondary} /> 369 + </View> 370 + ) : filesError ? ( 371 + <Text style={[styles.errorText, { textAlign: 'center', marginTop: 40 }]}> 372 + {filesError} 373 + </Text> 374 + ) : folderFiles.length === 0 ? ( 375 + <View style={styles.emptyState}> 376 + <Ionicons 377 + name="folder-outline" 378 + size={64} 379 + color={theme.colors.text.tertiary} 380 + style={{ opacity: 0.3 }} 381 + /> 382 + <Text style={[styles.emptyText, { color: theme.colors.text.tertiary }]}> 383 + No files uploaded yet 384 + </Text> 385 + </View> 386 + ) : ( 387 + <FlatList 388 + data={folderFiles} 389 + keyExtractor={(item) => item.id} 390 + contentContainerStyle={styles.listContent} 391 + renderItem={({ item }) => ( 392 + <View 393 + style={[ 394 + styles.fileCard, 395 + { 396 + backgroundColor: theme.colors.background.secondary, 397 + borderColor: theme.colors.border.primary, 398 + }, 399 + ]} 400 + > 401 + <View style={{ flex: 1 }}> 402 + <Text 403 + style={[styles.fileCardLabel, { color: theme.colors.text.primary }]} 404 + numberOfLines={1} 405 + > 406 + {item.fileName || item.name || 'Untitled'} 407 + </Text> 408 + <Text 409 + style={[ 410 + styles.fileCardPreview, 411 + { color: theme.colors.text.secondary, fontSize: 12 }, 412 + ]} 413 + > 414 + {new Date(item.createdAt || item.created_at || '').toLocaleDateString()} 415 + </Text> 416 + </View> 417 + <TouchableOpacity 418 + onPress={() => onFileDelete(item.id, item.fileName || item.name || '')} 419 + style={{ padding: 8 }} 420 + > 421 + <Ionicons name="trash-outline" size={20} color={theme.colors.status.error} /> 422 + </TouchableOpacity> 423 + </View> 424 + )} 425 + /> 426 + )} 427 + </> 428 + ) : knowledgeTab === 'archival' ? ( 429 + // ARCHIVAL MEMORY TAB 430 + isLoadingPassages ? ( 431 + <View style={styles.loadingContainer}> 432 + <ActivityIndicator size="large" color={theme.colors.text.secondary} /> 433 + </View> 434 + ) : passagesError ? ( 435 + <Text style={[styles.errorText, { textAlign: 'center', marginTop: 40 }]}> 436 + {passagesError} 437 + </Text> 438 + ) : passages.length === 0 ? ( 439 + <View style={styles.emptyState}> 440 + <Ionicons 441 + name="archive-outline" 442 + size={64} 443 + color={theme.colors.text.tertiary} 444 + style={{ opacity: 0.3 }} 445 + /> 446 + <Text style={[styles.emptyText, { color: theme.colors.text.tertiary }]}> 447 + No archival memories yet 448 + </Text> 449 + </View> 450 + ) : ( 451 + <FlatList 452 + data={passages} 453 + keyExtractor={(item) => item.id} 454 + contentContainerStyle={styles.listContent} 455 + renderItem={({ item }) => ( 456 + <View 457 + style={[ 458 + styles.passageCard, 459 + { 460 + backgroundColor: theme.colors.background.secondary, 461 + borderColor: theme.colors.border.primary, 462 + }, 463 + ]} 464 + > 465 + <View style={styles.passageHeader}> 466 + <Text 467 + style={[ 468 + styles.passageDate, 469 + { color: theme.colors.text.tertiary, fontSize: 11, flex: 1 }, 470 + ]} 471 + > 472 + {new Date(item.created_at).toLocaleString()} 473 + </Text> 474 + <View style={styles.passageActions}> 475 + <TouchableOpacity onPress={() => onPassageEdit(item)} style={{ padding: 4 }}> 476 + <Ionicons name="create-outline" size={18} color={theme.colors.text.secondary} /> 477 + </TouchableOpacity> 478 + <TouchableOpacity onPress={() => onPassageDelete(item.id)} style={{ padding: 4 }}> 479 + <Ionicons name="trash-outline" size={18} color={theme.colors.status.error} /> 480 + </TouchableOpacity> 481 + </View> 482 + </View> 483 + <Text 484 + style={[styles.passageText, { color: theme.colors.text.primary }]} 485 + numberOfLines={6} 486 + > 487 + {item.text} 488 + </Text> 489 + {item.tags && item.tags.length > 0 && ( 490 + <View style={styles.tagsContainer}> 491 + {item.tags.map((tag, idx) => ( 492 + <View 493 + key={idx} 494 + style={[ 495 + styles.tag, 496 + { backgroundColor: theme.colors.background.tertiary }, 497 + ]} 498 + > 499 + <Text style={{ color: theme.colors.text.secondary, fontSize: 11 }}> 500 + {tag} 501 + </Text> 502 + </View> 503 + ))} 504 + </View> 505 + )} 506 + </View> 507 + )} 508 + ListFooterComponent={ 509 + hasMorePassages ? ( 510 + <TouchableOpacity onPress={onLoadMorePassages} style={styles.loadMoreButton}> 511 + <Text style={{ color: theme.colors.text.secondary }}>Load more...</Text> 512 + </TouchableOpacity> 513 + ) : null 514 + } 515 + /> 516 + ) 517 + ) : ( 518 + // CORE MEMORY TAB 519 + isLoadingBlocks ? ( 520 + <View style={styles.loadingContainer}> 521 + <ActivityIndicator size="large" color={theme.colors.text.secondary} /> 522 + </View> 523 + ) : blocksError ? ( 524 + <Text style={[styles.errorText, { textAlign: 'center', marginTop: 40 }]}> 525 + {blocksError} 526 + </Text> 527 + ) : ( 528 + <FlatList 529 + data={memoryBlocks.filter((block) => { 530 + if (memorySearchQuery) { 531 + return ( 532 + block.label.toLowerCase().includes(memorySearchQuery.toLowerCase()) || 533 + block.value.toLowerCase().includes(memorySearchQuery.toLowerCase()) 534 + ); 535 + } 536 + return true; 537 + })} 538 + numColumns={isDesktop ? 2 : 1} 539 + key={isDesktop ? 'desktop' : 'mobile'} 540 + keyExtractor={(item) => item.id || item.label} 541 + contentContainerStyle={styles.listContent} 542 + renderItem={({ item }) => ( 543 + <TouchableOpacity 544 + style={[ 545 + styles.memoryCard, 546 + { 547 + backgroundColor: theme.colors.background.secondary, 548 + borderColor: theme.colors.border.primary, 549 + }, 550 + ]} 551 + onPress={() => onSelectBlock(item)} 552 + > 553 + <View style={styles.memoryCardHeader}> 554 + <Text style={[styles.memoryCardLabel, { color: theme.colors.text.primary }]}> 555 + {item.label} 556 + </Text> 557 + <Text style={[styles.memoryCardCount, { color: theme.colors.text.tertiary }]}> 558 + {item.value.length} chars 559 + </Text> 560 + </View> 561 + <Text 562 + style={[styles.memoryCardPreview, { color: theme.colors.text.secondary }]} 563 + numberOfLines={4} 564 + > 565 + {item.value || 'Empty'} 566 + </Text> 567 + </TouchableOpacity> 568 + )} 569 + ListEmptyComponent={ 570 + <View style={styles.emptyState}> 571 + <Ionicons 572 + name="library-outline" 573 + size={64} 574 + color={theme.colors.text.tertiary} 575 + style={{ opacity: 0.3 }} 576 + /> 577 + <Text style={[styles.emptyText, { color: theme.colors.text.secondary }]}> 578 + {memorySearchQuery ? 'No memory blocks found' : 'No memory blocks yet'} 579 + </Text> 580 + </View> 581 + } 582 + /> 583 + ) 584 + )} 585 + </View> 586 + </View> 587 + ); 588 + } 589 + 590 + const styles = StyleSheet.create({ 591 + container: { 592 + flex: 1, 593 + }, 594 + tabsContainer: { 595 + flexDirection: 'row', 596 + justifyContent: 'space-between', 597 + alignItems: 'center', 598 + paddingHorizontal: 16, 599 + paddingVertical: 4, 600 + borderBottomWidth: 1, 601 + }, 602 + tab: { 603 + paddingVertical: 12, 604 + paddingHorizontal: 16, 605 + flex: 1, 606 + alignItems: 'center', 607 + }, 608 + tabText: { 609 + fontSize: 14, 610 + fontFamily: 'Lexend_500Medium', 611 + }, 612 + searchContainer: { 613 + flexDirection: 'row', 614 + alignItems: 'center', 615 + paddingHorizontal: 16, 616 + paddingVertical: 12, 617 + }, 618 + searchIcon: { 619 + position: 'absolute', 620 + left: 24, 621 + zIndex: 1, 622 + }, 623 + searchInput: { 624 + flex: 1, 625 + height: 40, 626 + borderRadius: 20, 627 + paddingLeft: 40, 628 + paddingRight: 16, 629 + fontSize: 14, 630 + borderWidth: 1, 631 + fontFamily: 'Lexend_400Regular', 632 + }, 633 + clearSearchButton: { 634 + position: 'absolute', 635 + right: 64, 636 + padding: 8, 637 + }, 638 + createButton: { 639 + position: 'absolute', 640 + right: 28, 641 + padding: 8, 642 + }, 643 + contentGrid: { 644 + flex: 1, 645 + }, 646 + filesHeader: { 647 + flexDirection: 'row', 648 + justifyContent: 'space-between', 649 + alignItems: 'center', 650 + paddingHorizontal: 8, 651 + paddingVertical: 12, 652 + }, 653 + sectionTitle: { 654 + fontSize: 14, 655 + fontFamily: 'Lexend_600SemiBold', 656 + textTransform: 'uppercase', 657 + letterSpacing: 0.5, 658 + }, 659 + fileUploadButton: { 660 + padding: 4, 661 + }, 662 + uploadProgress: { 663 + marginHorizontal: 8, 664 + marginBottom: 12, 665 + paddingVertical: 8, 666 + paddingHorizontal: 12, 667 + borderRadius: 8, 668 + }, 669 + loadingContainer: { 670 + flex: 1, 671 + justifyContent: 'center', 672 + alignItems: 'center', 673 + }, 674 + emptyState: { 675 + flex: 1, 676 + justifyContent: 'center', 677 + alignItems: 'center', 678 + paddingTop: 80, 679 + }, 680 + emptyText: { 681 + fontSize: 16, 682 + fontFamily: 'Lexend_400Regular', 683 + marginTop: 16, 684 + }, 685 + errorText: { 686 + fontSize: 14, 687 + fontFamily: 'Lexend_400Regular', 688 + color: '#E07042', 689 + }, 690 + listContent: { 691 + padding: 8, 692 + }, 693 + fileCard: { 694 + flexDirection: 'row', 695 + justifyContent: 'space-between', 696 + alignItems: 'center', 697 + padding: 16, 698 + borderRadius: 12, 699 + borderWidth: 1, 700 + marginBottom: 8, 701 + }, 702 + fileCardLabel: { 703 + fontSize: 16, 704 + fontFamily: 'Lexend_500Medium', 705 + }, 706 + fileCardPreview: { 707 + fontSize: 12, 708 + fontFamily: 'Lexend_400Regular', 709 + marginTop: 4, 710 + }, 711 + passageCard: { 712 + padding: 16, 713 + borderRadius: 12, 714 + borderWidth: 1, 715 + marginBottom: 12, 716 + }, 717 + passageHeader: { 718 + flexDirection: 'row', 719 + justifyContent: 'space-between', 720 + alignItems: 'flex-start', 721 + marginBottom: 8, 722 + }, 723 + passageDate: { 724 + fontFamily: 'Lexend_400Regular', 725 + }, 726 + passageActions: { 727 + flexDirection: 'row', 728 + gap: 8, 729 + }, 730 + passageText: { 731 + fontSize: 14, 732 + fontFamily: 'Lexend_400Regular', 733 + lineHeight: 20, 734 + }, 735 + tagsContainer: { 736 + flexDirection: 'row', 737 + flexWrap: 'wrap', 738 + gap: 6, 739 + marginTop: 8, 740 + }, 741 + tag: { 742 + paddingHorizontal: 8, 743 + paddingVertical: 4, 744 + borderRadius: 4, 745 + }, 746 + memoryCard: { 747 + flex: 1, 748 + padding: 16, 749 + borderRadius: 12, 750 + borderWidth: 1, 751 + margin: 4, 752 + minHeight: 120, 753 + }, 754 + memoryCardHeader: { 755 + flexDirection: 'row', 756 + justifyContent: 'space-between', 757 + alignItems: 'center', 758 + marginBottom: 8, 759 + }, 760 + memoryCardLabel: { 761 + fontSize: 16, 762 + fontFamily: 'Lexend_600SemiBold', 763 + flex: 1, 764 + }, 765 + memoryCardCount: { 766 + fontSize: 11, 767 + fontFamily: 'Lexend_400Regular', 768 + marginLeft: 8, 769 + }, 770 + memoryCardPreview: { 771 + fontSize: 13, 772 + fontFamily: 'Lexend_400Regular', 773 + lineHeight: 18, 774 + }, 775 + loadMoreButton: { 776 + padding: 16, 777 + alignItems: 'center', 778 + }, 779 + }); 780 + 781 + export default KnowledgeView;