1
1
import { Autocomplete } from '@mui/material' ;
2
- import React , { useEffect , useRef , useState } from 'react' ;
3
- import { Box , Chip , Grid , TextField , Tooltip , Typography } from '../../base' ;
2
+ import React , { useCallback , useEffect , useState } from 'react' ;
3
+ import { Box , Chip , CircularProgress , Grid , TextField , Tooltip , Typography } from '../../base' ;
4
4
import { iconLarge , iconSmall } from '../../constants/iconsSizes' ;
5
5
import { CloseIcon , OrgIcon } from '../../icons' ;
6
6
@@ -10,95 +10,112 @@ interface Option {
10
10
}
11
11
12
12
interface InputFieldSearchProps {
13
- defaultData ?: Option [ ] ;
13
+ data : Option [ ] ;
14
+ setFilterData : ( data : Option [ ] ) => void ;
14
15
label ?: string ;
15
16
fetchSuggestions : ( value : string ) => void ;
16
- setFilterData : ( data : Option [ ] ) => void ;
17
17
isLoading : boolean ;
18
18
type : string ;
19
19
disabled ?: boolean ;
20
+ selectedData : Option [ ] ;
21
+ searchValue : string ;
22
+ setSearchValue : ( value : string ) => void ;
20
23
}
21
24
22
25
const InputFieldSearch : React . FC < InputFieldSearchProps > = ( {
23
- defaultData = [ ] ,
26
+ data ,
24
27
label,
25
28
fetchSuggestions,
26
29
setFilterData,
27
30
isLoading,
28
31
type,
29
- disabled
32
+ disabled,
33
+ selectedData,
34
+ searchValue,
35
+ setSearchValue
30
36
} ) => {
31
- const [ data , setData ] = useState < Option [ ] > ( [ ] ) ;
32
37
const [ error , setError ] = useState ( '' ) ;
33
- const [ inputValue , setInputValue ] = useState ( '' ) ;
34
38
const [ open , setOpen ] = useState ( false ) ;
35
- const [ showAllUsers , setShowAllUsers ] = useState ( false ) ;
36
- const [ selectedOption , setSelectedOption ] = useState < Option | undefined > ( undefined ) ;
37
- const isFirstRender = useRef ( true ) ;
39
+ const [ showAllItems , setShowAllItems ] = useState ( false ) ;
40
+ const [ localSelectedData , setLocalSelectedData ] = useState < Option [ ] > ( selectedData ) ;
38
41
42
+ // Sync local state with prop changes
39
43
useEffect ( ( ) => {
40
- if ( ! isFirstRender . current ) {
41
- setFilterData ( data ) ;
42
- } else {
43
- isFirstRender . current = false ;
44
- }
45
- } , [ data , setFilterData ] ) ;
44
+ setLocalSelectedData ( selectedData ) ;
45
+ } , [ selectedData ] ) ;
46
46
47
- const handleDelete = ( id : string ) => {
48
- setData ( ( prevData ) => prevData . filter ( ( item ) => item . id !== id ) ) ;
49
- } ;
47
+ const handleDelete = useCallback (
48
+ ( id : string ) => {
49
+ const newData = localSelectedData . filter ( ( item ) => item . id !== id ) ;
50
+ setLocalSelectedData ( newData ) ;
51
+ setFilterData ( newData ) ;
52
+ } ,
53
+ [ localSelectedData , setFilterData ]
54
+ ) ;
50
55
51
- const handleAdd = ( event : React . SyntheticEvent , value : Option | null ) => {
52
- if ( ! value ) return ;
56
+ const handleAdd = useCallback (
57
+ ( _event : React . SyntheticEvent , value : Option | null ) => {
58
+ if ( ! value ) return ;
53
59
54
- setData ( ( prevData ) => {
55
- const isDuplicate = prevData . some ( ( item ) => item . id === value . id ) ;
60
+ // Check for duplicates
61
+ const isDuplicate = localSelectedData . some ( ( item ) => item . id === value . id ) ;
56
62
if ( isDuplicate ) {
57
63
setError ( `${ type } already selected` ) ;
58
- return prevData ;
64
+ return ;
59
65
}
60
66
67
+ // Update both local and parent state
68
+ const newData = [ ...localSelectedData , value ] ;
69
+ setLocalSelectedData ( newData ) ;
70
+ setFilterData ( newData ) ;
61
71
setError ( '' ) ;
62
- return [ ...prevData , value ] ;
63
- } ) ;
64
- setSelectedOption ( undefined ) ;
65
- setInputValue ( '' ) ;
66
- } ;
67
-
68
- const handleInputChange = ( event : React . SyntheticEvent , value : string ) => {
69
- setInputValue ( value ) ;
70
- if ( value === '' ) {
72
+ setSearchValue ( '' ) ;
71
73
setOpen ( false ) ;
72
- } else {
73
- const encodedValue = encodeURIComponent ( value ) ;
74
- fetchSuggestions ( encodedValue ) ;
75
- setError ( '' ) ;
76
- setOpen ( true ) ;
77
- }
78
- } ;
74
+ } ,
75
+ [ localSelectedData , setFilterData , type , setSearchValue ]
76
+ ) ;
77
+
78
+ const handleInputChange = useCallback (
79
+ ( _event : React . SyntheticEvent , value : string ) => {
80
+ setSearchValue ( value ) ;
81
+ if ( value === '' ) {
82
+ setOpen ( false ) ;
83
+ } else {
84
+ const encodedValue = encodeURIComponent ( value ) ;
85
+ fetchSuggestions ( encodedValue ) ;
86
+ setError ( '' ) ;
87
+ setOpen ( true ) ;
88
+ }
89
+ } ,
90
+ [ fetchSuggestions , setSearchValue ]
91
+ ) ;
79
92
80
93
return (
81
- < >
82
- < Autocomplete < Option , false , true , false >
94
+ < Box sx = { { width : '100%' } } >
95
+ < Autocomplete
83
96
id = { `${ type } -search-field` }
84
- sx = { { width : 'auto ' } }
85
- options = { defaultData }
86
- getOptionLabel = { ( option : Option ) => option . name }
97
+ style = { { width : '100% ' } }
98
+ options = { data }
99
+ getOptionLabel = { ( ) => searchValue }
87
100
isOptionEqualToValue = { ( option : Option , value : Option ) => option . id === value . id }
88
101
noOptionsText = { isLoading ? 'Loading...' : `No ${ type } found` }
89
102
loading = { isLoading }
90
103
open = { open }
104
+ onClose = { ( ) => setOpen ( false ) }
91
105
disabled = { disabled }
92
- value = { selectedOption }
93
- inputValue = { inputValue }
106
+ value = { undefined }
107
+ inputValue = { searchValue }
94
108
onChange = { handleAdd }
95
109
onInputChange = { handleInputChange }
96
- ffilterOptions = { ( options ) => options }
110
+ ffilterOptions = { ( x ) => x }
97
111
disableClearable
98
112
includeInputInList
99
113
filterSelectedOptions
100
114
disableListWrap
101
115
clearOnBlur
116
+ popupIcon = { null }
117
+ blurOnSelect
118
+ forcePopupIcon = { false }
102
119
renderInput = { ( params ) => (
103
120
< TextField
104
121
{ ...params }
@@ -109,50 +126,55 @@ const InputFieldSearch: React.FC<InputFieldSearchProps> = ({
109
126
InputProps = { {
110
127
...params . InputProps ,
111
128
endAdornment : (
112
- < React . Fragment > { isLoading ? < div color = "inherit" /> : null } </ React . Fragment >
129
+ < React . Fragment >
130
+ { isLoading ? < CircularProgress color = "inherit" size = { 20 } /> : null }
131
+ </ React . Fragment >
113
132
)
114
133
} }
115
134
/>
116
135
) }
117
136
renderOption = { ( props , option : Option ) => (
118
- < li { ...props } >
119
- < Grid container alignItems = "center" >
120
- < Grid item >
121
- < Box sx = { { color : 'text.secondary' , mr : 2 } } >
122
- < OrgIcon { ...iconLarge } />
123
- </ Box >
137
+ < li { ...props } key = { option . id } >
138
+ < Box component = "li" sx = { { '& > img' : { mr : 2 , flexShrink : 0 } } } >
139
+ < Grid container alignItems = "center" >
140
+ < Grid item >
141
+ < Box sx = { { color : 'text.secondary' , mr : 2 } } >
142
+ < OrgIcon { ...iconLarge } />
143
+ </ Box >
144
+ </ Grid >
145
+ < Grid item xs >
146
+ < Typography variant = "body2" > { option . name } </ Typography >
147
+ </ Grid >
124
148
</ Grid >
125
- < Grid item xs >
126
- < Typography variant = "body2" > { option . name } </ Typography >
127
- </ Grid >
128
- </ Grid >
149
+ </ Box >
129
150
</ li >
130
151
) }
131
152
/>
153
+
132
154
< Box
133
155
sx = { {
134
156
display : 'flex' ,
135
157
flexWrap : 'wrap' ,
136
158
gap : 0.5 ,
137
- mt : data ?. length > 0 ? '0.5rem' : ''
159
+ mt : localSelectedData ?. length > 0 ? '0.5rem' : ''
138
160
} }
139
161
>
140
- { ! showAllUsers && data ?. length > 0 && (
162
+ { ! showAllItems && localSelectedData ?. length > 0 && (
141
163
< Chip
142
- key = { data [ data . length - 1 ] ?. id }
164
+ key = { localSelectedData [ localSelectedData . length - 1 ] ?. id }
143
165
avatar = { < OrgIcon { ...iconSmall } /> }
144
- label = { data [ data . length - 1 ] ?. name }
166
+ label = { localSelectedData [ localSelectedData . length - 1 ] ?. name }
145
167
size = "small"
146
- onDelete = { ( ) => handleDelete ( data [ data . length - 1 ] ?. id ) }
168
+ onDelete = { ( ) => handleDelete ( localSelectedData [ localSelectedData . length - 1 ] ?. id ) }
147
169
deleteIcon = {
148
- < Tooltip title = " Remove member" >
170
+ < Tooltip title = { ` Remove ${ type } ` } >
149
171
< CloseIcon style = { iconSmall } />
150
172
</ Tooltip >
151
173
}
152
174
/>
153
175
) }
154
- { showAllUsers &&
155
- data ?. map ( ( obj ) => (
176
+ { showAllItems &&
177
+ localSelectedData ?. map ( ( obj ) => (
156
178
< Chip
157
179
key = { obj . id }
158
180
avatar = { < OrgIcon { ...iconSmall } /> }
@@ -166,23 +188,23 @@ const InputFieldSearch: React.FC<InputFieldSearchProps> = ({
166
188
}
167
189
/>
168
190
) ) }
169
- { data ?. length > 1 && (
191
+ { localSelectedData ?. length > 1 && (
170
192
< Typography
171
- onClick = { ( ) => setShowAllUsers ( ! showAllUsers ) }
193
+ onClick = { ( ) => setShowAllItems ( ! showAllItems ) }
172
194
sx = { {
173
195
cursor : 'pointer' ,
174
- color : 'white ' ,
196
+ color : 'primary.main ' ,
175
197
fontWeight : '600' ,
176
198
'&:hover' : {
177
- color : 'black '
199
+ color : 'primary.dark '
178
200
}
179
201
} }
180
202
>
181
- { showAllUsers ? '(hide)' : `(+${ data ?. length - 1 } )` }
203
+ { showAllItems ? '(hide)' : `(+${ localSelectedData ?. length - 1 } )` }
182
204
</ Typography >
183
205
) }
184
206
</ Box >
185
- </ >
207
+ </ Box >
186
208
) ;
187
209
} ;
188
210
0 commit comments