@@ -4,10 +4,12 @@ import {
44 dropDownSpacingTheme ,
55 dropDownStyle ,
66} from "../../utils/componentStyles" ;
7- import Select , { GroupBase , Props , SelectInstance } from "react-select" ;
8- import CreatableSelect from "react-select/creatable" ;
7+ import { GroupBase , MenuListProps , Props , SelectInstance , createFilter } from "react-select" ;
98import { isJson } from "../../utils/utils" ;
109import { ParseKeys } from "i18next" ;
10+ import { FixedSizeList , ListChildComponentProps } from "react-window" ;
11+ import AsyncSelect from 'react-select/async' ;
12+ import AsyncCreatableSelect from 'react-select/async-creatable' ;
1113
1214export type DropDownOption = {
1315 label : string ,
@@ -34,7 +36,10 @@ const DropDown = <T, >({
3436 disabled = false ,
3537 menuIsOpen = undefined ,
3638 handleMenuIsOpen = undefined ,
39+ skipTranslate = false ,
40+ optionHeight = 25 ,
3741 customCSS,
42+ fetchOptions,
3843} : {
3944 ref ?: React . RefObject < SelectInstance < any , boolean , GroupBase < any > > | null >
4045 value : T
@@ -51,12 +56,15 @@ const DropDown = <T, >({
5156 disabled ?: boolean ,
5257 menuIsOpen ?: boolean ,
5358 handleMenuIsOpen ?: ( open : boolean ) => void ,
59+ skipTranslate ?: boolean ,
60+ optionHeight ?: number ,
5461 customCSS ?: {
5562 isMetadataStyle ?: boolean ,
5663 width ?: number | string ,
5764 optionPaddingTop ?: number ,
5865 optionLineHeight ?: string
59- }
66+ } ,
67+ fetchOptions ?: ( ) => { label : string , value : string } [ ]
6068} ) => {
6169 const { t } = useTranslation ( ) ;
6270
@@ -84,8 +92,11 @@ const DropDown = <T, >({
8492 filterText : string ,
8593 required : boolean ,
8694 ) => {
87- // Translate?
88- unformattedOptions = unformattedOptions . map ( option => ( { ...option , label : t ( option . label as ParseKeys ) } ) )
95+ // Translate
96+ // Translating is expensive, skip it if it is not required
97+ if ( ! skipTranslate ) {
98+ unformattedOptions = unformattedOptions . map ( option => ( { ...option , label : t ( option . label as ParseKeys ) } ) )
99+ }
89100
90101 // Filter
91102 filterText = filterText . toLowerCase ( ) ;
@@ -119,6 +130,41 @@ const DropDown = <T, >({
119130 return unformattedOptions ;
120131 } ;
121132
133+ const itemHeight = optionHeight ;
134+ /**
135+ * Custom component for list virtualization
136+ */
137+ const MenuList = ( props : MenuListProps < DropDownOption , false > ) => {
138+ const { options, children, maxHeight, getValue } = props
139+
140+ console . log ( "Menu List render" )
141+
142+ return Array . isArray ( children ) ? (
143+ < div style = { { paddingTop : 4 } } >
144+ < FixedSizeList
145+ height = { maxHeight < ( children . length * itemHeight ) ? maxHeight : children . length * itemHeight }
146+ itemCount = { children . length }
147+ itemSize = { itemHeight }
148+ overscanCount = { 4 }
149+ width = "100%"
150+ >
151+ { ( { index, style } : ListChildComponentProps ) => < div style = { { ...style } } > { children [ index ] } </ div > }
152+ </ FixedSizeList >
153+ </ div >
154+ ) : null
155+ }
156+
157+ const loadOptions = (
158+ inputValue : string ,
159+ callback : ( options : DropDownOption [ ] ) => void
160+ ) => {
161+ callback ( formatOptions (
162+ fetchOptions ? fetchOptions ( ) : options ,
163+ searchText ,
164+ required ,
165+ ) ) ;
166+ } ;
167+
122168
123169 let commonProps : Props = {
124170 tabIndex : tabIndex ,
@@ -129,11 +175,6 @@ const DropDown = <T, >({
129175 isSearchable : true ,
130176 value : { value : value , label : text === "" ? placeholder : text } ,
131177 inputValue : searchText ,
132- options : formatOptions (
133- options ,
134- searchText ,
135- required ,
136- ) ,
137178 placeholder : placeholder ,
138179 onInputChange : ( value : string ) => setSearch ( value ) ,
139180 onChange : ( element ) => handleChange ( element as { value : T , label : string } ) ,
@@ -142,18 +183,36 @@ const DropDown = <T, >({
142183 onMenuClose : ( ) => openMenu ( false ) ,
143184 isDisabled : disabled ,
144185 openMenuOnFocus : openMenuOnFocus ,
186+
187+ //@ts -expect-error: React-Select typing does not account for the typing of option it itself requires
188+ components : { MenuList } ,
189+ filterOption : createFilter ( { ignoreAccents : false } ) , // To improve performance on filtering
145190 } ;
146191
147192 return creatable ? (
148- < CreatableSelect
193+ < AsyncCreatableSelect
149194 ref = { selectRef }
150195 { ...commonProps }
196+ cacheOptions
197+ defaultOptions = { formatOptions (
198+ options ,
199+ searchText ,
200+ required ,
201+ ) }
202+ loadOptions = { loadOptions }
151203 />
152204 ) : (
153- < Select
205+ < AsyncSelect
154206 ref = { selectRef }
155207 { ...commonProps }
156208 noOptionsMessage = { ( ) => t ( "SELECT_NO_MATCHING_RESULTS" ) }
209+ cacheOptions
210+ defaultOptions = { formatOptions (
211+ options ,
212+ searchText ,
213+ required ,
214+ ) }
215+ loadOptions = { loadOptions }
157216 />
158217 ) ;
159218} ;
0 commit comments