1
1
import { WorkspaceStatuses } from "api/typesGenerated"
2
- import { FC , ReactNode , forwardRef , useRef , useState } from "react"
2
+ import { FC , ReactNode , forwardRef , useMemo , useRef , useState } from "react"
3
3
import Box from "@mui/material/Box"
4
4
import TextField from "@mui/material/TextField"
5
5
import { UserAvatar } from "components/UserAvatar/UserAvatar"
@@ -127,12 +127,48 @@ const useAutocomplete = <TOption extends BaseOption = BaseOption>({
127
127
const initialOptionQuery = useQuery ( {
128
128
queryKey : [ id , "autocomplete" , "initial" ] ,
129
129
queryFn : ( ) => getInitialOption ( ) ,
130
- onSuccess : setSelectedOption ,
130
+ onSuccess : ( option ) => setSelectedOption ( option ?? undefined ) ,
131
131
} )
132
132
const searchOptionsQuery = useQuery ( {
133
133
queryKey : [ id , "autoComplete" , "search" ] ,
134
134
queryFn : ( ) => getOptions ( query ) ,
135
135
} )
136
+ const searchOptions = useMemo ( ( ) => {
137
+ const isDataLoaded =
138
+ searchOptionsQuery . isFetched && initialOptionQuery . isFetched
139
+
140
+ if ( ! isDataLoaded ) {
141
+ return undefined
142
+ }
143
+
144
+ let options = searchOptionsQuery . data as TOption [ ]
145
+
146
+ if ( ! selectedOption ) {
147
+ return options
148
+ }
149
+
150
+ // We will add the initial option on the top of the options
151
+ // 1 - remove the initial option from the search options if it exists
152
+ // 2 - add the initial option on the top
153
+ options = options . filter ( ( option ) => option . value !== selectedOption . value )
154
+ options . unshift ( selectedOption )
155
+
156
+ // Filter data based o search query
157
+ options = options . filter (
158
+ ( option ) =>
159
+ option . label . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) ||
160
+ option . value . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) ,
161
+ )
162
+
163
+ return options
164
+ } , [
165
+ initialOptionQuery . isFetched ,
166
+ query ,
167
+ searchOptionsQuery . data ,
168
+ searchOptionsQuery . isFetched ,
169
+ selectedOption ,
170
+ ] )
171
+
136
172
const selectOption = ( option : TOption ) => {
137
173
let newSelectedOptionValue : TOption | undefined = option
138
174
@@ -145,16 +181,20 @@ const useAutocomplete = <TOption extends BaseOption = BaseOption>({
145
181
}
146
182
setSelectedOption ( newSelectedOptionValue )
147
183
}
184
+ const clearSelection = ( ) => {
185
+ setSelectedOption ( undefined )
186
+ }
148
187
149
188
return {
150
189
query,
151
190
setQuery,
152
191
selectedOption,
153
192
selectOption,
193
+ clearSelection,
154
194
isInitializing : initialOptionQuery . isInitialLoading ,
155
195
initialOption : initialOptionQuery . data ,
156
196
isSearching : searchOptionsQuery . isFetching ,
157
- searchOptions : searchOptionsQuery . data ,
197
+ searchOptions,
158
198
}
159
199
}
160
200
@@ -330,7 +370,15 @@ export const Filter = ({
330
370
endAdornment : hasFilterQuery && (
331
371
< InputAdornment position = "end" >
332
372
< Tooltip title = "Clear filter" >
333
- < IconButton size = "small" onClick = { ( ) => filter . update ( "" ) } >
373
+ < IconButton
374
+ size = "small"
375
+ onClick = { ( ) => {
376
+ filter . update ( "" )
377
+ autocomplete . users . clearSelection ( )
378
+ autocomplete . templates . clearSelection ( )
379
+ autocomplete . status . clearSelection ( )
380
+ } }
381
+ >
334
382
< CloseOutlined sx = { { fontSize : 14 } } />
335
383
</ IconButton >
336
384
</ Tooltip >
@@ -372,6 +420,8 @@ const OwnerFilter = ({ autocomplete }: { autocomplete: UsersAutocomplete }) => {
372
420
open = { isMenuOpen }
373
421
onClose = { handleClose }
374
422
options = { autocomplete . searchOptions }
423
+ query = { autocomplete . query }
424
+ onQueryChange = { autocomplete . setQuery }
375
425
renderOption = { ( option ) => (
376
426
< MenuItem
377
427
key = { option . label }
@@ -391,13 +441,21 @@ const OwnerFilter = ({ autocomplete }: { autocomplete: UsersAutocomplete }) => {
391
441
392
442
const UserOptionItem = ( { option } : { option : OwnerOption } ) => {
393
443
return (
394
- < Box display = "flex" alignItems = "center" gap = { 2 } fontSize = { 14 } >
444
+ < Box
445
+ display = "flex"
446
+ alignItems = "center"
447
+ gap = { 2 }
448
+ fontSize = { 14 }
449
+ overflow = "hidden"
450
+ >
395
451
< UserAvatar
396
452
username = { option . label }
397
453
avatarURL = { option . avatarUrl }
398
454
sx = { { width : 16 , height : 16 , fontSize : 8 } }
399
455
/>
400
- < span > { option . label } </ span >
456
+ < Box component = "span" overflow = "hidden" textOverflow = "ellipsis" >
457
+ { option . label }
458
+ </ Box >
401
459
</ Box >
402
460
)
403
461
}
@@ -433,6 +491,8 @@ const TemplatesFilter = ({
433
491
open = { isMenuOpen }
434
492
onClose = { handleClose }
435
493
options = { autocomplete . searchOptions }
494
+ query = { autocomplete . query }
495
+ onQueryChange = { autocomplete . setQuery }
436
496
renderOption = { ( option ) => (
437
497
< MenuItem
438
498
key = { option . label }
@@ -452,13 +512,21 @@ const TemplatesFilter = ({
452
512
453
513
const TemplateOptionItem = ( { option } : { option : TemplateOption } ) => {
454
514
return (
455
- < Box display = "flex" alignItems = "center" gap = { 2 } fontSize = { 14 } >
515
+ < Box
516
+ display = "flex"
517
+ alignItems = "center"
518
+ gap = { 2 }
519
+ fontSize = { 14 }
520
+ overflow = "hidden"
521
+ >
456
522
< TemplateAvatar
457
523
templateName = { option . label }
458
524
icon = { option . icon }
459
525
sx = { { width : 14 , height : 14 , fontSize : 8 } }
460
526
/>
461
- < span > { option . label } </ span >
527
+ < Box component = "span" overflow = "hidden" textOverflow = "ellipsis" >
528
+ { option . label }
529
+ </ Box >
462
530
</ Box >
463
531
)
464
532
}
@@ -504,6 +572,13 @@ const StatusFilter = ({
504
572
open = { isMenuOpen }
505
573
onClose = { handleClose }
506
574
sx = { { "& .MuiPaper-root" : { minWidth : 200 } } }
575
+ // Disabled this so when we clear the filter and do some sorting in the
576
+ // search items it does not look strange. Github removes exit transitions
577
+ // on their filters as well.
578
+ transitionDuration = { {
579
+ enter : 250 ,
580
+ exit : 0 ,
581
+ } }
507
582
>
508
583
{ autocomplete . searchOptions ?. map ( ( option ) => (
509
584
< MenuItem
@@ -553,8 +628,8 @@ const MenuButton = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
553
628
{ ...props }
554
629
sx = { {
555
630
borderRadius : "6px" ,
556
- lineHeight : 0 ,
557
631
justifyContent : "space-between" ,
632
+ lineHeight : 1 ,
558
633
...props . sx ,
559
634
} }
560
635
/>
@@ -564,36 +639,38 @@ const MenuButton = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
564
639
function SearchMenu < TOption extends { label : string ; value : string } > ( {
565
640
options,
566
641
renderOption,
642
+ query,
643
+ onQueryChange,
567
644
...menuProps
568
645
} : Pick < MenuProps , "anchorEl" | "open" | "onClose" | "id" > & {
569
646
options ?: TOption [ ]
570
647
renderOption : ( option : TOption ) => ReactNode
648
+ query : string
649
+ onQueryChange : ( query : string ) => void
571
650
} ) {
572
651
const menuListRef = useRef < HTMLUListElement > ( null )
573
652
const searchInputRef = useRef < HTMLInputElement > ( null )
574
- const [ searchInputValue , setSearchInputValue ] = useState ( "" )
575
- const visibleOptions = options
576
- ? options . filter (
577
- ( option ) =>
578
- option . label . toLowerCase ( ) . includes ( searchInputValue . toLowerCase ( ) ) ||
579
- option . value . toLowerCase ( ) . includes ( searchInputValue . toLowerCase ( ) ) ,
580
- )
581
- : undefined
582
653
583
654
return (
584
655
< Menu
585
656
{ ...menuProps }
586
657
onClose = { ( event , reason ) => {
587
658
menuProps . onClose && menuProps . onClose ( event , reason )
588
- // 250ms is the transition time to close menu
589
- setTimeout ( ( ) => setSearchInputValue ( "" ) , 250 )
659
+ onQueryChange ( "" )
590
660
} }
591
661
sx = { {
592
662
"& .MuiPaper-root" : {
593
663
width : 320 ,
594
664
paddingY : 0 ,
595
665
} ,
596
666
} }
667
+ // Disabled this so when we clear the filter and do some sorting in the
668
+ // search items it does not look strange. Github removes exit transitions
669
+ // on their filters as well.
670
+ transitionDuration = { {
671
+ enter : 250 ,
672
+ exit : 0 ,
673
+ } }
597
674
>
598
675
< Box
599
676
component = "li"
@@ -624,10 +701,10 @@ function SearchMenu<TOption extends { label: string; value: string }>({
624
701
type = "text"
625
702
placeholder = "Search..."
626
703
autoFocus
627
- value = { searchInputValue }
704
+ value = { query }
628
705
ref = { searchInputRef }
629
706
onChange = { ( e ) => {
630
- setSearchInputValue ( e . target . value )
707
+ onQueryChange ( e . target . value )
631
708
} }
632
709
sx = { {
633
710
height : "100%" ,
@@ -654,9 +731,9 @@ function SearchMenu<TOption extends { label: string; value: string }>({
654
731
}
655
732
} }
656
733
>
657
- { visibleOptions ? (
658
- visibleOptions . length > 0 ? (
659
- visibleOptions . map ( renderOption )
734
+ { options ? (
735
+ options . length > 0 ? (
736
+ options . map ( renderOption )
660
737
) : (
661
738
< Box
662
739
sx = { {
0 commit comments