11'use client' ;
22
3- import { useState , useEffect , useRef } from 'react' ;
4- import { Search as SearchIcon , X , ArrowLeft , ArrowRight } from 'lucide-react' ;
3+ import { useState , useEffect , useRef , useMemo } from 'react' ;
4+ import { Search as SearchIcon , X , ArrowLeft , Filter , FilterX } from 'lucide-react' ;
55import { SearchSuggestions } from './SearchSuggestions' ;
6+ import { SearchSuggestionFilters , AVAILABLE_INDICES } from './SearchSuggestionFilters' ;
67import { useSearchSuggestions } from '@/hooks/useSearchSuggestions' ;
78import { SearchSuggestion } from '@/types/search' ;
9+ import type { EntityType } from '@/types/search' ;
810import { useRouter , usePathname , useSearchParams } from 'next/navigation' ;
911import { navigateToAuthorProfile } from '@/utils/navigation' ;
1012import { BaseModal } from '@/components/ui/BaseModal' ;
@@ -19,12 +21,26 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
1921 const inputRef = useRef < HTMLInputElement > ( null ) ;
2022 const [ query , setQuery ] = useState ( '' ) ;
2123 const [ isFocused , setIsFocused ] = useState ( true ) ;
24+ const [ selectedIndices , setSelectedIndices ] = useState < EntityType [ ] > ( [ ] ) ;
25+ const [ isFiltersExpanded , setIsFiltersExpanded ] = useState ( false ) ;
2226 const router = useRouter ( ) ;
2327 const pathname = usePathname ( ) ;
2428 const searchParams = useSearchParams ( ) ;
2529 const hasPrefetchedRef = useRef ( false ) ;
2630 const navigatingToSearchRef = useRef ( false ) ;
2731
32+ const indicesToUse = useMemo ( ( ) => {
33+ if ( selectedIndices . length === 0 ) {
34+ return AVAILABLE_INDICES ;
35+ }
36+
37+ return selectedIndices ;
38+ } , [ selectedIndices ] ) ;
39+
40+ const hasActiveFilters = useMemo ( ( ) => {
41+ return selectedIndices . length > 0 ;
42+ } , [ selectedIndices ] ) ;
43+
2844 const prefetchSearchRoute = ( ) => {
2945 if ( ! hasPrefetchedRef . current ) {
3046 try {
@@ -35,8 +51,9 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
3551 } ;
3652
3753 // Get search suggestions
38- const { loading, suggestions } = useSearchSuggestions ( {
54+ const { loading, suggestions, hasLocalSuggestions , clearSearchHistory } = useSearchSuggestions ( {
3955 query,
56+ indices : indicesToUse ,
4057 includeLocalSuggestions : true ,
4158 } ) ;
4259
@@ -86,11 +103,13 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
86103 }
87104 } ;
88105
89- // Reset query when modal closes (but preserve if navigating to search page)
106+ // Reset query and filters when modal closes (but preserve if navigating to search page)
90107 useEffect ( ( ) => {
91108 if ( ! isOpen ) {
92109 if ( ! navigatingToSearchRef . current ) {
93110 setQuery ( '' ) ;
111+ setSelectedIndices ( [ ] ) ;
112+ setIsFiltersExpanded ( false ) ;
94113 }
95114 navigatingToSearchRef . current = false ;
96115 }
@@ -142,49 +161,68 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
142161 >
143162 { /* Search Input */ }
144163 < div className = "border-b border-gray-200 p-4" >
145- < div className = "relative" >
146- < SearchIcon className = "absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400" />
147- < input
148- ref = { inputRef }
149- type = "text"
150- placeholder = "Search papers, topics, authors..."
151- className = "h-12 w-full rounded-lg border border-gray-200 bg-white pl-10 pr-8 md:!pr-24 text-base focus:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400"
152- value = { query }
153- onChange = { ( e ) => setQuery ( e . target . value ) }
154- onClick = { ( e ) => {
155- ( e . target as HTMLInputElement ) . select ( ) ;
156- } }
157- onFocus = { ( ) => {
158- setIsFocused ( true ) ;
159- prefetchSearchRoute ( ) ;
160- inputRef . current ?. select ( ) ;
161- } }
162- onKeyDown = { ( e ) => {
163- if ( e . key === 'Enter' && e . shiftKey && query . trim ( ) ) {
164- e . preventDefault ( ) ;
165- navigatingToSearchRef . current = true ;
166- router . push ( `/search?debug&q=${ encodeURIComponent ( query . trim ( ) ) } ` ) ;
167- onClose ( ) ;
168- }
169- } }
170- />
171- { /* Keyboard shortcut hint - desktop only */ }
172- < div className = "absolute right-3 top-1/2 -translate-y-1/2 hidden md:!flex items-center space-x-1 text-xs text-gray-400" >
173- < kbd className = "px-1.5 py-0.5 text-xs font-semibold text-gray-500 bg-gray-100 border border-gray-200 rounded" >
174- { shortcutKey }
175- </ kbd >
176- < kbd className = "px-1.5 py-0.5 text-xs font-semibold text-gray-500 bg-gray-100 border border-gray-200 rounded" >
177- K
178- </ kbd >
164+ < div className = "flex items-center gap-2" >
165+ < div className = "relative flex-1" >
166+ < SearchIcon className = "absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400" />
167+ < input
168+ ref = { inputRef }
169+ type = "text"
170+ placeholder = "Search papers, topics, authors..."
171+ className = "h-12 w-full rounded-lg border border-gray-200 bg-white pl-10 pr-10 text-base focus:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400"
172+ value = { query }
173+ onChange = { ( e ) => setQuery ( e . target . value ) }
174+ onClick = { ( e ) => {
175+ ( e . target as HTMLInputElement ) . select ( ) ;
176+ } }
177+ onFocus = { ( ) => {
178+ setIsFocused ( true ) ;
179+ prefetchSearchRoute ( ) ;
180+ inputRef . current ?. select ( ) ;
181+ } }
182+ onKeyDown = { ( e ) => {
183+ if ( e . key === 'Enter' && e . shiftKey && query . trim ( ) ) {
184+ e . preventDefault ( ) ;
185+ navigatingToSearchRef . current = true ;
186+ router . push ( `/search?debug&q=${ encodeURIComponent ( query . trim ( ) ) } ` ) ;
187+ onClose ( ) ;
188+ }
189+ } }
190+ />
191+ { query && (
192+ < button
193+ onClick = { ( ) => setQuery ( '' ) }
194+ className = "absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-full hover:bg-gray-100"
195+ >
196+ < X className = "h-4 w-4 text-gray-400" />
197+ </ button >
198+ ) }
199+ </ div >
200+ { /* Settings/Filter Button */ }
201+ < Button
202+ variant = "outlined"
203+ size = "icon"
204+ onClick = { ( ) => setIsFiltersExpanded ( ! isFiltersExpanded ) }
205+ className = "flex-shrink-0 h-12 w-12"
206+ aria-label = "Toggle filters"
207+ >
208+ { hasActiveFilters ? (
209+ < FilterX className = "h-4 w-4 text-gray-500" />
210+ ) : (
211+ < Filter className = "h-4 w-4 text-gray-500" />
212+ ) }
213+ </ Button >
214+ </ div >
215+ { /* Filter Section */ }
216+ < div
217+ className = "grid transition-[grid-template-rows] duration-[500ms] ease-in-out"
218+ style = { { gridTemplateRows : isFiltersExpanded ? '1fr' : '0fr' } }
219+ >
220+ < div className = "overflow-hidden" >
221+ < SearchSuggestionFilters
222+ selectedIndices = { selectedIndices }
223+ onIndicesChange = { setSelectedIndices }
224+ />
179225 </ div >
180- { query && (
181- < button
182- onClick = { ( ) => setQuery ( '' ) }
183- className = "absolute right-2 md:!right-20 top-1/2 -translate-y-1/2 p-1 rounded-full hover:bg-gray-100"
184- >
185- < X className = "h-4 w-4 text-gray-400" />
186- </ button >
187- ) }
188226 </ div >
189227 </ div >
190228
@@ -197,6 +235,10 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
197235 onSelect = { handleSelect }
198236 displayMode = "inline"
199237 showSuggestionsOnFocus = { true }
238+ loading = { loading }
239+ suggestions = { suggestions }
240+ hasLocalSuggestions = { hasLocalSuggestions }
241+ clearSearchHistory = { clearSearchHistory }
200242 />
201243 ) : (
202244 < div className = "p-8 text-center text-gray-500" >
0 commit comments