import { Icon } from '@/components/ui/icon'; import { Text } from '@/components/ui/text'; import { View } from '@/components/ui/view'; import { useColor } from '@/hooks/useColor'; import { CORNERS, FONT_SIZE, HEIGHT } from '@/theme/globals'; import { Search, X } from 'lucide-react-native'; import React, { useCallback, useRef, useState } from 'react'; import { ActivityIndicator, TextInput, TextInputProps, TextStyle, TouchableOpacity, ViewStyle, } from 'react-native'; interface SearchBarProps extends Omit { loading?: boolean; onSearch?: (query: string) => void; onClear?: () => void; showClearButton?: boolean; leftIcon?: React.ReactNode; rightIcon?: React.ReactNode; containerStyle?: ViewStyle | ViewStyle[]; inputStyle?: TextStyle | TextStyle[]; debounceMs?: number; } export function SearchBar({ loading = false, onSearch, onClear, showClearButton = true, leftIcon, rightIcon, containerStyle, inputStyle, debounceMs = 300, placeholder = 'Search...', value, onChangeText, ...props }: SearchBarProps) { const [internalValue, setInternalValue] = useState(value || ''); const debounceRef = useRef(null); const inputRef = useRef(null); // Theme colors const cardColor = useColor('card'); const textColor = useColor('text'); const muted = useColor('textMuted'); const icon = useColor('icon'); // Handle text change with debouncing const handleTextChange = useCallback( (text: string) => { setInternalValue(text); onChangeText?.(text); if (onSearch && debounceMs > 0) { if (debounceRef.current) { clearTimeout(debounceRef.current); } (debounceRef.current as any) = setTimeout(() => { onSearch(text); }, debounceMs); } else if (onSearch) { onSearch(text); } }, [onChangeText, onSearch, debounceMs] ); // Handle clear button press const handleClear = useCallback(() => { setInternalValue(''); onChangeText?.(''); onClear?.(); onSearch?.(''); if (debounceRef.current) { clearTimeout(debounceRef.current); } }, [onChangeText, onClear, onSearch]); // Get container style based on variant and size const baseStyle: ViewStyle = { flexDirection: 'row', alignItems: 'center', backgroundColor: cardColor, height: HEIGHT, paddingHorizontal: 16, borderRadius: CORNERS, }; const baseInputStyle = { flex: 1, fontSize: FONT_SIZE, color: textColor, marginHorizontal: 8, }; const displayValue = value !== undefined ? value : internalValue; const showClear = showClearButton && displayValue.length > 0; return ( {/* Left Icon */} {leftIcon || } {/* Text Input */} {/* Loading Indicator */} {loading && ( )} {/* Clear Button */} {showClear && !loading && ( )} {/* Right Icon */} {rightIcon && !showClear && !loading && rightIcon} ); } // SearchBar with suggestions dropdown interface SearchBarWithSuggestionsProps extends SearchBarProps { suggestions?: string[]; onSuggestionPress?: (suggestion: string) => void; maxSuggestions?: number; showSuggestions?: boolean; } export function SearchBarWithSuggestions({ suggestions = [], onSuggestionPress, maxSuggestions = 5, showSuggestions = true, containerStyle, ...searchBarProps }: SearchBarWithSuggestionsProps) { const [isExpanded, setIsExpanded] = useState(false); const cardColor = useColor('card'); const borderColor = useColor('border'); const filteredSuggestions = suggestions .filter((suggestion) => suggestion .toLowerCase() .includes((searchBarProps.value || '').toLowerCase()) ) .slice(0, maxSuggestions); const shouldShowSuggestions = showSuggestions && isExpanded && filteredSuggestions.length > 0 && (searchBarProps.value || '').length > 0; const handleSuggestionPress = (suggestion: string) => { onSuggestionPress?.(suggestion); setIsExpanded(false); }; return ( { setIsExpanded(true); searchBarProps.onFocus?.(e); }} onBlur={(e) => { // Delay hiding suggestions to allow for suggestion tap setTimeout(() => setIsExpanded(false), 150); searchBarProps.onBlur?.(e); }} /> {/* Suggestions Dropdown */} {shouldShowSuggestions && ( {filteredSuggestions.map((suggestion, index) => ( handleSuggestionPress(suggestion)} style={{ paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: index < filteredSuggestions.length - 1 ? 0.6 : 0, borderBottomColor: borderColor, }} activeOpacity={0.7} > {suggestion} ))} )} ); }