14
14
* limitations under the License.
15
15
*/
16
16
17
- import { Fragment , useEffect , useMemo , useRef , useState } from 'react'
17
+ import { useEffect , useMemo , useRef , useState } from 'react'
18
18
import { useHistory , useLocation , useParams } from 'react-router-dom'
19
19
import { MultiValue , SelectInstance } from 'react-select'
20
20
import { parse as parseQueryString , ParsedQuery , stringify as stringifyQueryString } from 'query-string'
@@ -24,6 +24,7 @@ import {
24
24
Icon ,
25
25
OptionType ,
26
26
SelectPicker ,
27
+ SelectPickerOptionType ,
27
28
SelectPickerProps ,
28
29
useAsync ,
29
30
useRegisterShortcut ,
@@ -51,6 +52,7 @@ const NodeListSearchFilter = ({
51
52
} : NodeListSearchFilterType ) => {
52
53
// STATES
53
54
const [ nodeSearchKey , setNodeSearchKey ] = useState < NODE_SEARCH_KEYS | null > ( null )
55
+ const [ isNodeListSearchOpen , setIsNodeListSearchOpen ] = useState ( false )
54
56
55
57
// HOOKS
56
58
const { clusterId } = useParams < ClusterDetailBaseParams > ( )
@@ -78,11 +80,17 @@ const NodeListSearchFilter = ({
78
80
} , [ ] )
79
81
80
82
useEffect ( ( ) => {
81
- // focusing select picker after custom option click
82
- if ( nodeSearchKey ) {
83
+ // focusing select picker whenever secondary menu is opened or closed (handled via nodeSearchKey)
84
+ if ( nodeSearchKey || ( ! nodeSearchKey && isNodeListSearchOpen ) ) {
83
85
searchFilterRef . current ?. focus ( )
84
86
}
85
- } , [ nodeSearchKey ] )
87
+ } , [ nodeSearchKey , isNodeListSearchOpen ] )
88
+
89
+ // CONSTANTS
90
+ const isNodeSearchFilterApplied =
91
+ searchParams [ NODE_SEARCH_KEYS . NAME ] ||
92
+ searchParams [ NODE_SEARCH_KEYS . LABEL ] ||
93
+ searchParams [ NODE_SEARCH_KEYS . NODE_GROUP ]
86
94
87
95
// ASYNC CALLS
88
96
const [ nodeK8sVersionsLoading , nodeK8sVersionOptions , nodeK8sVersionsError , refetchNodeK8sVersions ] =
@@ -113,23 +121,24 @@ const NodeListSearchFilter = ({
113
121
const { nodeGroups, labels, nodeNames } = useMemo ( ( ) => getNodeSearchKeysOptionsList ( rows ) , [ JSON . stringify ( rows ) ] )
114
122
115
123
const searchOptions = useMemo (
116
- ( ) => getNodeListSearchOptions ( { labels, nodeGroups, nodeNames, nodeSearchKey } ) ,
124
+ ( ) =>
125
+ nodeSearchKey
126
+ ? getNodeListSearchOptions ( { labels, nodeGroups, nodeNames, nodeSearchKey } )
127
+ : [ { label : 'Filter by' , options : NODE_LIST_SEARCH_FILTER_OPTIONS } ] ,
117
128
[ nodeGroups , labels , nodeNames , nodeSearchKey ] ,
118
129
)
119
130
120
131
const searchValue = useMemo ( ( ) => {
121
- const queryObject = parseQueryString ( search )
122
-
123
- const nameMap = new Set ( ( ( queryObject [ NODE_SEARCH_KEYS . NAME ] as string ) || '' ) . split ( ',' ) )
124
- const nodeGroupMap = new Set ( ( ( queryObject [ NODE_SEARCH_KEYS . NODE_GROUP ] as string ) || '' ) . split ( ',' ) )
125
- const labelMap = new Set ( ( ( queryObject [ NODE_SEARCH_KEYS . LABEL ] as string ) || '' ) . split ( ',' ) )
132
+ const nameMap = new Set ( ( ( searchParams [ NODE_SEARCH_KEYS . NAME ] as string ) || '' ) . split ( ',' ) )
133
+ const nodeGroupMap = new Set ( ( ( searchParams [ NODE_SEARCH_KEYS . NODE_GROUP ] as string ) || '' ) . split ( ',' ) )
134
+ const labelMap = new Set ( ( ( searchParams [ NODE_SEARCH_KEYS . LABEL ] as string ) || '' ) . split ( ',' ) )
126
135
127
136
return [
128
137
...nodeNames . filter ( ( { value } ) => nameMap . has ( value ) ) ,
129
138
...nodeGroups . filter ( ( { value } ) => nodeGroupMap . has ( value ) ) ,
130
139
...labels . filter ( ( { value } ) => labelMap . has ( value ) ) ,
131
140
]
132
- } , [ search ] )
141
+ } , [ searchParams ] )
133
142
134
143
// HANDLERS
135
144
const handleQueryParamsUpdate = ( callback : ( queryObject : ParsedQuery ) => ParsedQuery ) => {
@@ -157,61 +166,67 @@ const NodeListSearchFilter = ({
157
166
} )
158
167
}
159
168
160
- const handleFilterGroupSelection = ( value : typeof nodeSearchKey ) => ( ) => {
161
- setNodeSearchKey ( value )
162
- }
169
+ const handleSearchFilterChange = (
170
+ newValue : SelectPickerOptionType < NODE_SEARCH_KEYS > | MultiValue < NodeSearchListOptionType > ,
171
+ ) => {
172
+ if ( newValue && ! Array . isArray ( newValue ) && ! isNodeSearchFilterApplied ) {
173
+ setNodeSearchKey ( ( newValue as SelectPickerOptionType < NODE_SEARCH_KEYS > ) . value )
174
+ return
175
+ }
163
176
164
- const renderCustomOptions = ( ) => (
165
- < >
166
- < div className = "py-4 px-8 bg__menu--secondary fs-12 fw-6 lh-20 cn-9" >
167
- { nodeSearchKey ? 'Match' : 'Filter by' }
168
- </ div >
169
- < div >
170
- { NODE_LIST_SEARCH_FILTER_OPTIONS . map ( ( { label, value } ) => (
171
- < Fragment key = { value } >
172
- < button
173
- className = "dc__transparent dc__truncate dc__hover-n50 fs-13 fw-4 lh-20 cn-9 px-8 py-6 w-100 dc__align-left"
174
- type = "button"
175
- onClick = { handleFilterGroupSelection ( value ) }
176
- >
177
- { label }
178
- </ button >
179
- </ Fragment >
180
- ) ) }
181
- </ div >
182
- </ >
183
- )
177
+ if (
178
+ Array . isArray ( newValue ) &&
179
+ newValue . length &&
180
+ isNodeSearchFilterApplied &&
181
+ ! ( 'identifier' in newValue [ newValue . length - 1 ] )
182
+ ) {
183
+ setNodeSearchKey ( newValue [ newValue . length - 1 ] . value as NODE_SEARCH_KEYS )
184
+ return
185
+ }
184
186
185
- const handleSearchFilterChange = ( newValue : MultiValue < NodeSearchListOptionType > ) => {
186
- handleQueryParamsUpdate ( ( queryObject ) => {
187
- const updatedQueryObject = structuredClone ( queryObject )
188
-
189
- const queries = newValue . reduce < Record < string , string [ ] > > (
190
- ( acc , curr ) => {
191
- acc [ curr . identifier ] . push ( curr . value )
192
- return acc
193
- } ,
194
- {
195
- [ NODE_SEARCH_KEYS . NAME ] : [ ] ,
196
- [ NODE_SEARCH_KEYS . LABEL ] : [ ] ,
197
- [ NODE_SEARCH_KEYS . NODE_GROUP ] : [ ] ,
198
- } ,
199
- )
200
-
201
- Object . values ( NODE_SEARCH_KEYS ) . forEach ( ( key ) => {
202
- if ( queries [ key ] ?. length ) {
203
- updatedQueryObject [ key ] = queries [ key ] . join ( ',' )
204
- } else {
205
- delete updatedQueryObject [ key ]
206
- }
187
+ if ( Array . isArray ( newValue ) ) {
188
+ handleQueryParamsUpdate ( ( queryObject ) => {
189
+ const updatedQueryObject = structuredClone ( queryObject )
190
+
191
+ const queries = newValue . reduce < Record < string , string [ ] > > (
192
+ ( acc , curr ) => {
193
+ acc [ curr . identifier ] . push ( curr . value )
194
+ return acc
195
+ } ,
196
+ {
197
+ [ NODE_SEARCH_KEYS . NAME ] : [ ] ,
198
+ [ NODE_SEARCH_KEYS . LABEL ] : [ ] ,
199
+ [ NODE_SEARCH_KEYS . NODE_GROUP ] : [ ] ,
200
+ } ,
201
+ )
202
+
203
+ Object . values ( NODE_SEARCH_KEYS ) . forEach ( ( key ) => {
204
+ if ( queries [ key ] ?. length ) {
205
+ updatedQueryObject [ key ] = queries [ key ] . join ( ',' )
206
+ } else {
207
+ delete updatedQueryObject [ key ]
208
+ }
209
+ } )
210
+
211
+ return updatedQueryObject
207
212
} )
213
+ }
214
+ }
208
215
209
- return updatedQueryObject
210
- } )
216
+ const handleSearchFilterKeyDown : SelectPickerProps [ 'onKeyDown' ] = ( e ) => {
217
+ if ( e . key === 'Backspace' && ! isNodeSearchFilterApplied ) {
218
+ e . preventDefault ( )
219
+ setNodeSearchKey ( null )
220
+ }
221
+ }
222
+
223
+ const handleMenuOpen = ( ) => {
224
+ setIsNodeListSearchOpen ( true )
211
225
}
212
226
213
227
const handleMenuClose = ( ) => {
214
228
searchFilterRef . current ?. blur ( )
229
+ setIsNodeListSearchOpen ( false )
215
230
setNodeSearchKey ( null )
216
231
}
217
232
@@ -227,21 +242,24 @@ const NodeListSearchFilter = ({
227
242
228
243
return (
229
244
< div className = "node-listing-search-container pt-16 px-20 pb-12 dc__zi-5" >
230
- < SelectPicker < string , true >
245
+ < SelectPicker
231
246
selectRef = { searchFilterRef }
247
+ menuIsOpen = { isNodeListSearchOpen }
248
+ onMenuOpen = { handleMenuOpen }
232
249
onMenuClose = { handleMenuClose }
233
250
options = { searchOptions }
234
- isMulti
251
+ isMulti = { ! ! nodeSearchKey || isNodeSearchFilterApplied }
252
+ showCheckboxForMultiSelect = { ! ! nodeSearchKey }
235
253
placeholder = { NODE_SEARCH_KEY_PLACEHOLDER [ nodeSearchKey ] || 'Filter by Node, Labels or Node groups' }
236
254
required
237
255
inputId = "node-list-search"
238
256
isSearchable = { ! ! nodeSearchKey }
239
257
isClearable
240
258
value = { searchValue }
241
259
onChange = { handleSearchFilterChange }
260
+ onKeyDown = { handleSearchFilterKeyDown }
242
261
getOptionValue = { getOptionValue }
243
- renderCustomOptions = { renderCustomOptions }
244
- shouldRenderCustomOptions = { ! nodeSearchKey }
262
+ closeMenuOnSelect = { false }
245
263
icon = { < Icon name = "ic-filter" color = "N600" /> }
246
264
keyboardShortcut = "/"
247
265
formatOptionLabel = { formatOptionLabel }
0 commit comments