1
1
/** @jsxImportSource @emotion /react */
2
- import { FormHTMLAttributes , useCallback , useEffect , useState } from "react" ;
2
+ import { FormHTMLAttributes , useCallback , useEffect , useMemo , useRef , useState } from "react" ;
3
3
import { useNavigate , useSearchParams } from "react-router-dom" ;
4
- import { Button , FormGroup , TextField } from "@mui/material" ;
4
+ import { Autocomplete , Button , FormGroup , TextField , debounce } from "@mui/material" ;
5
5
import SearchIcon from "@mui/icons-material/Search" ;
6
6
import { css , Theme } from "@emotion/react" ;
7
7
8
+ import { useAutocompleteSearchQuery } from "../hooks/useAutocompleteSearchQuery" ;
8
9
import { Network } from "../model/network" ;
9
10
import { getNetworks } from "../services/networksService" ;
10
11
11
12
import { NetworkSelect } from "./NetworkSelect" ;
12
13
14
+ const formStyle = css `
15
+ position : relative;
16
+ text-align : left;
17
+ ` ;
18
+
13
19
const formGroupStyle = css `
14
20
flex-direction : row;
15
21
justify-content : center;
16
22
flex-wrap : nowrap;
17
23
` ;
18
24
19
25
const networkSelectStyle = ( theme : Theme ) => css `
20
- flex : 1 0 auto;
26
+ flex : 0 0 auto;
21
27
22
28
border-top-right-radius : 0 ;
23
29
border-bottom-right-radius : 0 ;
@@ -47,12 +53,24 @@ const networkSelectStyle = (theme: Theme) => css`
47
53
min-width : 0 ;
48
54
}
49
55
50
- . MuiListItemText-root {
56
+ > span {
51
57
display : none;
52
58
}
53
59
}
54
60
` ;
55
61
62
+ const inputStyle = css `
63
+ flex: 1 0 auto ;
64
+
65
+ .MuiOutlinedInput-root {
66
+ padding : 0 !important ;
67
+
68
+ .MuiAutocomplete-input {
69
+ padding : 12px 16px ;
70
+ }
71
+ }
72
+ ` ;
73
+
56
74
const textFieldStyle = css `
57
75
.MuiInputBase-root {
58
76
border-radius : 0 ;
@@ -70,6 +88,24 @@ const textFieldStyle = css`
70
88
}
71
89
` ;
72
90
91
+ const autocompleteNameStyle = css `
92
+ flex : 1 1 auto;
93
+ overflow : hidden;
94
+ text-overflow : ellipsis;
95
+ padding-right : 16px ;
96
+ ` ;
97
+
98
+ const autocompleteTypeStyle = css `
99
+ margin-left : auto;
100
+ flex : 0 0 auto;
101
+ font-size : 12px ;
102
+ opacity : .75 ;
103
+ border : solid 1px gray;
104
+ border-radius : 8px ;
105
+ padding : 0 4px ;
106
+ background-color : rgba (0 , 0 , 0 , .025 );
107
+ ` ;
108
+
73
109
const buttonStyle = ( theme : Theme ) => css `
74
110
border-radius : 8px ;
75
111
border-top-left-radius : 0px ;
@@ -99,6 +135,14 @@ const buttonStyle = (theme: Theme) => css`
99
135
}
100
136
` ;
101
137
138
+ function storeNetworks ( networks : Network [ ] ) {
139
+ localStorage . setItem ( "networks" , JSON . stringify ( networks . map ( it => it . name ) ) ) ;
140
+ }
141
+
142
+ function loadNetworks ( ) {
143
+ return getNetworks ( JSON . parse ( localStorage . getItem ( "networks" ) || "[]" ) ) ;
144
+ }
145
+
102
146
export type SearchInputProps = FormHTMLAttributes < HTMLFormElement > & {
103
147
persist ?: boolean ;
104
148
defaultNetworks ?: Network [ ] ;
@@ -109,13 +153,17 @@ function SearchInput(props: SearchInputProps) {
109
153
110
154
const [ qs ] = useSearchParams ( ) ;
111
155
156
+ const navigate = useNavigate ( ) ;
157
+
158
+ const formRef = useRef < HTMLFormElement > ( null ) ;
159
+
112
160
const [ networks , setNetworks ] = useState < Network [ ] > ( defaultNetworks || getNetworks ( qs . getAll ( "network" ) || [ ] ) ) ;
113
161
const [ query , setQuery ] = useState < string > ( qs . get ( "query" ) || "" ) ;
162
+ const [ autocompleteQuery , _setAutocompleteQuery ] = useState < string > ( query || "" ) ;
114
163
115
- const navigate = useNavigate ( ) ;
164
+ const setAutocompleteQuery = useMemo ( ( ) => debounce ( _setAutocompleteQuery , 250 ) , [ ] ) ;
116
165
117
- const storeNetworks = ( networks : Network [ ] ) => localStorage . setItem ( "networks" , JSON . stringify ( networks . map ( it => it . name ) ) ) ;
118
- const loadNetworks = ( ) => getNetworks ( JSON . parse ( localStorage . getItem ( "networks" ) || "[]" ) ) ;
166
+ const autocompleteSuggestions = useAutocompleteSearchQuery ( autocompleteQuery , networks ) ;
119
167
120
168
const handleNetworkSelect = useCallback ( ( networks : Network [ ] , isUserAction : boolean ) => {
121
169
if ( isUserAction && persist ) {
@@ -126,6 +174,11 @@ function SearchInput(props: SearchInputProps) {
126
174
setNetworks ( networks ) ;
127
175
} , [ persist ] ) ;
128
176
177
+ const handleQueryChange = useCallback ( ( ev : any , value : string ) => {
178
+ setQuery ( value ) ;
179
+ setAutocompleteQuery ( value ) ;
180
+ } , [ ] ) ;
181
+
129
182
const handleSubmit = useCallback ( ( ev : any ) => {
130
183
ev . preventDefault ( ) ;
131
184
@@ -158,34 +211,67 @@ function SearchInput(props: SearchInputProps) {
158
211
} , [ persist ] ) ;
159
212
160
213
return (
161
- < form { ...restProps } onSubmit = { handleSubmit } >
162
- < FormGroup row css = { formGroupStyle } >
163
- < NetworkSelect
164
- css = { networkSelectStyle }
165
- onChange = { handleNetworkSelect }
166
- value = { networks }
167
- multiselect
168
- />
169
- < TextField
170
- css = { textFieldStyle }
171
- fullWidth
172
- id = "search"
173
- onChange = { ( e ) => setQuery ( e . target . value ) }
174
- placeholder = "Extrinsic hash / account address / block hash / block height / extrinsic name / event name"
175
- value = { query }
176
- />
177
- < Button
178
- css = { buttonStyle }
179
- onClick = { handleSubmit }
180
- startIcon = { < SearchIcon /> }
181
- type = "submit"
182
- variant = "contained"
183
- color = "primary"
184
- data-class = "search-button"
185
- >
186
- < span className = "text" > Search</ span >
187
- </ Button >
188
- </ FormGroup >
214
+ < form { ...restProps } css = { formStyle } onSubmit = { handleSubmit } data-test = "search-input" ref = { formRef } >
215
+ < Autocomplete
216
+ css = { inputStyle }
217
+ freeSolo
218
+ includeInputInList
219
+ autoComplete
220
+ disableClearable
221
+ options = { autocompleteSuggestions . data || [ ] }
222
+ disablePortal
223
+ fullWidth
224
+ filterOptions = { it => it }
225
+ inputValue = { query }
226
+ onInputChange = { handleQueryChange }
227
+ renderOption = { ( props , option ) => (
228
+ < li { ...props } >
229
+ < div css = { autocompleteNameStyle } >
230
+ { option . label . slice ( 0 , option . highlight [ 0 ] ) }
231
+ < strong > { option . label . slice ( option . highlight [ 0 ] , option . highlight [ 1 ] ) } </ strong >
232
+ { option . label . slice ( option . highlight [ 1 ] ) }
233
+ </ div >
234
+ < div css = { autocompleteTypeStyle } > { option . type } </ div >
235
+ </ li >
236
+ ) }
237
+ componentsProps = { {
238
+ popper : {
239
+ anchorEl : formRef . current ,
240
+ placement : "bottom-start" ,
241
+ style : {
242
+ width : "100%"
243
+ }
244
+ }
245
+ } }
246
+ renderInput = { ( params ) =>
247
+ < FormGroup row css = { formGroupStyle } >
248
+ < NetworkSelect
249
+ css = { networkSelectStyle }
250
+ onChange = { handleNetworkSelect }
251
+ value = { networks }
252
+ multiselect
253
+ />
254
+ < TextField
255
+ { ...params }
256
+ css = { textFieldStyle }
257
+ fullWidth
258
+ id = "search"
259
+ placeholder = "Extrinsic hash / account address / block hash / block height / extrinsic name / event name"
260
+ />
261
+ < Button
262
+ css = { buttonStyle }
263
+ onClick = { handleSubmit }
264
+ startIcon = { < SearchIcon /> }
265
+ type = "submit"
266
+ variant = "contained"
267
+ color = "primary"
268
+ data-class = "search-button"
269
+ >
270
+ < span className = "text" > Search</ span >
271
+ </ Button >
272
+ </ FormGroup >
273
+ }
274
+ />
189
275
</ form >
190
276
) ;
191
277
}
0 commit comments