import React, { useRef, useEffect } from 'react'; import { View, TextInput, TouchableOpacity, Text, StyleSheet, Animated } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; import type { ComponentProps } from 'react'; type IconName = ComponentProps['name']; export interface SearchFilters { name: boolean; ocrText: boolean; } export type SortKey = 'date' | 'name' | 'size'; export type SortDirection = 'asc' | 'desc'; export interface SortState { key: SortKey; direction: SortDirection; } interface SearchBarProps { query: string; onQueryChange: (q: string) => void; onClear: () => void; filters: SearchFilters; onFiltersChange: (f: SearchFilters) => void; sort: SortState; onSortChange: (s: SortState) => void; bottomPadding?: number; } const FILTER_OPTIONS: { key: keyof SearchFilters; label: string; icon: IconName }[] = [ { key: 'name', label: 'Nom', icon: 'drive-file-rename-outline' }, { key: 'ocrText', label: 'Texte OCR', icon: 'document-scanner' }, ]; const SORT_OPTIONS: { key: SortKey; label: string; icon: IconName }[] = [ { key: 'date', label: 'Date', icon: 'schedule' }, { key: 'name', label: 'Nom', icon: 'sort-by-alpha' }, { key: 'size', label: 'Taille', icon: 'data-usage' }, ]; const SORT_LABELS: Record = { date: 'Date', name: 'Nom', size: 'Taille', }; export function SearchBar({ query, onQueryChange, onClear, filters, onFiltersChange, sort, onSortChange, bottomPadding = 0 }: SearchBarProps) { const animatedHeight = useRef(new Animated.Value(0)).current; const [panelOpen, setPanelOpen] = React.useState(false); useEffect(() => { Animated.spring(animatedHeight, { toValue: panelOpen ? 1 : 0, useNativeDriver: false, tension: 60, friction: 8, }).start(); }, [panelOpen]); const panelMaxHeight = animatedHeight.interpolate({ inputRange: [0, 1], outputRange: [0, 260], }); const toggleFilter = (key: keyof SearchFilters) => { onFiltersChange({ ...filters, [key]: !filters[key] }); }; const selectSort = (key: SortKey) => { if (sort.key === key) { onSortChange({ key, direction: sort.direction === 'asc' ? 'desc' : 'asc' }); } else { onSortChange({ key, direction: 'desc' }); } }; const hasActiveFilter = filters.name || filters.ocrText || sort.key !== 'date'; return ( {query.length > 0 && ( )} setPanelOpen(!panelOpen)} > Rechercher dans {FILTER_OPTIONS.map((opt) => ( toggleFilter(opt.key)} > {opt.label} ))} Trier par {SORT_OPTIONS.map((opt) => { const active = sort.key === opt.key; return ( selectSort(opt.key)} > {opt.label} ); })} {sort.key !== 'date' && ( Tri : {SORT_LABELS[sort.key]} ({sort.direction === 'asc' ? 'A → Z' : 'Z → A'}) )} ); } const styles = StyleSheet.create({ wrapper: { backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#e0e0e0', }, inputRow: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingVertical: 10, gap: 8, }, inputContainer: { flex: 1, flexDirection: 'row', alignItems: 'center', backgroundColor: '#f5f5f5', borderRadius: 10, paddingHorizontal: 10, }, searchIcon: { marginRight: 6, }, input: { flex: 1, paddingVertical: 8, fontSize: 14, color: '#333', }, clearBtn: { padding: 4, }, iconBtn: { width: 40, height: 40, borderRadius: 10, borderWidth: 1, borderColor: '#1976D2', justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff', }, iconBtnActive: { backgroundColor: '#1976D2', }, filterPanel: { flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', paddingHorizontal: 12, paddingBottom: 10, gap: 8, overflow: 'hidden', }, sectionLabel: { width: '100%', fontSize: 12, fontWeight: '700', color: '#666', marginTop: 4, textTransform: 'uppercase', letterSpacing: 0.5, }, filterChip: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderColor: '#1976D2', gap: 4, }, filterChipActive: { backgroundColor: '#1976D2', }, filterChipText: { fontSize: 12, color: '#1976D2', }, filterChipTextActive: { color: '#fff', }, sortHint: { width: '100%', fontSize: 12, color: '#999', }, });