@@ -23,6 +23,13 @@ import { BaseOption } from "./options"
23
23
import debounce from "just-debounce-it"
24
24
import MenuList from "@mui/material/MenuList"
25
25
import { Loader } from "components/Loader/Loader"
26
+ import Divider from "@mui/material/Divider"
27
+ import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined"
28
+
29
+ export type PresetFilter = {
30
+ name : string
31
+ query : string
32
+ }
26
33
27
34
type FilterValues = Record < string , string | undefined >
28
35
@@ -127,12 +134,16 @@ export const Filter = ({
127
134
error,
128
135
skeleton,
129
136
options,
137
+ learnMoreLink,
138
+ presets,
130
139
} : {
131
140
filter : ReturnType < typeof useFilter >
132
141
skeleton : ReactNode
133
142
isLoading : boolean
143
+ learnMoreLink : string
134
144
error ?: unknown
135
145
options ?: ReactNode
146
+ presets : PresetFilter [ ]
136
147
} ) => {
137
148
const shouldDisplayError = hasError ( error ) && isApiValidationError ( error )
138
149
const hasFilterQuery = filter . query !== ""
@@ -148,61 +159,150 @@ export const Filter = ({
148
159
skeleton
149
160
) : (
150
161
< >
151
- < TextField
152
- fullWidth
153
- error = { shouldDisplayError }
154
- helperText = {
155
- shouldDisplayError ? getValidationErrorMessage ( error ) : undefined
156
- }
157
- size = "small"
158
- InputProps = { {
159
- name : "query" ,
160
- placeholder : "Search..." ,
161
- value : searchQuery ,
162
- onChange : ( e ) => {
163
- setSearchQuery ( e . target . value )
164
- filter . debounceUpdate ( e . target . value )
165
- } ,
166
- sx : {
167
- borderRadius : "6px" ,
168
- "& input::placeholder" : {
169
- color : ( theme ) => theme . palette . text . secondary ,
162
+ < Box sx = { { display : "flex" , width : "100%" } } >
163
+ < PresetMenu
164
+ onSelect = { ( query ) => filter . update ( query ) }
165
+ presets = { presets }
166
+ learnMoreLink = { learnMoreLink }
167
+ />
168
+ < TextField
169
+ fullWidth
170
+ error = { shouldDisplayError }
171
+ helperText = {
172
+ shouldDisplayError
173
+ ? getValidationErrorMessage ( error )
174
+ : undefined
175
+ }
176
+ size = "small"
177
+ InputProps = { {
178
+ name : "query" ,
179
+ placeholder : "Search..." ,
180
+ value : searchQuery ,
181
+ onChange : ( e ) => {
182
+ setSearchQuery ( e . target . value )
183
+ filter . debounceUpdate ( e . target . value )
184
+ } ,
185
+ sx : {
186
+ borderRadius : "6px" ,
187
+ borderTopLeftRadius : 0 ,
188
+ borderBottomLeftRadius : 0 ,
189
+ marginLeft : "-1px" ,
190
+ "&:hover" : {
191
+ zIndex : 2 ,
192
+ } ,
193
+ "& input::placeholder" : {
194
+ color : ( theme ) => theme . palette . text . secondary ,
195
+ } ,
196
+ "& .MuiInputAdornment-root" : {
197
+ marginLeft : 0 ,
198
+ } ,
170
199
} ,
171
- } ,
172
- startAdornment : (
173
- < InputAdornment position = "start" >
174
- < SearchOutlined
175
- sx = { {
176
- fontSize : 14 ,
177
- color : ( theme ) => theme . palette . text . secondary ,
178
- } }
179
- />
180
- </ InputAdornment >
181
- ) ,
182
- endAdornment : hasFilterQuery && (
183
- < InputAdornment position = "end" >
184
- < Tooltip title = "Clear filter" >
185
- < IconButton
186
- size = "small"
187
- onClick = { ( ) => {
188
- filter . update ( "" )
200
+ startAdornment : (
201
+ < InputAdornment position = "start" >
202
+ < SearchOutlined
203
+ sx = { {
204
+ fontSize : 14 ,
205
+ color : ( theme ) => theme . palette . text . secondary ,
189
206
} }
190
- >
191
- < CloseOutlined sx = { { fontSize : 14 } } />
192
- </ IconButton >
193
- </ Tooltip >
194
- </ InputAdornment >
195
- ) ,
196
- } }
197
- />
198
-
207
+ />
208
+ </ InputAdornment >
209
+ ) ,
210
+ endAdornment : hasFilterQuery && (
211
+ < InputAdornment position = "end" >
212
+ < Tooltip title = "Clear filter" >
213
+ < IconButton
214
+ size = "small"
215
+ onClick = { ( ) => {
216
+ filter . update ( "" )
217
+ } }
218
+ >
219
+ < CloseOutlined sx = { { fontSize : 14 } } />
220
+ </ IconButton >
221
+ </ Tooltip >
222
+ </ InputAdornment >
223
+ ) ,
224
+ } }
225
+ />
226
+ </ Box >
199
227
{ options }
200
228
</ >
201
229
) }
202
230
</ Box >
203
231
)
204
232
}
205
233
234
+ const PresetMenu = ( {
235
+ presets,
236
+ learnMoreLink,
237
+ onSelect,
238
+ } : {
239
+ presets : PresetFilter [ ]
240
+ learnMoreLink : string
241
+ onSelect : ( query : string ) => void
242
+ } ) => {
243
+ const [ isOpen , setIsOpen ] = useState ( false )
244
+ const anchorRef = useRef < HTMLButtonElement > ( null )
245
+
246
+ return (
247
+ < >
248
+ < Button
249
+ onClick = { ( ) => setIsOpen ( true ) }
250
+ ref = { anchorRef }
251
+ sx = { {
252
+ borderTopRightRadius : 0 ,
253
+ borderBottomRightRadius : 0 ,
254
+ flexShrink : 0 ,
255
+ zIndex : 1 ,
256
+ } }
257
+ endIcon = { < KeyboardArrowDown /> }
258
+ >
259
+ Filters
260
+ </ Button >
261
+ < Menu
262
+ id = "filter-menu"
263
+ anchorEl = { anchorRef . current }
264
+ open = { isOpen }
265
+ onClose = { ( ) => setIsOpen ( false ) }
266
+ anchorOrigin = { {
267
+ vertical : "bottom" ,
268
+ horizontal : "left" ,
269
+ } }
270
+ transformOrigin = { {
271
+ vertical : "top" ,
272
+ horizontal : "left" ,
273
+ } }
274
+ sx = { { "& .MuiMenu-paper" : { py : 1 } } }
275
+ >
276
+ { presets . map ( ( presetFilter ) => (
277
+ < MenuItem
278
+ sx = { { fontSize : 14 } }
279
+ key = { presetFilter . name }
280
+ onClick = { ( ) => {
281
+ onSelect ( presetFilter . query )
282
+ setIsOpen ( false )
283
+ } }
284
+ >
285
+ { presetFilter . name }
286
+ </ MenuItem >
287
+ ) ) }
288
+ < Divider sx = { { borderColor : ( theme ) => theme . palette . divider } } />
289
+ < MenuItem
290
+ component = "a"
291
+ href = { learnMoreLink }
292
+ target = "_blank"
293
+ sx = { { fontSize : 13 , fontWeight : 500 } }
294
+ onClick = { ( ) => {
295
+ setIsOpen ( false )
296
+ } }
297
+ >
298
+ < OpenInNewOutlined sx = { { fontSize : "14px !important" } } />
299
+ View advanced filtering
300
+ </ MenuItem >
301
+ </ Menu >
302
+ </ >
303
+ )
304
+ }
305
+
206
306
export const FilterMenu = < TOption extends BaseOption > ( {
207
307
id,
208
308
menu,
0 commit comments