1
1
/* eslint-disable jsx-a11y/no-static-element-interactions */
2
2
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
3
- import React , { useState , useRef } from 'react'
3
+ import React , { useState , useRef , useEffect } from 'react'
4
4
import PropTypes from 'prop-types'
5
5
import { Field } from 'redux-form'
6
6
import {
@@ -22,6 +22,67 @@ import InputErrorText from './parts/InputErrorText'
22
22
import InputLabel from './parts/InputLabel'
23
23
import styles from './Select.module.scss'
24
24
25
+ const isEmpty = ( val ) => isNil ( val ) || val === '' || val . length === 0
26
+
27
+ const Content = React . memo (
28
+ ( {
29
+ options,
30
+ handleOnChange,
31
+ searchInputRef,
32
+ searchValue,
33
+ setSearchValue,
34
+ searchable,
35
+ placeholder,
36
+ loading,
37
+ multiple,
38
+ value,
39
+ } ) => {
40
+ const Title = ( ) => {
41
+ const title =
42
+ get (
43
+ find ( options , ( o ) => o . value === value ) ,
44
+ 'label'
45
+ ) || placeholder
46
+ if ( ! multiple ) return < span > { title } </ span >
47
+ if ( ! isEmpty ( value ) ) {
48
+ return map ( options , ( { label : l , value : key } ) => {
49
+ if ( includes ( value , key ) ) {
50
+ return (
51
+ < BaseButton
52
+ key = { key }
53
+ className = { styles . chip }
54
+ onClick = { ( ) => handleOnChange ( filter ( value , ( v ) => v !== key ) ) }
55
+ >
56
+ < span className = { styles . title } > { l } </ span >
57
+ < CloseIcon />
58
+ </ BaseButton >
59
+ )
60
+ }
61
+ } )
62
+ }
63
+ return < span > { title } </ span >
64
+ }
65
+
66
+ if ( loading ) return < Loader loading className = { styles . loading } />
67
+ if ( searchable )
68
+ return (
69
+ < input
70
+ ref = { searchInputRef }
71
+ value = { searchValue }
72
+ placeholder = { placeholder }
73
+ className = { styles . searchInput }
74
+ onChange = { ( e ) => setSearchValue ( e . target . value ) }
75
+ onBlur = { ( ) => {
76
+ if ( value === '' ) {
77
+ setSearchValue ( '' )
78
+ }
79
+ } }
80
+ />
81
+ )
82
+ return < Title />
83
+ }
84
+ )
85
+
25
86
const SelectComponent = ( {
26
87
disabled,
27
88
loading,
@@ -38,45 +99,32 @@ const SelectComponent = ({
38
99
multiple,
39
100
isCustom,
40
101
onReset,
102
+ searchable,
41
103
} ) => {
42
104
const { value } = input
43
105
const { error, active } = meta
44
106
const [ open , setOpen ] = useState ( false )
107
+ const [ searchValue , setSearchValue ] = useState ( '' )
45
108
const ref = useRef ( null )
46
- const isEmpty = ( val ) => isNil ( val ) || val === '' || val . length === 0
109
+ const searchInputRef = useRef ( null )
110
+
47
111
const isActive = ( key ) => ( multiple ? includes ( value , key ) : value === key )
48
112
const handleOnChange = ( val ) => {
49
113
if ( input . onChange ) input . onChange ( val )
50
114
if ( onChange ) onChange ( val )
51
115
}
52
- const Title = ( ) => {
53
- const title =
54
- get (
55
- find ( options , ( o ) => o . value === value ) ,
56
- 'label'
57
- ) || placeholder
58
- if ( ! multiple ) return < span > { title } </ span >
59
- if ( ! isEmpty ( value ) ) {
60
- return map ( options , ( { label : l , value : key } ) => {
61
- if ( includes ( value , key ) ) {
62
- return (
63
- < BaseButton
64
- key = { key }
65
- className = { styles . chip }
66
- onClick = { ( ) => handleOnChange ( filter ( value , ( v ) => v !== key ) ) }
67
- >
68
- < span className = { styles . title } > { l } </ span >
69
- < CloseIcon />
70
- </ BaseButton >
71
- )
72
- }
73
- } )
116
+
117
+ useEffect ( ( ) => {
118
+ if ( value === '' && searchable ) {
119
+ setSearchValue ( '' )
74
120
}
75
- return < span > { title } </ span >
76
- }
121
+ if ( searchable && searchValue === '' && value && value !== '' ) {
122
+ handleOnChange ( '' )
123
+ }
124
+ } , [ value , searchable ] )
77
125
78
126
useOutsideClick ( { ref, isOpen : open , setOpen } )
79
- const handleOptionClick = ( key ) => {
127
+ const handleOptionClick = ( key , label ) => {
80
128
if ( multiple ) {
81
129
if ( includes ( value , key ) ) {
82
130
handleOnChange ( filter ( value , ( v ) => v !== key ) )
@@ -87,11 +135,22 @@ const SelectComponent = ({
87
135
handleOnChange ( key )
88
136
}
89
137
if ( ! multiple ) setOpen ( false )
138
+ if ( searchable ) {
139
+ setTimeout ( ( ) => {
140
+ setSearchValue ( label )
141
+ setOpen ( false )
142
+ } , 300 )
143
+ }
90
144
}
91
145
92
146
const handleOpen = ( ) => {
93
147
if ( loading || disabled || lodashEmpty ( options ) ) return
94
148
setOpen ( ! open )
149
+ if ( searchInputRef . current ) {
150
+ setTimeout ( ( ) => {
151
+ searchInputRef . current . focus ( )
152
+ } , 600 )
153
+ }
95
154
}
96
155
97
156
const handleKeyPress = ( e ) => {
@@ -100,10 +159,11 @@ const SelectComponent = ({
100
159
}
101
160
}
102
161
103
- const Content = ( ) => {
104
- if ( loading ) return < Loader loading className = { styles . loading } />
105
- return < Title />
106
- }
162
+ const filteredOptions = ! searchable
163
+ ? options
164
+ : filter ( options , ( option ) =>
165
+ option . label . toLowerCase ( ) . includes ( searchValue . toLowerCase ( ) )
166
+ )
107
167
108
168
return (
109
169
< div
@@ -154,7 +214,18 @@ const SelectComponent = ({
154
214
) }
155
215
tabIndex = "0"
156
216
>
157
- < Content />
217
+ < Content
218
+ options = { options }
219
+ handleOnChange = { handleOnChange }
220
+ searchInputRef = { searchInputRef }
221
+ searchValue = { searchValue }
222
+ setSearchValue = { setSearchValue }
223
+ searchable = { searchable }
224
+ loading = { loading }
225
+ placeholder = { placeholder }
226
+ multiple = { multiple }
227
+ value = { value }
228
+ />
158
229
< ArrowDownIcon
159
230
className = { classNames ( styles . arrowIcon , open && styles . active ) }
160
231
/>
@@ -166,18 +237,22 @@ const SelectComponent = ({
166
237
< div className = { styles . wrapper } >
167
238
{ open && ! isCustom && (
168
239
< div className = { styles . options } >
169
- { map ( options , ( option , index ) => (
170
- < BaseButton
171
- key = { index }
172
- className = { classNames (
173
- styles . option ,
174
- isActive ( option . value ) && styles . active
175
- ) }
176
- onClick = { ( ) => handleOptionClick ( option . value ) }
177
- >
178
- < span > { option . label } </ span >
179
- </ BaseButton >
180
- ) ) }
240
+ { isEmpty ( filteredOptions ) ? (
241
+ < span className = { styles . option } > 😢</ span >
242
+ ) : (
243
+ map ( filteredOptions , ( option , index ) => (
244
+ < BaseButton
245
+ key = { index }
246
+ className = { classNames (
247
+ styles . option ,
248
+ isActive ( option . value ) && styles . active
249
+ ) }
250
+ onClick = { ( ) => handleOptionClick ( option . value , option . label ) }
251
+ >
252
+ < span > { option . label } </ span >
253
+ </ BaseButton >
254
+ ) )
255
+ ) }
181
256
</ div >
182
257
) }
183
258
</ div >
@@ -206,6 +281,7 @@ SelectComponent.propTypes = {
206
281
active : PropTypes . bool ,
207
282
} ) ,
208
283
isCustom : PropTypes . bool ,
284
+ searchable : PropTypes . bool ,
209
285
placeholder : PropTypes . string ,
210
286
}
211
287
@@ -225,6 +301,7 @@ SelectComponent.defaultProps = {
225
301
fluid : false ,
226
302
loading : false ,
227
303
name : null ,
304
+ searchable : false ,
228
305
disabled : false ,
229
306
options : [ ] ,
230
307
multiple : false ,
@@ -290,6 +367,7 @@ Select.propTypes = {
290
367
onChange : PropTypes . func ,
291
368
normalize : PropTypes . func ,
292
369
fluid : PropTypes . bool ,
370
+ searchable : PropTypes . bool ,
293
371
label : PropTypes . string ,
294
372
multiple : PropTypes . bool ,
295
373
onReset : PropTypes . any ,
@@ -302,6 +380,7 @@ Select.defaultProps = {
302
380
label : '' ,
303
381
className : null ,
304
382
visible : true ,
383
+ searchable : false ,
305
384
fluid : false ,
306
385
loading : false ,
307
386
name : null ,
0 commit comments