@backstage/plugin-search-react 1.0.2-next.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1,17 +1,20 @@
1
- import { createApiRef, useApi, AnalyticsContext, useAnalytics, configApiRef } from '@backstage/core-plugin-api';
2
- import React, { useMemo, useContext, useState, useCallback, useEffect, useRef } from 'react';
3
- import { makeStyles, TextField, Chip, FormControl, FormLabel, FormControlLabel, Checkbox, InputLabel, Select, MenuItem, Button, InputBase, InputAdornment, IconButton, ListItem, ListItemIcon, ListItemText, Box, Divider } from '@material-ui/core';
4
- import { createVersionedContext, createVersionedValueMap } from '@backstage/version-bridge';
1
+ import { createApiRef, AnalyticsContext, useApi, useAnalytics, configApiRef } from '@backstage/core-plugin-api';
2
+ import React, { useMemo, useContext, useState, useCallback, useEffect, forwardRef, useRef } from 'react';
3
+ import { makeStyles, InputBase, InputAdornment, IconButton, CircularProgress, ListItemIcon, ListItemText, TextField, Chip, FormControl, FormLabel, FormControlLabel, Checkbox, InputLabel, Select, MenuItem, Button, ListItem, Box, Divider, List, Typography, ListSubheader, Menu } from '@material-ui/core';
4
+ import useDebounce from 'react-use/lib/useDebounce';
5
+ import SearchIcon from '@material-ui/icons/Search';
6
+ import ClearButton from '@material-ui/icons/Clear';
7
+ import { isEqual } from 'lodash';
5
8
  import useAsync from 'react-use/lib/useAsync';
6
9
  import usePrevious from 'react-use/lib/usePrevious';
10
+ import { createVersionedContext, createVersionedValueMap } from '@backstage/version-bridge';
7
11
  import { Autocomplete } from '@material-ui/lab';
8
12
  import useAsyncFn from 'react-use/lib/useAsyncFn';
9
- import useDebounce from 'react-use/lib/useDebounce';
10
13
  import { Progress, ResponseErrorPanel, EmptyState, Link } from '@backstage/core-components';
11
14
  import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
12
15
  import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
13
- import SearchIcon from '@material-ui/icons/Search';
14
- import ClearButton from '@material-ui/icons/Clear';
16
+ import qs from 'qs';
17
+ import AddIcon from '@material-ui/icons/Add';
15
18
 
16
19
  const searchApiRef = createApiRef({
17
20
  id: "plugin.search.queryservice"
@@ -25,7 +28,7 @@ class MockSearchApi {
25
28
  }
26
29
  }
27
30
 
28
- const useStyles$2 = makeStyles(
31
+ const useStyles$3 = makeStyles(
29
32
  () => ({
30
33
  highlight: {}
31
34
  }),
@@ -36,7 +39,7 @@ const HighlightedSearchResultText = ({
36
39
  preTag,
37
40
  postTag
38
41
  }) => {
39
- const classes = useStyles$2();
42
+ const classes = useStyles$3();
40
43
  const terms = useMemo(
41
44
  () => text.split(new RegExp(`(${preTag}.+?${postTag})`)),
42
45
  [postTag, preTag, text]
@@ -71,17 +74,17 @@ const searchInitialState = {
71
74
  filters: {},
72
75
  types: []
73
76
  };
74
- const SearchContextProvider = (props) => {
77
+ const useSearchContextValue = (initialValue = searchInitialState) => {
75
78
  var _a, _b, _c, _d;
76
- const { initialState = searchInitialState, children } = props;
77
79
  const searchApi = useApi(searchApiRef);
80
+ const [term, setTerm] = useState(initialValue.term);
81
+ const [types, setTypes] = useState(initialValue.types);
82
+ const [filters, setFilters] = useState(initialValue.filters);
78
83
  const [pageCursor, setPageCursor] = useState(
79
- initialState.pageCursor
84
+ initialValue.pageCursor
80
85
  );
81
- const [filters, setFilters] = useState(initialState.filters);
82
- const [term, setTerm] = useState(initialState.term);
83
- const [types, setTypes] = useState(initialState.types);
84
86
  const prevTerm = usePrevious(term);
87
+ const prevFilters = usePrevious(filters);
85
88
  const result = useAsync(
86
89
  () => searchApi.query({
87
90
  term,
@@ -89,7 +92,7 @@ const SearchContextProvider = (props) => {
89
92
  pageCursor,
90
93
  types
91
94
  }),
92
- [term, filters, types, pageCursor]
95
+ [term, types, filters, pageCursor]
93
96
  );
94
97
  const hasNextPage = !result.loading && !result.error && ((_a = result.value) == null ? void 0 : _a.nextPageCursor);
95
98
  const hasPreviousPage = !result.loading && !result.error && ((_b = result.value) == null ? void 0 : _b.previousPageCursor);
@@ -106,6 +109,11 @@ const SearchContextProvider = (props) => {
106
109
  setPageCursor(void 0);
107
110
  }
108
111
  }, [term, prevTerm, setPageCursor]);
112
+ useEffect(() => {
113
+ if (prevFilters !== void 0 && !isEqual(filters, prevFilters)) {
114
+ setPageCursor(void 0);
115
+ }
116
+ }, [filters, prevFilters, setPageCursor]);
109
117
  const value = {
110
118
  result,
111
119
  filters,
@@ -119,14 +127,248 @@ const SearchContextProvider = (props) => {
119
127
  fetchNextPage: hasNextPage ? fetchNextPage : void 0,
120
128
  fetchPreviousPage: hasPreviousPage ? fetchPreviousPage : void 0
121
129
  };
122
- const versionedValue = createVersionedValueMap({ 1: value });
130
+ return value;
131
+ };
132
+ const LocalSearchContext = (props) => {
133
+ const { initialState, children } = props;
134
+ const value = useSearchContextValue(initialState);
123
135
  return /* @__PURE__ */ React.createElement(AnalyticsContext, {
124
- attributes: { searchTypes: types.sort().join(",") }
136
+ attributes: { searchTypes: value.types.sort().join(",") }
125
137
  }, /* @__PURE__ */ React.createElement(SearchContext.Provider, {
126
- value: versionedValue,
127
- children
138
+ value: createVersionedValueMap({ 1: value })
139
+ }, children));
140
+ };
141
+ const SearchContextProvider = (props) => {
142
+ const { initialState, inheritParentContextIfAvailable, children } = props;
143
+ const hasParentContext = useSearchContextCheck();
144
+ return hasParentContext && inheritParentContextIfAvailable ? /* @__PURE__ */ React.createElement(React.Fragment, null, children) : /* @__PURE__ */ React.createElement(LocalSearchContext, {
145
+ initialState
146
+ }, children);
147
+ };
148
+
149
+ const TrackSearch = ({ children }) => {
150
+ const analytics = useAnalytics();
151
+ const { term } = useSearch();
152
+ useEffect(() => {
153
+ if (term) {
154
+ analytics.captureEvent("search", term);
155
+ }
156
+ }, [analytics, term]);
157
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
158
+ };
159
+
160
+ function withContext$1(Component) {
161
+ return forwardRef((props, ref) => /* @__PURE__ */ React.createElement(SearchContextProvider, {
162
+ inheritParentContextIfAvailable: true
163
+ }, /* @__PURE__ */ React.createElement(Component, {
164
+ ...props,
165
+ ref
166
+ })));
167
+ }
168
+ const SearchBarBase = withContext$1(
169
+ forwardRef((props, ref) => {
170
+ const {
171
+ onChange,
172
+ onKeyDown = () => {
173
+ },
174
+ onClear = () => {
175
+ },
176
+ onSubmit = () => {
177
+ },
178
+ debounceTime = 200,
179
+ clearButton = true,
180
+ fullWidth = true,
181
+ value: defaultValue,
182
+ placeholder: defaultPlaceholder,
183
+ inputProps: defaultInputProps = {},
184
+ endAdornment: defaultEndAdornment,
185
+ ...rest
186
+ } = props;
187
+ const configApi = useApi(configApiRef);
188
+ const [value, setValue] = useState("");
189
+ useEffect(() => {
190
+ setValue(
191
+ (prevValue) => prevValue !== defaultValue ? String(defaultValue) : prevValue
192
+ );
193
+ }, [defaultValue]);
194
+ useDebounce(() => onChange(value), debounceTime, [value]);
195
+ const handleChange = useCallback(
196
+ (e) => {
197
+ setValue(e.target.value);
198
+ },
199
+ [setValue]
200
+ );
201
+ const handleKeyDown = useCallback(
202
+ (e) => {
203
+ if (onKeyDown)
204
+ onKeyDown(e);
205
+ if (onSubmit && e.key === "Enter") {
206
+ onSubmit();
207
+ }
208
+ },
209
+ [onKeyDown, onSubmit]
210
+ );
211
+ const handleClear = useCallback(() => {
212
+ onChange("");
213
+ if (onClear) {
214
+ onClear();
215
+ }
216
+ }, [onChange, onClear]);
217
+ const placeholder = defaultPlaceholder != null ? defaultPlaceholder : `Search in ${configApi.getOptionalString("app.title") || "Backstage"}`;
218
+ const startAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
219
+ position: "start"
220
+ }, /* @__PURE__ */ React.createElement(IconButton, {
221
+ "aria-label": "Query",
222
+ size: "small",
223
+ disabled: true
224
+ }, /* @__PURE__ */ React.createElement(SearchIcon, null)));
225
+ const endAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
226
+ position: "end"
227
+ }, /* @__PURE__ */ React.createElement(IconButton, {
228
+ "aria-label": "Clear",
229
+ size: "small",
230
+ onClick: handleClear
231
+ }, /* @__PURE__ */ React.createElement(ClearButton, null)));
232
+ return /* @__PURE__ */ React.createElement(TrackSearch, null, /* @__PURE__ */ React.createElement(InputBase, {
233
+ "data-testid": "search-bar-next",
234
+ ref,
235
+ value,
236
+ placeholder,
237
+ startAdornment,
238
+ endAdornment: clearButton ? endAdornment : defaultEndAdornment,
239
+ inputProps: { "aria-label": "Search", ...defaultInputProps },
240
+ fullWidth,
241
+ onChange: handleChange,
242
+ onKeyDown: handleKeyDown,
243
+ ...rest
244
+ }));
245
+ })
246
+ );
247
+ const SearchBar = withContext$1(
248
+ forwardRef((props, ref) => {
249
+ const { value: initialValue = "", onChange, ...rest } = props;
250
+ const { term, setTerm } = useSearch();
251
+ useEffect(() => {
252
+ if (initialValue) {
253
+ setTerm(String(initialValue));
254
+ }
255
+ }, [initialValue, setTerm]);
256
+ const handleChange = useCallback(
257
+ (newValue) => {
258
+ if (onChange) {
259
+ onChange(newValue);
260
+ } else {
261
+ setTerm(newValue);
262
+ }
263
+ },
264
+ [onChange, setTerm]
265
+ );
266
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, {
267
+ attributes: { pluginId: "search", extension: "SearchBar" }
268
+ }, /* @__PURE__ */ React.createElement(SearchBarBase, {
269
+ ...rest,
270
+ ref,
271
+ value: term,
272
+ onChange: handleChange
273
+ }));
274
+ })
275
+ );
276
+
277
+ const withContext = (Component) => {
278
+ return (props) => /* @__PURE__ */ React.createElement(SearchContextProvider, {
279
+ inheritParentContextIfAvailable: true
280
+ }, /* @__PURE__ */ React.createElement(Component, {
281
+ ...props
128
282
  }));
129
283
  };
284
+ const SearchAutocomplete = withContext(
285
+ function SearchAutocompleteComponent(props) {
286
+ const {
287
+ loading,
288
+ value,
289
+ onChange = () => {
290
+ },
291
+ options = [],
292
+ getOptionLabel = (option) => String(option),
293
+ inputPlaceholder,
294
+ inputDebounceTime,
295
+ freeSolo = true,
296
+ fullWidth = true,
297
+ clearOnBlur = false,
298
+ "data-testid": dataTestId = "search-autocomplete",
299
+ ...rest
300
+ } = props;
301
+ const { setTerm } = useSearch();
302
+ const getInputValue = useCallback(
303
+ (option) => {
304
+ if (!option)
305
+ return "";
306
+ if (typeof option === "string")
307
+ return option;
308
+ return getOptionLabel(option);
309
+ },
310
+ [getOptionLabel]
311
+ );
312
+ const inputValue = useMemo(
313
+ () => getInputValue(value),
314
+ [value, getInputValue]
315
+ );
316
+ const handleChange = useCallback(
317
+ (event, option, reason, details) => {
318
+ setTerm(getInputValue(option));
319
+ onChange(event, option, reason, details);
320
+ },
321
+ [getInputValue, setTerm, onChange]
322
+ );
323
+ const renderInput = useCallback(
324
+ ({
325
+ InputProps: { ref, endAdornment },
326
+ InputLabelProps,
327
+ ...params
328
+ }) => /* @__PURE__ */ React.createElement(SearchBar, {
329
+ ...params,
330
+ ref,
331
+ clearButton: false,
332
+ value: inputValue,
333
+ placeholder: inputPlaceholder,
334
+ debounceTime: inputDebounceTime,
335
+ endAdornment: loading ? /* @__PURE__ */ React.createElement(CircularProgress, {
336
+ "data-testid": "search-autocomplete-progressbar",
337
+ color: "inherit",
338
+ size: 20
339
+ }) : endAdornment
340
+ }),
341
+ [loading, inputValue, inputPlaceholder, inputDebounceTime]
342
+ );
343
+ return /* @__PURE__ */ React.createElement(Autocomplete, {
344
+ ...rest,
345
+ "data-testid": dataTestId,
346
+ value,
347
+ onChange: handleChange,
348
+ options,
349
+ getOptionLabel,
350
+ renderInput,
351
+ freeSolo,
352
+ fullWidth,
353
+ clearOnBlur
354
+ });
355
+ }
356
+ );
357
+
358
+ const SearchAutocompleteDefaultOption = ({
359
+ icon,
360
+ primaryText,
361
+ primaryTextTypographyProps,
362
+ secondaryText,
363
+ secondaryTextTypographyProps,
364
+ disableTextTypography
365
+ }) => /* @__PURE__ */ React.createElement(React.Fragment, null, icon ? /* @__PURE__ */ React.createElement(ListItemIcon, null, icon) : null, /* @__PURE__ */ React.createElement(ListItemText, {
366
+ primary: primaryText,
367
+ primaryTypographyProps: primaryTextTypographyProps,
368
+ secondary: secondaryText,
369
+ secondaryTypographyProps: secondaryTextTypographyProps,
370
+ disableTypography: disableTextTypography
371
+ }));
130
372
 
131
373
  const useAsyncFilterValues = (fn, inputValue, defaultValues = [], debounce = 250) => {
132
374
  const valuesMemo = useRef({});
@@ -234,7 +476,7 @@ const AutocompleteFilter = (props) => {
234
476
  });
235
477
  };
236
478
 
237
- const useStyles$1 = makeStyles({
479
+ const useStyles$2 = makeStyles({
238
480
  label: {
239
481
  textTransform: "capitalize"
240
482
  }
@@ -248,7 +490,7 @@ const CheckboxFilter = (props) => {
248
490
  values: givenValues = [],
249
491
  valuesDebounceMs
250
492
  } = props;
251
- const classes = useStyles$1();
493
+ const classes = useStyles$2();
252
494
  const { filters, setFilters } = useSearch();
253
495
  useDefaultFilterValue(name, defaultValue);
254
496
  const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
@@ -303,7 +545,7 @@ const SelectFilter = (props) => {
303
545
  values: givenValues,
304
546
  valuesDebounceMs
305
547
  } = props;
306
- const classes = useStyles$1();
548
+ const classes = useStyles$2();
307
549
  useDefaultFilterValue(name, defaultValue);
308
550
  const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
309
551
  const defaultValues = typeof givenValues === "function" ? void 0 : givenValues;
@@ -362,39 +604,68 @@ SearchFilter.Autocomplete = (props) => /* @__PURE__ */ React.createElement(Searc
362
604
  component: AutocompleteFilter
363
605
  });
364
606
 
365
- const SearchResultComponent = ({ children }) => {
366
- const {
367
- result: { loading, error, value }
368
- } = useSearch();
369
- if (loading) {
370
- return /* @__PURE__ */ React.createElement(Progress, null);
371
- }
372
- if (error) {
373
- return /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
374
- title: "Error encountered while fetching search results",
375
- error
376
- });
377
- }
378
- if (!(value == null ? void 0 : value.results.length)) {
379
- return /* @__PURE__ */ React.createElement(EmptyState, {
380
- missing: "data",
381
- title: "Sorry, no results were found"
382
- });
383
- }
384
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children({ results: value.results }));
607
+ const SearchResultContext = (props) => {
608
+ const { children } = props;
609
+ const context = useSearch();
610
+ const state = context.result;
611
+ return children(state);
385
612
  };
386
- const HigherOrderSearchResult = (props) => {
387
- return /* @__PURE__ */ React.createElement(AnalyticsContext, {
388
- attributes: {
389
- pluginId: "search",
390
- extension: "SearchResult"
613
+ const SearchResultApi = (props) => {
614
+ const { query, children } = props;
615
+ const searchApi = useApi(searchApiRef);
616
+ const state = useAsync(
617
+ () => {
618
+ var _a, _b, _c;
619
+ return searchApi.query({
620
+ term: (_a = query.term) != null ? _a : "",
621
+ types: (_b = query.types) != null ? _b : [],
622
+ filters: (_c = query.filters) != null ? _c : {},
623
+ pageCursor: query.pageCursor
624
+ });
625
+ },
626
+ [query]
627
+ );
628
+ return children(state);
629
+ };
630
+ const SearchResultState = (props) => {
631
+ const { query, children } = props;
632
+ return query ? /* @__PURE__ */ React.createElement(SearchResultApi, {
633
+ query
634
+ }, children) : /* @__PURE__ */ React.createElement(SearchResultContext, null, children);
635
+ };
636
+ const SearchResultComponent = (props) => {
637
+ const { query, children } = props;
638
+ return /* @__PURE__ */ React.createElement(SearchResultState, {
639
+ query
640
+ }, ({ loading, error, value }) => {
641
+ if (loading) {
642
+ return /* @__PURE__ */ React.createElement(Progress, null);
391
643
  }
392
- }, /* @__PURE__ */ React.createElement(SearchResultComponent, {
393
- ...props
394
- }));
644
+ if (error) {
645
+ return /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
646
+ title: "Error encountered while fetching search results",
647
+ error
648
+ });
649
+ }
650
+ if (!(value == null ? void 0 : value.results.length)) {
651
+ return /* @__PURE__ */ React.createElement(EmptyState, {
652
+ missing: "data",
653
+ title: "Sorry, no results were found"
654
+ });
655
+ }
656
+ return children(value);
657
+ });
395
658
  };
659
+ const SearchResult = (props) => /* @__PURE__ */ React.createElement(AnalyticsContext, {
660
+ attributes: {
661
+ pluginId: "search",
662
+ extension: "SearchResult"
663
+ }
664
+ }, /* @__PURE__ */ React.createElement(SearchResultComponent, {
665
+ ...props
666
+ }));
396
667
 
397
- const useStyles = makeStyles((theme) => ({
668
+ const useStyles$1 = makeStyles((theme) => ({
398
669
  root: {
399
670
  display: "flex",
400
671
  justifyContent: "space-between",
@@ -404,12 +675,12 @@ const useStyles = makeStyles((theme) => ({
404
675
  }));
405
676
  const SearchResultPager = () => {
406
677
  const { fetchNextPage, fetchPreviousPage } = useSearch();
407
- const classes = useStyles();
678
+ const classes = useStyles$1();
408
679
  if (!fetchNextPage && !fetchPreviousPage) {
409
680
  return /* @__PURE__ */ React.createElement(React.Fragment, null);
410
681
  }
411
682
  return /* @__PURE__ */ React.createElement("nav", {
412
- "arial-label": "pagination navigation",
683
+ "aria-label": "pagination navigation",
413
684
  className: classes.root
414
685
  }, /* @__PURE__ */ React.createElement(Button, {
415
686
  "aria-label": "previous page",
@@ -424,105 +695,6 @@ const SearchResultPager = () => {
424
695
  }, "Next"));
425
696
  };
426
697
 
427
- const TrackSearch = ({ children }) => {
428
- const analytics = useAnalytics();
429
- const { term } = useSearch();
430
- useEffect(() => {
431
- if (term) {
432
- analytics.captureEvent("search", term);
433
- }
434
- }, [analytics, term]);
435
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
436
- };
437
-
438
- const SearchBarBase = ({
439
- onChange,
440
- onKeyDown,
441
- onSubmit,
442
- debounceTime = 200,
443
- clearButton = true,
444
- fullWidth = true,
445
- value: defaultValue,
446
- inputProps: defaultInputProps = {},
447
- endAdornment: defaultEndAdornment,
448
- ...props
449
- }) => {
450
- const configApi = useApi(configApiRef);
451
- const [value, setValue] = useState(defaultValue);
452
- const hasSearchContext = useSearchContextCheck();
453
- useEffect(() => {
454
- setValue(
455
- (prevValue) => prevValue !== defaultValue ? defaultValue : prevValue
456
- );
457
- }, [defaultValue]);
458
- useDebounce(() => onChange(value), debounceTime, [value]);
459
- const handleChange = useCallback(
460
- (e) => {
461
- setValue(e.target.value);
462
- },
463
- [setValue]
464
- );
465
- const handleKeyDown = useCallback(
466
- (e) => {
467
- if (onKeyDown)
468
- onKeyDown(e);
469
- if (onSubmit && e.key === "Enter") {
470
- onSubmit();
471
- }
472
- },
473
- [onKeyDown, onSubmit]
474
- );
475
- const handleClear = useCallback(() => {
476
- onChange("");
477
- }, [onChange]);
478
- const placeholder = `Search in ${configApi.getOptionalString("app.title") || "Backstage"}`;
479
- const startAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
480
- position: "start"
481
- }, /* @__PURE__ */ React.createElement(IconButton, {
482
- "aria-label": "Query",
483
- disabled: true
484
- }, /* @__PURE__ */ React.createElement(SearchIcon, null)));
485
- const endAdornment = /* @__PURE__ */ React.createElement(InputAdornment, {
486
- position: "end"
487
- }, /* @__PURE__ */ React.createElement(IconButton, {
488
- "aria-label": "Clear",
489
- onClick: handleClear
490
- }, /* @__PURE__ */ React.createElement(ClearButton, null)));
491
- const searchBar = /* @__PURE__ */ React.createElement(TrackSearch, null, /* @__PURE__ */ React.createElement(InputBase, {
492
- "data-testid": "search-bar-next",
493
- value,
494
- placeholder,
495
- startAdornment,
496
- endAdornment: clearButton ? endAdornment : defaultEndAdornment,
497
- inputProps: { "aria-label": "Search", ...defaultInputProps },
498
- fullWidth,
499
- onChange: handleChange,
500
- onKeyDown: handleKeyDown,
501
- ...props
502
- }));
503
- return hasSearchContext ? searchBar : /* @__PURE__ */ React.createElement(SearchContextProvider, null, searchBar);
504
- };
505
- const SearchBar = ({ onChange, ...props }) => {
506
- const { term, setTerm } = useSearch();
507
- const handleChange = useCallback(
508
- (newValue) => {
509
- if (onChange) {
510
- onChange(newValue);
511
- } else {
512
- setTerm(newValue);
513
- }
514
- },
515
- [onChange, setTerm]
516
- );
517
- return /* @__PURE__ */ React.createElement(AnalyticsContext, {
518
- attributes: { pluginId: "search", extension: "SearchBar" }
519
- }, /* @__PURE__ */ React.createElement(SearchBarBase, {
520
- value: term,
521
- onChange: handleChange,
522
- ...props
523
- }));
524
- };
525
-
526
698
  const DefaultResultListItemComponent = ({
527
699
  result,
528
700
  highlight,
@@ -578,5 +750,277 @@ const HigherOrderDefaultResultListItem = (props) => {
578
750
  }));
579
751
  };
580
752
 
581
- export { AutocompleteFilter, CheckboxFilter, HigherOrderDefaultResultListItem as DefaultResultListItem, HighlightedSearchResultText, MockSearchApi, SearchBar, SearchBarBase, SearchContextProvider, SearchFilter, HigherOrderSearchResult as SearchResult, SearchResultComponent, SearchResultPager, SelectFilter, searchApiRef, useSearch, useSearchContextCheck };
753
+ const SearchResultListLayout = (props) => {
754
+ const { loading, error, resultItems, renderResultItem, ...rest } = props;
755
+ return /* @__PURE__ */ React.createElement(List, {
756
+ ...rest
757
+ }, loading ? /* @__PURE__ */ React.createElement(Progress, null) : null, !loading && error ? /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
758
+ title: "Error encountered while fetching search results",
759
+ error
760
+ }) : null, !loading && !error && (resultItems == null ? void 0 : resultItems.length) ? resultItems.map((resultItem) => {
761
+ var _a;
762
+ return (_a = renderResultItem == null ? void 0 : renderResultItem(resultItem)) != null ? _a : null;
763
+ }) : null, !loading && !error && !(resultItems == null ? void 0 : resultItems.length) ? /* @__PURE__ */ React.createElement(EmptyState, {
764
+ missing: "data",
765
+ title: "Sorry, no results were found"
766
+ }) : null);
767
+ };
768
+ const SearchResultList = (props) => {
769
+ const {
770
+ query,
771
+ renderResultItem = ({ document }) => /* @__PURE__ */ React.createElement(HigherOrderDefaultResultListItem, {
772
+ key: document.location,
773
+ result: document
774
+ }),
775
+ ...rest
776
+ } = props;
777
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, {
778
+ attributes: {
779
+ pluginId: "search",
780
+ extension: "SearchResultList"
781
+ }
782
+ }, /* @__PURE__ */ React.createElement(SearchResultState, {
783
+ query
784
+ }, ({ loading, error, value }) => /* @__PURE__ */ React.createElement(SearchResultListLayout, {
785
+ ...rest,
786
+ loading,
787
+ error,
788
+ resultItems: value == null ? void 0 : value.results,
789
+ renderResultItem
790
+ })));
791
+ };
792
+
793
+ const useStyles = makeStyles((theme) => ({
794
+ listSubheader: {
795
+ display: "flex",
796
+ alignItems: "center"
797
+ },
798
+ listSubheaderName: {
799
+ marginLeft: theme.spacing(1),
800
+ textTransform: "uppercase"
801
+ },
802
+ listSubheaderChip: {
803
+ color: theme.palette.text.secondary,
804
+ margin: theme.spacing(0, 0, 0, 1.5)
805
+ },
806
+ listSubheaderFilter: {
807
+ display: "flex",
808
+ color: theme.palette.text.secondary,
809
+ margin: theme.spacing(0, 0, 0, 1.5)
810
+ },
811
+ listSubheaderLink: {
812
+ marginLeft: "auto",
813
+ display: "flex",
814
+ alignItems: "center"
815
+ },
816
+ listSubheaderLinkIcon: {
817
+ fontSize: "inherit",
818
+ marginLeft: theme.spacing(0.5)
819
+ }
820
+ }));
821
+ const SearchResultGroupFilterFieldLayout = (props) => {
822
+ const classes = useStyles();
823
+ const { label, children, ...rest } = props;
824
+ return /* @__PURE__ */ React.createElement(Chip, {
825
+ ...rest,
826
+ className: classes.listSubheaderFilter,
827
+ variant: "outlined",
828
+ label: /* @__PURE__ */ React.createElement(React.Fragment, null, label, ": ", children)
829
+ });
830
+ };
831
+ const NullIcon = () => null;
832
+ const useSearchResultGroupTextFilterStyles = makeStyles((theme) => ({
833
+ root: {
834
+ fontSize: "inherit",
835
+ "&:focus": {
836
+ outline: "none",
837
+ background: theme.palette.common.white
838
+ },
839
+ "&:not(:focus)": {
840
+ cursor: "pointer",
841
+ color: theme.palette.primary.main,
842
+ "&:hover": {
843
+ textDecoration: "underline"
844
+ }
845
+ }
846
+ }
847
+ }));
848
+ const SearchResultGroupTextFilterField = (props) => {
849
+ const classes = useSearchResultGroupTextFilterStyles();
850
+ const { label, value = "None", onChange, onDelete } = props;
851
+ const handleChange = useCallback(
852
+ (e) => {
853
+ onChange(e.target.value);
854
+ },
855
+ [onChange]
856
+ );
857
+ return /* @__PURE__ */ React.createElement(SearchResultGroupFilterFieldLayout, {
858
+ label,
859
+ onDelete
860
+ }, /* @__PURE__ */ React.createElement(Typography, {
861
+ role: "textbox",
862
+ component: "span",
863
+ className: classes.root,
864
+ onChange: handleChange,
865
+ contentEditable: true,
866
+ suppressContentEditableWarning: true
867
+ }, value));
868
+ };
869
+ const useSearchResultGroupSelectFilterStyles = makeStyles((theme) => ({
870
+ root: {
871
+ fontSize: "inherit",
872
+ "&:not(:focus)": {
873
+ cursor: "pointer",
874
+ color: theme.palette.primary.main,
875
+ "&:hover": {
876
+ textDecoration: "underline"
877
+ }
878
+ },
879
+ "&:focus": {
880
+ outline: "none"
881
+ },
882
+ "&>div:first-child": {
883
+ padding: 0
884
+ }
885
+ }
886
+ }));
887
+ const SearchResultGroupSelectFilterField = (props) => {
888
+ const classes = useSearchResultGroupSelectFilterStyles();
889
+ const { label, value = "none", onChange, onDelete, children } = props;
890
+ const handleChange = useCallback(
891
+ (e) => {
892
+ onChange(e.target.value);
893
+ },
894
+ [onChange]
895
+ );
896
+ return /* @__PURE__ */ React.createElement(SearchResultGroupFilterFieldLayout, {
897
+ label,
898
+ onDelete
899
+ }, /* @__PURE__ */ React.createElement(Select, {
900
+ className: classes.root,
901
+ value,
902
+ onChange: handleChange,
903
+ input: /* @__PURE__ */ React.createElement(InputBase, null),
904
+ IconComponent: NullIcon
905
+ }, /* @__PURE__ */ React.createElement(MenuItem, {
906
+ value: "none"
907
+ }, "None"), children));
908
+ };
909
+ function SearchResultGroupLayout(props) {
910
+ const classes = useStyles();
911
+ const [anchorEl, setAnchorEl] = useState(null);
912
+ const {
913
+ loading,
914
+ error,
915
+ icon,
916
+ title,
917
+ titleProps = {},
918
+ link,
919
+ linkProps = {},
920
+ filterOptions,
921
+ renderFilterOption,
922
+ filterFields,
923
+ renderFilterField,
924
+ resultItems,
925
+ renderResultItem,
926
+ ...rest
927
+ } = props;
928
+ const handleClick = useCallback((e) => {
929
+ setAnchorEl(e.currentTarget);
930
+ }, []);
931
+ const handleClose = useCallback(() => {
932
+ setAnchorEl(null);
933
+ }, []);
934
+ return /* @__PURE__ */ React.createElement(List, {
935
+ ...rest
936
+ }, /* @__PURE__ */ React.createElement(ListSubheader, {
937
+ className: classes.listSubheader
938
+ }, icon, /* @__PURE__ */ React.createElement(Typography, {
939
+ className: classes.listSubheaderName,
940
+ component: "strong",
941
+ ...titleProps
942
+ }, title), filterOptions ? /* @__PURE__ */ React.createElement(Chip, {
943
+ className: classes.listSubheaderChip,
944
+ component: "button",
945
+ icon: /* @__PURE__ */ React.createElement(AddIcon, null),
946
+ variant: "outlined",
947
+ label: "Add filter",
948
+ "aria-controls": "filters-menu",
949
+ "aria-haspopup": "true",
950
+ onClick: handleClick
951
+ }) : null, filterOptions ? /* @__PURE__ */ React.createElement(Menu, {
952
+ id: "filters-menu",
953
+ anchorEl,
954
+ open: Boolean(anchorEl),
955
+ onClose: handleClose,
956
+ onClick: handleClose,
957
+ keepMounted: true
958
+ }, filterOptions.map(
959
+ (filterOption) => renderFilterOption ? renderFilterOption(filterOption) : /* @__PURE__ */ React.createElement(MenuItem, {
960
+ key: String(filterOption),
961
+ value: String(filterOption)
962
+ }, filterOption)
963
+ )) : null, filterFields == null ? void 0 : filterFields.map(
964
+ (filterField) => {
965
+ var _a;
966
+ return (_a = renderFilterField == null ? void 0 : renderFilterField(filterField)) != null ? _a : null;
967
+ }
968
+ ), /* @__PURE__ */ React.createElement(Link, {
969
+ className: classes.listSubheaderLink,
970
+ to: "/search",
971
+ ...linkProps
972
+ }, link != null ? link : /* @__PURE__ */ React.createElement(React.Fragment, null, "See all", /* @__PURE__ */ React.createElement(ArrowForwardIosIcon, {
973
+ className: classes.listSubheaderLinkIcon
974
+ })))), loading ? /* @__PURE__ */ React.createElement(Progress, null) : null, !loading && error ? /* @__PURE__ */ React.createElement(ResponseErrorPanel, {
975
+ title: "Error encountered while fetching search results",
976
+ error
977
+ }) : null, !loading && !error && (resultItems == null ? void 0 : resultItems.length) ? resultItems.map((resultItem) => {
978
+ var _a;
979
+ return (_a = renderResultItem == null ? void 0 : renderResultItem(resultItem)) != null ? _a : null;
980
+ }) : null, !loading && !error && !(resultItems == null ? void 0 : resultItems.length) ? /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(EmptyState, {
981
+ missing: "data",
982
+ title: "Sorry, no results were found"
983
+ })) : null);
984
+ }
985
+ function SearchResultGroup(props) {
986
+ const {
987
+ query,
988
+ linkProps = {},
989
+ renderResultItem = ({ document }) => /* @__PURE__ */ React.createElement(HigherOrderDefaultResultListItem, {
990
+ key: document.location,
991
+ result: document
992
+ }),
993
+ ...rest
994
+ } = props;
995
+ const to = `/search?${qs.stringify(
996
+ {
997
+ query: query.term,
998
+ types: query.types,
999
+ filters: query.filters,
1000
+ pageCursor: query.pageCursor
1001
+ },
1002
+ { arrayFormat: "brackets" }
1003
+ )}`;
1004
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, {
1005
+ attributes: {
1006
+ pluginId: "search",
1007
+ extension: "SearchResultGroup"
1008
+ }
1009
+ }, /* @__PURE__ */ React.createElement(SearchResultState, {
1010
+ query
1011
+ }, ({ loading, error, value }) => {
1012
+ var _a;
1013
+ return /* @__PURE__ */ React.createElement(SearchResultGroupLayout, {
1014
+ ...rest,
1015
+ loading,
1016
+ error,
1017
+ linkProps: { to, ...linkProps },
1018
+ resultItems: value == null ? void 0 : value.results,
1019
+ renderResultItem,
1020
+ filterFields: Object.keys((_a = query.filters) != null ? _a : {})
1021
+ });
1022
+ }));
1023
+ }
1024
+
1025
+ export { AutocompleteFilter, CheckboxFilter, HigherOrderDefaultResultListItem as DefaultResultListItem, HighlightedSearchResultText, MockSearchApi, SearchAutocomplete, SearchAutocompleteDefaultOption, SearchBar, SearchBarBase, SearchContextProvider, SearchFilter, SearchResult, SearchResultApi, SearchResultComponent, SearchResultContext, SearchResultGroup, SearchResultGroupFilterFieldLayout, SearchResultGroupLayout, SearchResultGroupSelectFilterField, SearchResultGroupTextFilterField, SearchResultList, SearchResultListLayout, SearchResultPager, SearchResultState, SelectFilter, searchApiRef, useSearch, useSearchContextCheck };
582
1026
  //# sourceMappingURL=index.esm.js.map