11'use client' ;
22
33import { isDefined } from "@/lib/utils" ;
4- import { CommitIcon , MixerVerticalIcon } from "@radix-ui/react-icons" ;
5- import { IconProps } from "@radix-ui/react-icons/dist/types" ;
64import assert from "assert" ;
75import clsx from "clsx" ;
86import escapeStringRegexp from "escape-string-regexp" ;
@@ -16,13 +14,14 @@ import {
1614 refineModeSuggestions ,
1715 suggestionModeMappings
1816} from "./constants" ;
19-
20- type Icon = React . ForwardRefExoticComponent < IconProps & React . RefAttributes < SVGSVGElement > > ;
17+ import { IconType } from "react-icons/lib" ;
18+ import { VscFile , VscFilter , VscRepo , VscSymbolMisc } from "react-icons/vsc" ;
2119
2220export type Suggestion = {
2321 value : string ;
2422 description ?: string ;
2523 spotlight ?: boolean ;
24+ Icon ?: IconType ;
2625}
2726
2827export type SuggestionMode =
@@ -50,30 +49,42 @@ interface SearchSuggestionsBoxProps {
5049 onSuggestionModeChanged : ( suggestionMode : SuggestionMode ) => void ;
5150 onSuggestionQueryChanged : ( suggestionQuery : string ) => void ;
5251
53- data : {
54- repos : Suggestion [ ] ;
55- languages : Suggestion [ ] ;
56- files : Suggestion [ ] ;
57- }
52+ isLoadingSuggestions : boolean ;
53+ repoSuggestions : Suggestion [ ] ;
54+ fileSuggestions : Suggestion [ ] ;
55+ symbolSuggestions : Suggestion [ ] ;
56+ languageSuggestions : Suggestion [ ] ;
5857}
5958
6059const SearchSuggestionsBox = forwardRef ( ( {
6160 query,
6261 onCompletion,
6362 isEnabled,
64- data,
6563 cursorPosition,
6664 isFocused,
6765 onFocus,
6866 onBlur,
6967 onReturnFocus,
7068 onSuggestionModeChanged,
7169 onSuggestionQueryChanged,
70+ isLoadingSuggestions,
71+ repoSuggestions,
72+ fileSuggestions,
73+ symbolSuggestions,
74+ languageSuggestions,
7275} : SearchSuggestionsBoxProps , ref : Ref < HTMLDivElement > ) => {
7376
7477 const [ highlightedSuggestionIndex , setHighlightedSuggestionIndex ] = useState ( 0 ) ;
7578
7679 const { suggestionQuery, suggestionMode } = useMemo < { suggestionQuery ?: string , suggestionMode ?: SuggestionMode } > ( ( ) => {
80+ // Only re-calculate the suggestion mode and query if the box is enabled.
81+ // This is to avoid transitioning the suggestion mode and causing a fetch
82+ // when it is not needed.
83+ // @see : useSuggestionsData.ts
84+ if ( ! isEnabled ) {
85+ return { } ;
86+ }
87+
7788 const { queryParts, cursorIndex } = splitQuery ( query , cursorPosition ) ;
7889 if ( queryParts . length === 0 ) {
7990 return { } ;
@@ -107,10 +118,10 @@ const SearchSuggestionsBox = forwardRef(({
107118 suggestionQuery : part ,
108119 suggestionMode : "refine" ,
109120 }
110- } , [ cursorPosition , query ] ) ;
121+ } , [ cursorPosition , isEnabled , query ] ) ;
111122
112- const { suggestions, isHighlightEnabled, Icon , onSuggestionClicked } = useMemo ( ( ) => {
113- if ( ! isDefined ( suggestionQuery ) || ! isDefined ( suggestionMode ) ) {
123+ const { suggestions, isHighlightEnabled, DefaultIcon , onSuggestionClicked } = useMemo ( ( ) => {
124+ if ( ! isEnabled || ! isDefined ( suggestionQuery ) || ! isDefined ( suggestionMode ) ) {
114125 return { } ;
115126 }
116127
@@ -144,7 +155,7 @@ const SearchSuggestionsBox = forwardRef(({
144155 isSpotlightEnabled = false ,
145156 isClientSideSearchEnabled = true ,
146157 onSuggestionClicked,
147- Icon ,
158+ DefaultIcon ,
148159 } = ( ( ) : {
149160 threshold ?: number ,
150161 limit ?: number ,
@@ -153,7 +164,7 @@ const SearchSuggestionsBox = forwardRef(({
153164 isSpotlightEnabled ?: boolean ,
154165 isClientSideSearchEnabled ?: boolean ,
155166 onSuggestionClicked : ( value : string ) => void ,
156- Icon ?: Icon
167+ DefaultIcon ?: IconType
157168 } => {
158169 switch ( suggestionMode ) {
159170 case "public" :
@@ -178,13 +189,13 @@ const SearchSuggestionsBox = forwardRef(({
178189 }
179190 case "repo" :
180191 return {
181- list : data . repos ,
182- Icon : CommitIcon ,
192+ list : repoSuggestions ,
193+ DefaultIcon : VscRepo ,
183194 onSuggestionClicked : createOnSuggestionClickedHandler ( { regexEscaped : true } ) ,
184195 }
185196 case "language" : {
186197 return {
187- list : data . languages ,
198+ list : languageSuggestions ,
188199 onSuggestionClicked : createOnSuggestionClickedHandler ( ) ,
189200 isSpotlightEnabled : true ,
190201 }
@@ -195,18 +206,25 @@ const SearchSuggestionsBox = forwardRef(({
195206 list : refineModeSuggestions ,
196207 isHighlightEnabled : true ,
197208 isSpotlightEnabled : true ,
198- Icon : MixerVerticalIcon ,
209+ DefaultIcon : VscFilter ,
199210 onSuggestionClicked : createOnSuggestionClickedHandler ( { trailingSpace : false } ) ,
200211 }
201212 case "file" :
202213 return {
203- list : data . files ,
214+ list : fileSuggestions ,
215+ onSuggestionClicked : createOnSuggestionClickedHandler ( ) ,
216+ isClientSideSearchEnabled : false ,
217+ DefaultIcon : VscFile ,
218+ }
219+ case "symbol" :
220+ return {
221+ list : symbolSuggestions ,
204222 onSuggestionClicked : createOnSuggestionClickedHandler ( ) ,
205223 isClientSideSearchEnabled : false ,
224+ DefaultIcon : VscSymbolMisc ,
206225 }
207226 case "revision" :
208227 case "content" :
209- case "symbol" :
210228 return {
211229 list : [ ] ,
212230 onSuggestionClicked : createOnSuggestionClickedHandler ( ) ,
@@ -252,11 +270,11 @@ const SearchSuggestionsBox = forwardRef(({
252270 return {
253271 suggestions,
254272 isHighlightEnabled,
255- Icon ,
273+ DefaultIcon ,
256274 onSuggestionClicked,
257275 }
258276
259- } , [ suggestionQuery , suggestionMode , query , cursorPosition , onCompletion , data . repos , data . files , data . languages ] ) ;
277+ } , [ isEnabled , suggestionQuery , suggestionMode , query , cursorPosition , onCompletion , repoSuggestions , fileSuggestions , symbolSuggestions , languageSuggestions ] ) ;
260278
261279 // When the list of suggestions change, reset the highlight index
262280 useEffect ( ( ) => {
@@ -283,20 +301,29 @@ const SearchSuggestionsBox = forwardRef(({
283301 case "repo" :
284302 return "Repositories" ;
285303 case "refine" :
286- return "Refine search"
304+ return "Refine search" ;
305+ case "file" :
306+ return "Files" ;
307+ case "symbol" :
308+ return "Symbols" ;
309+ case "language" :
310+ return "Languages" ;
287311 default :
288312 return "" ;
289313 }
290314 } , [ suggestionMode ] ) ;
291315
292316 if (
293317 ! isEnabled ||
294- ! suggestions ||
295- suggestions . length === 0
318+ ! suggestions
296319 ) {
297320 return null ;
298321 }
299322
323+ if ( suggestions . length === 0 && ! isLoadingSuggestions ) {
324+ return null ;
325+ }
326+
300327 return (
301328 < div
302329 ref = { ref }
@@ -305,6 +332,9 @@ const SearchSuggestionsBox = forwardRef(({
305332 onKeyDown = { ( e ) => {
306333 if ( e . key === 'Enter' ) {
307334 e . stopPropagation ( ) ;
335+ if ( highlightedSuggestionIndex < 0 || highlightedSuggestionIndex >= suggestions . length ) {
336+ return ;
337+ }
308338 const value = suggestions [ highlightedSuggestionIndex ] . value ;
309339 onSuggestionClicked ( value ) ;
310340 }
@@ -334,7 +364,17 @@ const SearchSuggestionsBox = forwardRef(({
334364 < p className = "text-muted-foreground text-sm mb-1" >
335365 { suggestionModeText }
336366 </ p >
337- { suggestions . map ( ( result , index ) => (
367+ { isLoadingSuggestions ? (
368+ // Skeleton placeholder
369+ < div className = "animate-pulse flex flex-col gap-2 px-1 py-0.5" >
370+ {
371+ Array . from ( { length : 10 } ) . map ( ( _ , index ) => (
372+ < div key = { index } className = "h-4 bg-muted rounded-md w-full" > </ div >
373+ ) )
374+ }
375+ </ div >
376+ ) : suggestions . map ( ( result , index ) => (
377+ // Suggestion list
338378 < div
339379 key = { index }
340380 className = { clsx ( "flex flex-row items-center font-mono text-sm hover:bg-muted rounded-md px-1 py-0.5 cursor-pointer" , {
@@ -345,23 +385,24 @@ const SearchSuggestionsBox = forwardRef(({
345385 onSuggestionClicked ( result . value )
346386 } }
347387 >
348- { Icon && (
349- < Icon className = "w-3 h-3 mr-2" />
350- ) }
351- < div className = "flex flex-row items-center" >
352- < span
353- className = { clsx ( 'mr-2 flex-none' , {
354- "text-highlight" : isHighlightEnabled
355- } ) }
356- >
357- { result . value }
388+ { result . Icon ? (
389+ < result . Icon className = "w-3 h-3 mr-2 flex-none" />
390+ ) : DefaultIcon ? (
391+ < DefaultIcon className = "w-3 h-3 mr-2 flex-none" />
392+ ) : null }
393+ < span
394+ className = { clsx ( 'mr-2' , {
395+ "text-highlight" : isHighlightEnabled ,
396+ "truncate" : ! result . description ,
397+ } ) }
398+ >
399+ { result . value }
400+ </ span >
401+ { result . description && (
402+ < span className = "text-muted-foreground font-light" >
403+ { result . description }
358404 </ span >
359- { result . description && (
360- < span className = "text-muted-foreground font-light" >
361- { result . description }
362- </ span >
363- ) }
364- </ div >
405+ ) }
365406 </ div >
366407 ) ) }
367408 { isFocused && (
0 commit comments