ThatAIguyA world of ideas

THE CONSCIOUSNESS ATLAS
Read. Experiment. Follow the evidence.

ConsciousnessLearn · Explore · Joseph Anady

Janady13/llms-txt-hub / apps/web/components/search/search-autocomplete.tsx

Source identity, revision, and recovered contents.

GitHub sourceSource text recovered

Source identity

FieldRecorded value
Source typeGitHub source
Original recordJanady13/llms-txt-hub / apps/web/components/search/search-autocomplete.tsx
DateNot specified in source
RepositoryJanady13/llms-txt-hub
Revisione0f4bf978d4df168369c7601d9c5f5ea89e84377
Branchmain
Path or archive memberapps/web/components/search/search-autocomplete.tsx
Original SHA-256074693f59228b3284a10cf054acc21cc29595eb42b234327f9a51872925d2848
Git blob SHA6b2a3c3a0bcd5539cb278f7e6405fcdd10608b8f
Reading-copy SHA-256074693f59228b3284a10cf054acc21cc29595eb42b234327f9a51872925d2848

Original source ↗

Recovered source transcript

'use client' import { cn } from '@thedaviddias/design-system/lib/utils' import { logger } from '@thedaviddias/logging' import { ArrowRight, Clock, Search, TrendingUp } from 'lucide-react' import { useRouter } from 'next/navigation' import { useCallback, useEffect, useRef, useState } from 'react' import { useAnalyticsEvents } from '@/components/analytics-tracker' import { categories } from '@/lib/categories' import { getRoute } from '@/lib/routes' import { Favicon } from './favicon' interface SearchSuggestion { type: 'website' | 'category' | 'recent' | 'trending' title: string description?: string url?: string category?: string website?: string icon?: React.ComponentType<{ className?: string }> } interface SearchAutocompleteProps { searchQuery: string onSelect?: (suggestion: SearchSuggestion) => void isOpen: boolean onClose: () => void anchorRef?: React.RefObject<HTMLInputElement | null> } /** * Renders a search autocomplete dropdown with suggestions, recent searches, and categories */ export function SearchAutocomplete({ searchQuery, onSelect, isOpen, onClose, anchorRef }: SearchAutocompleteProps) { const [suggestions, setSuggestions] = useState<SearchSuggestion[]>([]) const [selectedIndex, setSelectedIndex] = useState(-1) const [loading, setLoading] = useState(false) const router = useRouter() const dropdownRef = useRef<HTMLDivElement>(null) const { trackSearch, trackSearchAutocomplete } = useAnalyticsEvents() const getRecentSearches = useCallback((): string[] => { if (typeof window === 'undefined') return [] const recent = localStorage.getItem('recentSearches') return recent ? JSON.parse(recent).slice(0, 3) : [] }, []) const saveRecentSearch = useCallback( (query: string) => { if (typeof window === 'undefined') return const recent = getRecentSearches() const updated = [query, ...recent.filter(s => s !== query)].slice(0, 5) localStorage.setItem('recentSearches', JSON.stringify(updated)) }, [getRecentSearches] ) useEffect(() => { /** Fetches and filters search suggestions based on the current query */ const fetchSuggestions = async () => { if (!searchQuery.trim()) { const recentSearches = getRecentSearches().map(search => ({ type: 'recent' as const, title: search, icon: Clock })) const categoryMatches = categories.slice(0, 3).map(cat => ({ type: 'category' as const, title: `Browse ${cat.name}`, description: `View all ${cat.name.toLowerCase()} websites`, icon: cat.icon, url: getRoute('category.page', { category: cat.slug }) })) setSuggestions([...recentSearches, ...categoryMatches]) setSelectedIndex(-1) return } setLoading(true) try { const response = await fetch('/search/search-index.json') if (!response.ok) throw new Error('Failed to fetch search index') const searchIndex = await response.json() const query = searchQuery.toLowerCase() const websiteMatches = searchIndex .filter((item: any) => { const searchableText = `${item.name} ${item.description} ${item.category}`.toLowerCase() return searchableText.includes(query) }) .slice(0, 5) .map((item: any) => ({ type: 'website' as const, title: item.name, description: item.description, url: getRoute('website.detail', { slug: item.slug }), category: item.category, website: item.website })) const categoryMatches = categories .filter(cat => cat.name.toLowerCase().includes(query) || cat.slug.includes(query)) .slice(0, 2) .map(cat => ({ type: 'category' as const, title: `Browse ${cat.name}`, description: `View all ${cat.name.toLowerCase()} websites`, icon: cat.icon, url: getRoute('category.page', { category: cat.slug }) })) setSuggestions([...websiteMatches, ...categoryMatches]) setSelectedIndex(-1) } catch (error) { logger.error('Error fetching suggestions:', { data: error, tags: { type: 'component' } }) setSuggestions([]) } finally { setLoading(false) } } const debounceTimer = setTimeout(fetchSuggestions, 150) return () => clearTimeout(debounceTimer) }, [searchQuery, getRecentSearches]) useEffect(() => { if (!isOpen) return /** Handles keyboard navigation within the autocomplete dropdown */ const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault() setSelectedIndex(prev => (prev < suggestions.length - 1 ? prev + 1 : 0)) break case 'ArrowUp': e.preventDefault() setSelectedIndex(prev => (prev > 0 ? prev - 1 : suggestions.length - 1)) break case 'Enter': e.preventDefault() if (selectedIndex >= 0 && suggestions[selectedIndex]) { handleSuggestionClick(suggestions[selectedIndex]) } else if (searchQuery.trim()) { trackSearch(searchQuery, 0, 'autocomplete-keyboard-enter') saveRecentSearch(searchQuery) router.push(`${getRoute('search')}?q=${encodeURIComponent(searchQuery)}`) onClose() } break case 'Escape': e.preventDefault() onClose() break } } document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) }, [isOpen, selectedIndex, suggestions, searchQuery, router, onClose, saveRecentSearch]) useEffect(() => { if (!isOpen) return /** Closes the dropdown when clicking outside */ const handleClickOutside = (e: MouseEvent) => { const target = e.target if (!(target instanceof Node)) return if ( dropdownRef.current && !dropdownRef.current.contains(target) && anchorRef?.current && !anchorRef.current.contains(target) ) { onClose() } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, [isOpen, onClose, anchorRef]) /** Navigates to the selected suggestion and tracks the interaction */ const handleSuggestionClick = (suggestion: SearchSuggestion) => { trackSearchAutocomplete(searchQuery, suggestion.title, `autocomplete-${suggestion.type}`) if (suggestion.type === 'recent') { saveRecentSearch(suggestion.title) trackSearch(suggestion.title, 0, 'autocomplete-recent') router.push(`${getRoute('search')}?q=${encodeURIComponent(suggestion.title)}`) } else if (suggestion.url) { router.push(suggestion.url) } onSelect?.(suggestion) onClose() } if (!isOpen || (!suggestions.length && !loading && searchQuery)) return null return ( <div ref={dropdownRef} className="absolute top-full mt-2 w-full bg-background border rounded-lg shadow-lg z-[100] max-h-[400px] overflow-y-auto" > {loading ? ( <div className="p-4 text-center text-muted-foreground"> <div className="animate-pulse">Searching...</div> </div> ) : suggestions.length === 0 ? ( <div className="p-4 text-center text-muted-foreground"> {searchQuery ? 'No suggestions found' : 'Start typing to search'} </div> ) : ( <div className="py-2"> {suggestions.map((suggestion, index) => { const Icon = suggestion.icon || Search const isSelected = index === selectedIndex return ( <button key={`${suggestion.type}-${suggestion.title}-${index}`} type="button" onClick={() => handleSuggestionClick(suggestion)} onMouseEnter={() => setSelectedIndex(index)} className={cn( 'w-full px-4 py-3 flex items-start gap-3 hover:bg-muted/50 transition-colors text-left cursor-pointer group', isSelected && 'bg-muted/50' )} > <div className="mt-0.5"> {suggestion.type === 'website' && suggestion.website ? ( <Favicon website={suggestion.website} fallbackIcon={Icon} title={suggestion.title} className="h-4 w-4" /> ) : ( <Icon className="h-4 w-4 text-muted-foreground" /> )} </div> <div className="flex-1 min-w-0"> <div className="font-medium text-sm truncate">{suggestion.title}</div> {suggestion.description && ( <div className="text-xs text-muted-foreground truncate mt-1"> {suggestion.description} </div> )} {suggestion.category && ( <div className="text-xs text-muted-foreground mt-1"> Category: {suggestion.category} </div> )} </div> {suggestion.type === 'recent' && ( <div className="text-xs text-muted-foreground bg-muted/30 px-1.5 py-0.5 rounded"> Recent </div> )} {suggestion.type === 'trending' && ( <TrendingUp className="h-3 w-3 text-muted-foreground" /> )} {suggestion.type === 'category' && ( <div className="text-xs text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-950/50 px-1.5 py-0.5 rounded"> Category </div> )} <ArrowRight className="h-4 w-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" /> </button> ) })} </div> )} {searchQuery && ( <div className="border-t px-4 py-2"> <button type="button" onClick={() => { trackSearch(searchQuery, 0, 'autocomplete-search-button') saveRecentSearch(searchQuery) router.push(`${getRoute('search')}?q=${encodeURIComponent(searchQuery)}`) onClose() }} className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1 cursor-pointer" > <Search className="h-3 w-3" /> Search for "{searchQuery}" </button> </div> )} </div> ) }

Search equations, builds, manuscript text, and recovered sources.