@backstage/plugin-search-react 1.9.0-next.1 → 1.9.0-next.2
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/CHANGELOG.md +15 -0
- package/dist/alpha.d.ts +18 -1
- package/dist/alpha.esm.js +1 -0
- package/dist/alpha.esm.js.map +1 -1
- package/dist/components/SearchBar/SearchBar.esm.js +7 -2
- package/dist/components/SearchBar/SearchBar.esm.js.map +1 -1
- package/dist/components/SearchFilter/SearchFilter.Autocomplete.esm.js +5 -2
- package/dist/components/SearchFilter/SearchFilter.Autocomplete.esm.js.map +1 -1
- package/dist/components/SearchFilter/SearchFilter.esm.js +7 -1
- package/dist/components/SearchFilter/SearchFilter.esm.js.map +1 -1
- package/dist/components/SearchPagination/SearchPagination.esm.js +5 -2
- package/dist/components/SearchPagination/SearchPagination.esm.js.map +1 -1
- package/dist/components/SearchResult/SearchResult.esm.js +5 -2
- package/dist/components/SearchResult/SearchResult.esm.js.map +1 -1
- package/dist/components/SearchResultGroup/SearchResultGroup.esm.js +6 -3
- package/dist/components/SearchResultGroup/SearchResultGroup.esm.js.map +1 -1
- package/dist/components/SearchResultList/SearchResultList.esm.js +5 -2
- package/dist/components/SearchResultList/SearchResultList.esm.js.map +1 -1
- package/dist/components/SearchResultPager/SearchResultPager.esm.js +5 -2
- package/dist/components/SearchResultPager/SearchResultPager.esm.js.map +1 -1
- package/dist/translation.esm.js +30 -0
- package/dist/translation.esm.js.map +1 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @backstage/plugin-search-react
|
|
2
2
|
|
|
3
|
+
## 1.9.0-next.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2c76614: Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets
|
|
8
|
+
- fa48594: search plugin support i18n
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/core-components@0.17.2-next.1
|
|
11
|
+
- @backstage/core-plugin-api@1.10.7-next.0
|
|
12
|
+
- @backstage/frontend-plugin-api@0.10.2-next.1
|
|
13
|
+
- @backstage/theme@0.6.6-next.0
|
|
14
|
+
- @backstage/types@1.2.1
|
|
15
|
+
- @backstage/version-bridge@1.0.11
|
|
16
|
+
- @backstage/plugin-search-common@1.2.18-next.0
|
|
17
|
+
|
|
3
18
|
## 1.9.0-next.1
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/alpha.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api';
|
|
2
2
|
import { ListItemProps } from '@material-ui/core/ListItem';
|
|
3
3
|
import { SearchDocument, SearchResult } from '@backstage/plugin-search-common';
|
|
4
|
+
import * as _backstage_core_plugin_api_alpha from '@backstage/core-plugin-api/alpha';
|
|
4
5
|
|
|
5
6
|
/** @alpha */
|
|
6
7
|
type BaseSearchResultListItemProps<T = {}> = T & {
|
|
@@ -124,4 +125,20 @@ declare const SearchFilterBlueprint: _backstage_frontend_plugin_api.ExtensionBlu
|
|
|
124
125
|
};
|
|
125
126
|
}>;
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
/**
|
|
129
|
+
* @alpha
|
|
130
|
+
*/
|
|
131
|
+
declare const searchReactTranslationRef: _backstage_core_plugin_api_alpha.TranslationRef<"search-react", {
|
|
132
|
+
readonly "searchBar.title": "Search";
|
|
133
|
+
readonly "searchBar.placeholder": "Search in {{org}}";
|
|
134
|
+
readonly "searchFilter.allOptionTitle": "All";
|
|
135
|
+
readonly "searchPagination.limitLabel": "Results per page:";
|
|
136
|
+
readonly "searchPagination.limitText": "of {{num}}";
|
|
137
|
+
readonly noResultsDescription: "Sorry, no results were found";
|
|
138
|
+
readonly "searchResultGroup.linkTitle": "See All";
|
|
139
|
+
readonly "searchResultGroup.addFilterButtonTitle": "Add filter";
|
|
140
|
+
readonly "searchResultPager.next": "Next";
|
|
141
|
+
readonly "searchResultPager.previous": "Previous";
|
|
142
|
+
}>;
|
|
143
|
+
|
|
144
|
+
export { type BaseSearchResultListItemProps, SearchFilterBlueprint, type SearchFilterBlueprintParams, type SearchFilterExtensionComponent, type SearchFilterExtensionComponentProps, SearchFilterResultTypeBlueprint, type SearchFilterResultTypeBlueprintParams, type SearchResultItemExtensionComponent, type SearchResultItemExtensionPredicate, SearchResultListItemBlueprint, type SearchResultListItemBlueprintParams, searchReactTranslationRef };
|
package/dist/alpha.esm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SearchResultListItemBlueprint } from './alpha/blueprints/SearchResultListItemBlueprint.esm.js';
|
|
2
2
|
export { SearchFilterResultTypeBlueprint } from './alpha/blueprints/SearchFilterResultTypeBlueprint.esm.js';
|
|
3
3
|
export { SearchFilterBlueprint } from './alpha/blueprints/SearchFilterBlueprint.esm.js';
|
|
4
|
+
export { searchReactTranslationRef } from './translation.esm.js';
|
|
4
5
|
//# sourceMappingURL=alpha.esm.js.map
|
package/dist/alpha.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alpha.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"alpha.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
|
@@ -8,6 +8,8 @@ import DefaultSearchIcon from '@material-ui/icons/Search';
|
|
|
8
8
|
import { forwardRef, useState, useRef, useEffect, useCallback } from 'react';
|
|
9
9
|
import useDebounce from 'react-use/esm/useDebounce';
|
|
10
10
|
import { SearchContextProvider, useSearch } from '../../context/SearchContext.esm.js';
|
|
11
|
+
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
|
12
|
+
import { searchReactTranslationRef } from '../../translation.esm.js';
|
|
11
13
|
|
|
12
14
|
const SearchBarBase = forwardRef((props, ref) => {
|
|
13
15
|
const {
|
|
@@ -32,6 +34,7 @@ const SearchBarBase = forwardRef((props, ref) => {
|
|
|
32
34
|
const configApi = useApi(configApiRef);
|
|
33
35
|
const [value, setValue] = useState("");
|
|
34
36
|
const forwardedValueRef = useRef("");
|
|
37
|
+
const { t } = useTranslationRef(searchReactTranslationRef);
|
|
35
38
|
useEffect(() => {
|
|
36
39
|
setValue((prevValue) => {
|
|
37
40
|
if (prevValue === forwardedValueRef.current) {
|
|
@@ -71,8 +74,10 @@ const SearchBarBase = forwardRef((props, ref) => {
|
|
|
71
74
|
onClear();
|
|
72
75
|
}
|
|
73
76
|
}, [onChange, onClear]);
|
|
74
|
-
const ariaLabel = label ? void 0 : "
|
|
75
|
-
const inputPlaceholder = placeholder ??
|
|
77
|
+
const ariaLabel = label ? void 0 : t("searchBar.title");
|
|
78
|
+
const inputPlaceholder = placeholder ?? t("searchBar.placeholder", {
|
|
79
|
+
org: configApi.getOptionalString("app.title") || "Backstage"
|
|
80
|
+
});
|
|
76
81
|
const SearchIcon = useApp().getSystemIcon("search") || DefaultSearchIcon;
|
|
77
82
|
const startAdornment = /* @__PURE__ */ jsx(InputAdornment, { position: "start", children: /* @__PURE__ */ jsx(IconButton, { "aria-label": "Query", size: "small", disabled: true, children: /* @__PURE__ */ jsx(SearchIcon, {}) }) });
|
|
78
83
|
const clearButtonEndAdornment = /* @__PURE__ */ jsx(InputAdornment, { position: "end", children: /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchBar.esm.js","sources":["../../../src/components/SearchBar/SearchBar.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AnalyticsContext,\n configApiRef,\n useApi,\n useApp,\n} from '@backstage/core-plugin-api';\nimport IconButton from '@material-ui/core/IconButton';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport TextField from '@material-ui/core/TextField';\nimport Button from '@material-ui/core/Button';\nimport { TextFieldProps } from '@material-ui/core/TextField';\nimport DefaultSearchIcon from '@material-ui/icons/Search';\nimport {\n ReactNode,\n ChangeEvent,\n forwardRef,\n KeyboardEvent,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport useDebounce from 'react-use/esm/useDebounce';\nimport { SearchContextProvider, useSearch } from '../../context';\n\n/**\n * Props for {@link SearchBarBase}.\n *\n * @public\n */\nexport type SearchBarBaseProps = Omit<TextFieldProps, 'onChange'> & {\n debounceTime?: number;\n clearButton?: boolean;\n onClear?: () => void;\n onSubmit?: () => void;\n onChange: (value: string) => void;\n endAdornment?: ReactNode;\n};\n\n/**\n * All search boxes exported by the search plugin are based on the <SearchBarBase />,\n * and this one is based on the <InputBase /> component from Material UI.\n * Recommended if you don't use Search Provider or Search Context.\n *\n * @public\n */\nexport const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => {\n const {\n onChange,\n onKeyDown = () => {},\n onClear = () => {},\n onSubmit = () => {},\n debounceTime = 200,\n clearButton = true,\n fullWidth = true,\n value: defaultValue,\n label,\n placeholder,\n inputProps = {},\n InputProps = {},\n endAdornment,\n ...rest\n } = props;\n\n const configApi = useApi(configApiRef);\n const [value, setValue] = useState<string>('');\n const forwardedValueRef = useRef<string>('');\n\n useEffect(() => {\n setValue(prevValue => {\n // We only update the value if our current value is the same as it was\n // for the most recent onChange call. Otherwise it means that the users\n // has continued typing and we should not replace their input.\n if (prevValue === forwardedValueRef.current) {\n return String(defaultValue);\n }\n return prevValue;\n });\n }, [defaultValue, forwardedValueRef]);\n\n useDebounce(\n () => {\n forwardedValueRef.current = value;\n onChange(value);\n },\n debounceTime,\n [value],\n );\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n setValue(e.target.value);\n },\n [setValue],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (onKeyDown) onKeyDown(e);\n if (onSubmit && e.key === 'Enter') {\n onSubmit();\n }\n },\n [onKeyDown, onSubmit],\n );\n\n const handleClear = useCallback(() => {\n forwardedValueRef.current = '';\n onChange('');\n setValue('');\n if (onClear) {\n onClear();\n }\n }, [onChange, onClear]);\n\n const ariaLabel: string | undefined = label ? undefined : 'Search';\n\n const inputPlaceholder =\n placeholder ??\n `Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`;\n\n const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon;\n\n const startAdornment = (\n <InputAdornment position=\"start\">\n <IconButton aria-label=\"Query\" size=\"small\" disabled>\n <SearchIcon />\n </IconButton>\n </InputAdornment>\n );\n\n const clearButtonEndAdornment = (\n <InputAdornment position=\"end\">\n <Button\n aria-label=\"Clear\"\n size=\"small\"\n onClick={handleClear}\n onKeyDown={event => {\n if (event.key === 'Enter') {\n // write your functionality here\n event.stopPropagation();\n }\n }}\n >\n Clear\n </Button>\n </InputAdornment>\n );\n\n return (\n <SearchContextProvider inheritParentContextIfAvailable>\n <TextField\n id=\"search-bar-text-field\"\n data-testid=\"search-bar-next\"\n variant=\"outlined\"\n margin=\"normal\"\n inputRef={ref}\n value={value}\n label={label}\n placeholder={inputPlaceholder}\n InputProps={{\n startAdornment,\n endAdornment: clearButton ? clearButtonEndAdornment : endAdornment,\n ...InputProps,\n }}\n inputProps={{\n 'aria-label': ariaLabel,\n ...inputProps,\n }}\n fullWidth={fullWidth}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n {...rest}\n />\n </SearchContextProvider>\n );\n});\n\n/**\n * Props for {@link SearchBar}.\n *\n * @public\n */\nexport type SearchBarProps = Partial<SearchBarBaseProps>;\n\n/**\n * Recommended search bar when you use the Search Provider or Search Context.\n *\n * @public\n */\nexport const SearchBar = forwardRef((props: SearchBarProps, ref) => {\n const { value: initialValue = '', onChange, ...rest } = props;\n\n const { term, setTerm } = useSearch();\n\n useEffect(() => {\n if (initialValue) {\n setTerm(String(initialValue));\n }\n }, [initialValue, setTerm]);\n\n const handleChange = useCallback(\n (newValue: string) => {\n if (onChange) {\n onChange(newValue);\n } else {\n setTerm(newValue);\n }\n },\n [onChange, setTerm],\n );\n\n return (\n <SearchContextProvider inheritParentContextIfAvailable>\n <AnalyticsContext\n attributes={{ pluginId: 'search', extension: 'SearchBar' }}\n >\n <SearchBarBase\n {...rest}\n ref={ref}\n value={term}\n onChange={handleChange}\n />\n </AnalyticsContext>\n </SearchContextProvider>\n );\n});\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SearchBar.esm.js","sources":["../../../src/components/SearchBar/SearchBar.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AnalyticsContext,\n configApiRef,\n useApi,\n useApp,\n} from '@backstage/core-plugin-api';\nimport IconButton from '@material-ui/core/IconButton';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport TextField from '@material-ui/core/TextField';\nimport Button from '@material-ui/core/Button';\nimport { TextFieldProps } from '@material-ui/core/TextField';\nimport DefaultSearchIcon from '@material-ui/icons/Search';\nimport {\n ReactNode,\n ChangeEvent,\n forwardRef,\n KeyboardEvent,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport useDebounce from 'react-use/esm/useDebounce';\nimport { SearchContextProvider, useSearch } from '../../context';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\nimport { searchReactTranslationRef } from '../../translation';\n\n/**\n * Props for {@link SearchBarBase}.\n *\n * @public\n */\nexport type SearchBarBaseProps = Omit<TextFieldProps, 'onChange'> & {\n debounceTime?: number;\n clearButton?: boolean;\n onClear?: () => void;\n onSubmit?: () => void;\n onChange: (value: string) => void;\n endAdornment?: ReactNode;\n};\n\n/**\n * All search boxes exported by the search plugin are based on the <SearchBarBase />,\n * and this one is based on the <InputBase /> component from Material UI.\n * Recommended if you don't use Search Provider or Search Context.\n *\n * @public\n */\nexport const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => {\n const {\n onChange,\n onKeyDown = () => {},\n onClear = () => {},\n onSubmit = () => {},\n debounceTime = 200,\n clearButton = true,\n fullWidth = true,\n value: defaultValue,\n label,\n placeholder,\n inputProps = {},\n InputProps = {},\n endAdornment,\n ...rest\n } = props;\n\n const configApi = useApi(configApiRef);\n const [value, setValue] = useState<string>('');\n const forwardedValueRef = useRef<string>('');\n const { t } = useTranslationRef(searchReactTranslationRef);\n\n useEffect(() => {\n setValue(prevValue => {\n // We only update the value if our current value is the same as it was\n // for the most recent onChange call. Otherwise it means that the users\n // has continued typing and we should not replace their input.\n if (prevValue === forwardedValueRef.current) {\n return String(defaultValue);\n }\n return prevValue;\n });\n }, [defaultValue, forwardedValueRef]);\n\n useDebounce(\n () => {\n forwardedValueRef.current = value;\n onChange(value);\n },\n debounceTime,\n [value],\n );\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n setValue(e.target.value);\n },\n [setValue],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (onKeyDown) onKeyDown(e);\n if (onSubmit && e.key === 'Enter') {\n onSubmit();\n }\n },\n [onKeyDown, onSubmit],\n );\n\n const handleClear = useCallback(() => {\n forwardedValueRef.current = '';\n onChange('');\n setValue('');\n if (onClear) {\n onClear();\n }\n }, [onChange, onClear]);\n\n const ariaLabel: string | undefined = label\n ? undefined\n : t('searchBar.title');\n\n const inputPlaceholder =\n placeholder ??\n t('searchBar.placeholder', {\n org: configApi.getOptionalString('app.title') || 'Backstage',\n });\n const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon;\n\n const startAdornment = (\n <InputAdornment position=\"start\">\n <IconButton aria-label=\"Query\" size=\"small\" disabled>\n <SearchIcon />\n </IconButton>\n </InputAdornment>\n );\n\n const clearButtonEndAdornment = (\n <InputAdornment position=\"end\">\n <Button\n aria-label=\"Clear\"\n size=\"small\"\n onClick={handleClear}\n onKeyDown={event => {\n if (event.key === 'Enter') {\n // write your functionality here\n event.stopPropagation();\n }\n }}\n >\n Clear\n </Button>\n </InputAdornment>\n );\n\n return (\n <SearchContextProvider inheritParentContextIfAvailable>\n <TextField\n id=\"search-bar-text-field\"\n data-testid=\"search-bar-next\"\n variant=\"outlined\"\n margin=\"normal\"\n inputRef={ref}\n value={value}\n label={label}\n placeholder={inputPlaceholder}\n InputProps={{\n startAdornment,\n endAdornment: clearButton ? clearButtonEndAdornment : endAdornment,\n ...InputProps,\n }}\n inputProps={{\n 'aria-label': ariaLabel,\n ...inputProps,\n }}\n fullWidth={fullWidth}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n {...rest}\n />\n </SearchContextProvider>\n );\n});\n\n/**\n * Props for {@link SearchBar}.\n *\n * @public\n */\nexport type SearchBarProps = Partial<SearchBarBaseProps>;\n\n/**\n * Recommended search bar when you use the Search Provider or Search Context.\n *\n * @public\n */\nexport const SearchBar = forwardRef((props: SearchBarProps, ref) => {\n const { value: initialValue = '', onChange, ...rest } = props;\n\n const { term, setTerm } = useSearch();\n\n useEffect(() => {\n if (initialValue) {\n setTerm(String(initialValue));\n }\n }, [initialValue, setTerm]);\n\n const handleChange = useCallback(\n (newValue: string) => {\n if (onChange) {\n onChange(newValue);\n } else {\n setTerm(newValue);\n }\n },\n [onChange, setTerm],\n );\n\n return (\n <SearchContextProvider inheritParentContextIfAvailable>\n <AnalyticsContext\n attributes={{ pluginId: 'search', extension: 'SearchBar' }}\n >\n <SearchBarBase\n {...rest}\n ref={ref}\n value={term}\n onChange={handleChange}\n />\n </AnalyticsContext>\n </SearchContextProvider>\n );\n});\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useState, useMemo } from 'react';
|
|
3
3
|
import Chip from '@material-ui/core/Chip';
|
|
4
4
|
import TextField from '@material-ui/core/TextField';
|
|
5
5
|
import Autocomplete from '@material-ui/lab/Autocomplete';
|
|
@@ -33,7 +33,10 @@ const AutocompleteFilter = (props) => {
|
|
|
33
33
|
const filterValueWithLabel = ensureFilterValueWithLabel(
|
|
34
34
|
filters[name]
|
|
35
35
|
);
|
|
36
|
-
const filterValue =
|
|
36
|
+
const filterValue = useMemo(
|
|
37
|
+
() => filterValueWithLabel || (multiple ? [] : null),
|
|
38
|
+
[filterValueWithLabel, multiple]
|
|
39
|
+
);
|
|
37
40
|
const handleChange = (_, newValue) => {
|
|
38
41
|
setFilters((prevState) => {
|
|
39
42
|
const { [name]: filter, ...others } = prevState;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchFilter.Autocomplete.esm.js","sources":["../../../src/components/SearchFilter/SearchFilter.Autocomplete.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ChangeEvent, useState } from 'react';\nimport Chip from '@material-ui/core/Chip';\nimport TextField from '@material-ui/core/TextField';\nimport Autocomplete, {\n AutocompleteGetTagProps,\n AutocompleteRenderInputParams,\n} from '@material-ui/lab/Autocomplete';\n\nimport { useSearch } from '../../context';\nimport { useAsyncFilterValues, useDefaultFilterValue } from './hooks';\nimport { SearchFilterComponentProps } from './SearchFilter';\nimport { ensureFilterValueWithLabel, FilterValueWithLabel } from './types';\n\n/**\n * @public\n */\nexport type SearchAutocompleteFilterProps = SearchFilterComponentProps & {\n filterSelectedOptions?: boolean;\n limitTags?: number;\n multiple?: boolean;\n};\n\n/**\n * @public\n */\nexport const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => {\n const {\n className,\n defaultValue,\n name,\n values: givenValues,\n valuesDebounceMs,\n label,\n filterSelectedOptions,\n limitTags,\n multiple,\n } = props;\n const [inputValue, setInputValue] = useState<string>('');\n useDefaultFilterValue(name, defaultValue);\n const asyncValues =\n typeof givenValues === 'function' ? givenValues : undefined;\n const defaultValues =\n typeof givenValues === 'function'\n ? undefined\n : givenValues?.map(v => ensureFilterValueWithLabel(v));\n const { value: values, loading } = useAsyncFilterValues(\n asyncValues,\n inputValue,\n defaultValues,\n valuesDebounceMs,\n );\n const { filters, setFilters } = useSearch();\n const filterValueWithLabel = ensureFilterValueWithLabel(\n filters[name] as string | string[] | undefined,\n );\n const filterValue = filterValueWithLabel || (multiple ? [] : null);\n\n // Set new filter values on input change.\n const handleChange = (\n _: ChangeEvent<{}>,\n newValue: FilterValueWithLabel | FilterValueWithLabel[] | null,\n ) => {\n setFilters(prevState => {\n const { [name]: filter, ...others } = prevState;\n\n if (newValue) {\n return {\n ...others,\n [name]: Array.isArray(newValue)\n ? newValue.map(v => v.value)\n : newValue.value,\n };\n }\n return { ...others };\n });\n };\n\n // Provide the input field.\n const renderInput = (params: AutocompleteRenderInputParams) => (\n <TextField\n {...params}\n name=\"search\"\n variant=\"outlined\"\n label={label}\n fullWidth\n />\n );\n\n // Render tags as primary-colored chips.\n const renderTags = (\n tagValue: FilterValueWithLabel[],\n getTagProps: AutocompleteGetTagProps,\n ) =>\n tagValue.map((option, index: number) => (\n <Chip label={option.label} color=\"primary\" {...getTagProps({ index })} />\n ));\n\n return (\n <Autocomplete\n filterSelectedOptions={filterSelectedOptions}\n limitTags={limitTags}\n multiple={multiple}\n className={className}\n id={`${multiple ? 'multi-' : ''}select-filter-${name}--select`}\n options={values || []}\n loading={loading}\n value={filterValue}\n onChange={handleChange}\n onInputChange={(_, newValue) => setInputValue(newValue)}\n getOptionLabel={option => option.label}\n renderInput={renderInput}\n renderTags={renderTags}\n />\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAyCa,MAAA,kBAAA,GAAqB,CAAC,KAAyC,KAAA;AAC1E,EAAM,MAAA;AAAA,IACJ,SAAA;AAAA,IACA,YAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAQ,EAAA,WAAA;AAAA,IACR,gBAAA;AAAA,IACA,KAAA;AAAA,IACA,qBAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACE,GAAA,KAAA;AACJ,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAiB,EAAE,CAAA;AACvD,EAAA,qBAAA,CAAsB,MAAM,YAAY,CAAA;AACxC,EAAA,MAAM,WACJ,GAAA,OAAO,WAAgB,KAAA,UAAA,GAAa,WAAc,GAAA,KAAA,CAAA;AACpD,EAAM,MAAA,aAAA,GACJ,OAAO,WAAA,KAAgB,UACnB,GAAA,KAAA,CAAA,GACA,aAAa,GAAI,CAAA,CAAA,CAAA,KAAK,0BAA2B,CAAA,CAAC,CAAC,CAAA;AACzD,EAAA,MAAM,EAAE,KAAA,EAAO,MAAQ,EAAA,OAAA,EAAY,GAAA,oBAAA;AAAA,IACjC,WAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,MAAM,EAAE,OAAA,EAAS,UAAW,EAAA,GAAI,SAAU,EAAA;AAC1C,EAAA,MAAM,oBAAuB,GAAA,0BAAA;AAAA,IAC3B,QAAQ,IAAI;AAAA,GACd;AACA,EAAA,MAAM,WAAc,GAAA,oBAAA,KAAyB,QAAW,GAAA,EAAK,GAAA,IAAA,CAAA;
|
|
1
|
+
{"version":3,"file":"SearchFilter.Autocomplete.esm.js","sources":["../../../src/components/SearchFilter/SearchFilter.Autocomplete.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ChangeEvent, useState, useMemo } from 'react';\nimport Chip from '@material-ui/core/Chip';\nimport TextField from '@material-ui/core/TextField';\nimport Autocomplete, {\n AutocompleteGetTagProps,\n AutocompleteRenderInputParams,\n} from '@material-ui/lab/Autocomplete';\n\nimport { useSearch } from '../../context';\nimport { useAsyncFilterValues, useDefaultFilterValue } from './hooks';\nimport { SearchFilterComponentProps } from './SearchFilter';\nimport { ensureFilterValueWithLabel, FilterValueWithLabel } from './types';\n\n/**\n * @public\n */\nexport type SearchAutocompleteFilterProps = SearchFilterComponentProps & {\n filterSelectedOptions?: boolean;\n limitTags?: number;\n multiple?: boolean;\n};\n\n/**\n * @public\n */\nexport const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => {\n const {\n className,\n defaultValue,\n name,\n values: givenValues,\n valuesDebounceMs,\n label,\n filterSelectedOptions,\n limitTags,\n multiple,\n } = props;\n const [inputValue, setInputValue] = useState<string>('');\n useDefaultFilterValue(name, defaultValue);\n const asyncValues =\n typeof givenValues === 'function' ? givenValues : undefined;\n const defaultValues =\n typeof givenValues === 'function'\n ? undefined\n : givenValues?.map(v => ensureFilterValueWithLabel(v));\n const { value: values, loading } = useAsyncFilterValues(\n asyncValues,\n inputValue,\n defaultValues,\n valuesDebounceMs,\n );\n const { filters, setFilters } = useSearch();\n const filterValueWithLabel = ensureFilterValueWithLabel(\n filters[name] as string | string[] | undefined,\n );\n const filterValue = useMemo(\n () => filterValueWithLabel || (multiple ? [] : null),\n [filterValueWithLabel, multiple],\n );\n\n // Set new filter values on input change.\n const handleChange = (\n _: ChangeEvent<{}>,\n newValue: FilterValueWithLabel | FilterValueWithLabel[] | null,\n ) => {\n setFilters(prevState => {\n const { [name]: filter, ...others } = prevState;\n\n if (newValue) {\n return {\n ...others,\n [name]: Array.isArray(newValue)\n ? newValue.map(v => v.value)\n : newValue.value,\n };\n }\n return { ...others };\n });\n };\n\n // Provide the input field.\n const renderInput = (params: AutocompleteRenderInputParams) => (\n <TextField\n {...params}\n name=\"search\"\n variant=\"outlined\"\n label={label}\n fullWidth\n />\n );\n\n // Render tags as primary-colored chips.\n const renderTags = (\n tagValue: FilterValueWithLabel[],\n getTagProps: AutocompleteGetTagProps,\n ) =>\n tagValue.map((option, index: number) => (\n <Chip label={option.label} color=\"primary\" {...getTagProps({ index })} />\n ));\n\n return (\n <Autocomplete\n filterSelectedOptions={filterSelectedOptions}\n limitTags={limitTags}\n multiple={multiple}\n className={className}\n id={`${multiple ? 'multi-' : ''}select-filter-${name}--select`}\n options={values || []}\n loading={loading}\n value={filterValue}\n onChange={handleChange}\n onInputChange={(_, newValue) => setInputValue(newValue)}\n getOptionLabel={option => option.label}\n renderInput={renderInput}\n renderTags={renderTags}\n />\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,8 @@ import { useSearch } from '../../context/SearchContext.esm.js';
|
|
|
12
12
|
import { AutocompleteFilter } from './SearchFilter.Autocomplete.esm.js';
|
|
13
13
|
import { useDefaultFilterValue, useAsyncFilterValues } from './hooks.esm.js';
|
|
14
14
|
import { ensureFilterValueWithLabel } from './types.esm.js';
|
|
15
|
+
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
|
16
|
+
import { searchReactTranslationRef } from '../../translation.esm.js';
|
|
15
17
|
|
|
16
18
|
const useStyles = makeStyles({
|
|
17
19
|
label: {
|
|
@@ -103,6 +105,7 @@ const SelectFilter = (props) => {
|
|
|
103
105
|
values: givenValues,
|
|
104
106
|
valuesDebounceMs
|
|
105
107
|
} = props;
|
|
108
|
+
const { t } = useTranslationRef(searchReactTranslationRef);
|
|
106
109
|
useDefaultFilterValue(name, defaultValue);
|
|
107
110
|
const asyncValues = typeof givenValues === "function" ? givenValues : void 0;
|
|
108
111
|
const defaultValues = typeof givenValues === "function" ? void 0 : givenValues?.map((v) => ensureFilterValueWithLabel(v));
|
|
@@ -113,7 +116,10 @@ const SelectFilter = (props) => {
|
|
|
113
116
|
valuesDebounceMs
|
|
114
117
|
);
|
|
115
118
|
const allOptionValue = useRef(v4());
|
|
116
|
-
const allOption = {
|
|
119
|
+
const allOption = {
|
|
120
|
+
value: allOptionValue.current,
|
|
121
|
+
label: t("searchFilter.allOptionTitle")
|
|
122
|
+
};
|
|
117
123
|
const { filters, setFilters } = useSearch();
|
|
118
124
|
const handleChange = (value) => {
|
|
119
125
|
setFilters((prevFilters) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchFilter.esm.js","sources":["../../../src/components/SearchFilter/SearchFilter.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactElement, ChangeEvent, useRef } from 'react';\nimport { capitalize } from 'lodash';\nimport { v4 as uuid } from 'uuid';\nimport FormControl from '@material-ui/core/FormControl';\nimport FormControlLabel from '@material-ui/core/FormControlLabel';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport FormLabel from '@material-ui/core/FormLabel';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { Select, SelectedItems } from '@backstage/core-components';\n\nimport { useSearch } from '../../context';\nimport {\n AutocompleteFilter,\n SearchAutocompleteFilterProps,\n} from './SearchFilter.Autocomplete';\nimport { useAsyncFilterValues, useDefaultFilterValue } from './hooks';\nimport { ensureFilterValueWithLabel, FilterValue } from './types';\n\nconst useStyles = makeStyles({\n label: {\n textTransform: 'capitalize',\n },\n checkboxWrapper: {\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n },\n textWrapper: {\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n});\n\n/**\n * @public\n */\nexport type SearchFilterComponentProps = {\n className?: string;\n name: string;\n label?: string;\n /**\n * Either an array of values directly, or an async function to return a list\n * of values to be used in the filter. In the autocomplete filter, the last\n * input value is provided as an input to allow values to be filtered. This\n * function is debounced and values cached.\n */\n values?: FilterValue[] | ((partial: string) => Promise<FilterValue[]>);\n defaultValue?: string[] | string | null;\n /**\n * Debounce time in milliseconds, used when values is an async callback.\n * Defaults to 250ms.\n */\n valuesDebounceMs?: number;\n};\n\n/**\n * @public\n */\nexport type SearchFilterWrapperProps = SearchFilterComponentProps & {\n component: (props: SearchFilterComponentProps) => ReactElement;\n debug?: boolean;\n};\n\n/**\n * @public\n */\nexport const CheckboxFilter = (props: SearchFilterComponentProps) => {\n const {\n className,\n defaultValue,\n label: formLabel,\n name,\n values: givenValues = [],\n valuesDebounceMs,\n } = props;\n const classes = useStyles();\n const { filters, setFilters } = useSearch();\n useDefaultFilterValue(name, defaultValue);\n const asyncValues =\n typeof givenValues === 'function' ? givenValues : undefined;\n const defaultValues =\n typeof givenValues === 'function'\n ? undefined\n : givenValues.map(v => ensureFilterValueWithLabel(v));\n const { value: values = [], loading } = useAsyncFilterValues(\n asyncValues,\n '',\n defaultValues,\n valuesDebounceMs,\n );\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n const {\n target: { value, checked },\n } = e;\n\n setFilters(prevFilters => {\n const { [name]: filter, ...others } = prevFilters;\n const rest = ((filter as string[]) || []).filter(i => i !== value);\n const items = checked ? [...rest, value] : rest;\n return items.length ? { ...others, [name]: items } : others;\n });\n };\n\n return (\n <FormControl\n className={className}\n disabled={loading}\n fullWidth\n data-testid=\"search-checkboxfilter-next\"\n >\n {!!formLabel && (\n <FormLabel className={classes.label}>{formLabel}</FormLabel>\n )}\n {values.map(({ value, label }) => (\n <FormControlLabel\n key={value}\n classes={{\n root: classes.checkboxWrapper,\n label: classes.textWrapper,\n }}\n label={label}\n control={\n <Checkbox\n color=\"primary\"\n inputProps={{ 'aria-labelledby': label }}\n value={value}\n name={label}\n onChange={handleChange}\n checked={((filters[name] as string[]) ?? []).includes(value)}\n />\n }\n />\n ))}\n </FormControl>\n );\n};\n\n/**\n * @public\n */\nexport const SelectFilter = (props: SearchFilterComponentProps) => {\n const {\n className,\n defaultValue,\n label,\n name,\n values: givenValues,\n valuesDebounceMs,\n } = props;\n useDefaultFilterValue(name, defaultValue);\n const asyncValues =\n typeof givenValues === 'function' ? givenValues : undefined;\n const defaultValues =\n typeof givenValues === 'function'\n ? undefined\n : givenValues?.map(v => ensureFilterValueWithLabel(v));\n const { value: values = [], loading } = useAsyncFilterValues(\n asyncValues,\n '',\n defaultValues,\n valuesDebounceMs,\n );\n const allOptionValue = useRef(uuid());\n const allOption = { value: allOptionValue.current, label: 'All' };\n const { filters, setFilters } = useSearch();\n\n const handleChange = (value: SelectedItems) => {\n setFilters(prevFilters => {\n const { [name]: filter, ...others } = prevFilters;\n return value !== allOptionValue.current\n ? { ...others, [name]: value as string }\n : others;\n });\n };\n\n const items = [allOption, ...values];\n\n return (\n <FormControl\n disabled={loading}\n className={className}\n variant=\"filled\"\n fullWidth\n data-testid=\"search-selectfilter-next\"\n >\n <Select\n label={label ?? capitalize(name)}\n selected={(filters[name] || allOptionValue.current) as string}\n onChange={handleChange}\n items={items}\n />\n </FormControl>\n );\n};\n\n/**\n * @public\n */\nconst SearchFilter = (props: SearchFilterWrapperProps) => {\n const { component: Element, ...elementProps } = props;\n return <Element {...elementProps} />;\n};\n\nSearchFilter.Checkbox = (\n props: Omit<SearchFilterWrapperProps, 'component'> &\n SearchFilterComponentProps,\n) => <SearchFilter {...props} component={CheckboxFilter} />;\n\nSearchFilter.Select = (\n props: Omit<SearchFilterWrapperProps, 'component'> &\n SearchFilterComponentProps,\n) => <SearchFilter {...props} component={SelectFilter} />;\n\n/**\n * A control surface for a given filter field name, rendered as an autocomplete\n * textfield. A hard-coded list of values may be provided, or an async function\n * which returns values may be provided instead.\n *\n * @public\n */\nSearchFilter.Autocomplete = (props: SearchAutocompleteFilterProps) => (\n <SearchFilter {...props} component={AutocompleteFilter} />\n);\n\nexport { SearchFilter };\n"],"names":["uuid"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SearchFilter.esm.js","sources":["../../../src/components/SearchFilter/SearchFilter.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactElement, ChangeEvent, useRef } from 'react';\nimport { capitalize } from 'lodash';\nimport { v4 as uuid } from 'uuid';\nimport FormControl from '@material-ui/core/FormControl';\nimport FormControlLabel from '@material-ui/core/FormControlLabel';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport FormLabel from '@material-ui/core/FormLabel';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { Select, SelectedItems } from '@backstage/core-components';\n\nimport { useSearch } from '../../context';\nimport {\n AutocompleteFilter,\n SearchAutocompleteFilterProps,\n} from './SearchFilter.Autocomplete';\nimport { useAsyncFilterValues, useDefaultFilterValue } from './hooks';\nimport { ensureFilterValueWithLabel, FilterValue } from './types';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\nimport { searchReactTranslationRef } from '../../translation';\n\nconst useStyles = makeStyles({\n label: {\n textTransform: 'capitalize',\n },\n checkboxWrapper: {\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n },\n textWrapper: {\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n});\n\n/**\n * @public\n */\nexport type SearchFilterComponentProps = {\n className?: string;\n name: string;\n label?: string;\n /**\n * Either an array of values directly, or an async function to return a list\n * of values to be used in the filter. In the autocomplete filter, the last\n * input value is provided as an input to allow values to be filtered. This\n * function is debounced and values cached.\n */\n values?: FilterValue[] | ((partial: string) => Promise<FilterValue[]>);\n defaultValue?: string[] | string | null;\n /**\n * Debounce time in milliseconds, used when values is an async callback.\n * Defaults to 250ms.\n */\n valuesDebounceMs?: number;\n};\n\n/**\n * @public\n */\nexport type SearchFilterWrapperProps = SearchFilterComponentProps & {\n component: (props: SearchFilterComponentProps) => ReactElement;\n debug?: boolean;\n};\n\n/**\n * @public\n */\nexport const CheckboxFilter = (props: SearchFilterComponentProps) => {\n const {\n className,\n defaultValue,\n label: formLabel,\n name,\n values: givenValues = [],\n valuesDebounceMs,\n } = props;\n const classes = useStyles();\n const { filters, setFilters } = useSearch();\n useDefaultFilterValue(name, defaultValue);\n const asyncValues =\n typeof givenValues === 'function' ? givenValues : undefined;\n const defaultValues =\n typeof givenValues === 'function'\n ? undefined\n : givenValues.map(v => ensureFilterValueWithLabel(v));\n const { value: values = [], loading } = useAsyncFilterValues(\n asyncValues,\n '',\n defaultValues,\n valuesDebounceMs,\n );\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n const {\n target: { value, checked },\n } = e;\n\n setFilters(prevFilters => {\n const { [name]: filter, ...others } = prevFilters;\n const rest = ((filter as string[]) || []).filter(i => i !== value);\n const items = checked ? [...rest, value] : rest;\n return items.length ? { ...others, [name]: items } : others;\n });\n };\n\n return (\n <FormControl\n className={className}\n disabled={loading}\n fullWidth\n data-testid=\"search-checkboxfilter-next\"\n >\n {!!formLabel && (\n <FormLabel className={classes.label}>{formLabel}</FormLabel>\n )}\n {values.map(({ value, label }) => (\n <FormControlLabel\n key={value}\n classes={{\n root: classes.checkboxWrapper,\n label: classes.textWrapper,\n }}\n label={label}\n control={\n <Checkbox\n color=\"primary\"\n inputProps={{ 'aria-labelledby': label }}\n value={value}\n name={label}\n onChange={handleChange}\n checked={((filters[name] as string[]) ?? []).includes(value)}\n />\n }\n />\n ))}\n </FormControl>\n );\n};\n\n/**\n * @public\n */\nexport const SelectFilter = (props: SearchFilterComponentProps) => {\n const {\n className,\n defaultValue,\n label,\n name,\n values: givenValues,\n valuesDebounceMs,\n } = props;\n const { t } = useTranslationRef(searchReactTranslationRef);\n useDefaultFilterValue(name, defaultValue);\n const asyncValues =\n typeof givenValues === 'function' ? givenValues : undefined;\n const defaultValues =\n typeof givenValues === 'function'\n ? undefined\n : givenValues?.map(v => ensureFilterValueWithLabel(v));\n const { value: values = [], loading } = useAsyncFilterValues(\n asyncValues,\n '',\n defaultValues,\n valuesDebounceMs,\n );\n const allOptionValue = useRef(uuid());\n const allOption = {\n value: allOptionValue.current,\n label: t('searchFilter.allOptionTitle'),\n };\n const { filters, setFilters } = useSearch();\n\n const handleChange = (value: SelectedItems) => {\n setFilters(prevFilters => {\n const { [name]: filter, ...others } = prevFilters;\n return value !== allOptionValue.current\n ? { ...others, [name]: value as string }\n : others;\n });\n };\n\n const items = [allOption, ...values];\n\n return (\n <FormControl\n disabled={loading}\n className={className}\n variant=\"filled\"\n fullWidth\n data-testid=\"search-selectfilter-next\"\n >\n <Select\n label={label ?? capitalize(name)}\n selected={(filters[name] || allOptionValue.current) as string}\n onChange={handleChange}\n items={items}\n />\n </FormControl>\n );\n};\n\n/**\n * @public\n */\nconst SearchFilter = (props: SearchFilterWrapperProps) => {\n const { component: Element, ...elementProps } = props;\n return <Element {...elementProps} />;\n};\n\nSearchFilter.Checkbox = (\n props: Omit<SearchFilterWrapperProps, 'component'> &\n SearchFilterComponentProps,\n) => <SearchFilter {...props} component={CheckboxFilter} />;\n\nSearchFilter.Select = (\n props: Omit<SearchFilterWrapperProps, 'component'> &\n SearchFilterComponentProps,\n) => <SearchFilter {...props} component={SelectFilter} />;\n\n/**\n * A control surface for a given filter field name, rendered as an autocomplete\n * textfield. A hard-coded list of values may be provided, or an async function\n * which returns values may be provided instead.\n *\n * @public\n */\nSearchFilter.Autocomplete = (props: SearchAutocompleteFilterProps) => (\n <SearchFilter {...props} component={AutocompleteFilter} />\n);\n\nexport { SearchFilter };\n"],"names":["uuid"],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { useMemo, useCallback } from 'react';
|
|
3
3
|
import TablePagination from '@material-ui/core/TablePagination';
|
|
4
4
|
import { useSearch } from '../../context/SearchContext.esm.js';
|
|
5
|
+
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
|
6
|
+
import { searchReactTranslationRef } from '../../translation.esm.js';
|
|
5
7
|
|
|
6
8
|
const encodePageCursor = (pageCursor) => {
|
|
7
9
|
return Buffer.from(pageCursor.toString(), "utf-8").toString("base64");
|
|
@@ -11,14 +13,15 @@ const decodePageCursor = (pageCursor) => {
|
|
|
11
13
|
return Number(Buffer.from(pageCursor, "base64").toString("utf-8"));
|
|
12
14
|
};
|
|
13
15
|
const SearchPaginationBase = (props) => {
|
|
16
|
+
const { t } = useTranslationRef(searchReactTranslationRef);
|
|
14
17
|
const {
|
|
15
18
|
total: count = -1,
|
|
16
19
|
cursor: pageCursor,
|
|
17
20
|
hasNextPage,
|
|
18
21
|
onCursorChange: onPageCursorChange,
|
|
19
22
|
limit: rowsPerPage = 25,
|
|
20
|
-
limitLabel: labelRowsPerPage = "
|
|
21
|
-
limitText: labelDisplayedRows = ({ from, to }) => count > 0 ?
|
|
23
|
+
limitLabel: labelRowsPerPage = t("searchPagination.limitLabel"),
|
|
24
|
+
limitText: labelDisplayedRows = ({ from, to }) => count > 0 ? t("searchPagination.limitText", { num: `${count}` }) : `${from}-${to}`,
|
|
22
25
|
limitOptions: rowsPerPageOptions,
|
|
23
26
|
onLimitChange: onPageLimitChange,
|
|
24
27
|
...rest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchPagination.esm.js","sources":["../../../src/components/SearchPagination/SearchPagination.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ReactNode,\n ChangeEvent,\n MouseEvent,\n useCallback,\n useMemo,\n} from 'react';\nimport TablePagination from '@material-ui/core/TablePagination';\nimport { useSearch } from '../../context';\n\nconst encodePageCursor = (pageCursor: number): string => {\n return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64');\n};\n\nconst decodePageCursor = (pageCursor?: string): number => {\n if (!pageCursor) return 0;\n return Number(Buffer.from(pageCursor, 'base64').toString('utf-8'));\n};\n\n/**\n * A page limit option, this value must not be greater than 100.\n * @public\n */\nexport type SearchPaginationLimitOption<\n Current extends number = 101,\n Accumulator extends number[] = [],\n> = Accumulator['length'] extends Current\n ? Accumulator[number]\n : SearchPaginationLimitOption<\n Current,\n [...Accumulator, Accumulator['length']]\n >;\n\n/**\n * A page limit text, this function is called with a \"\\{ from, to, page, count \\}\" object.\n * @public\n */\nexport type SearchPaginationLimitText = (params: {\n from: number;\n to: number;\n page: number;\n count: number;\n}) => ReactNode;\n\n/**\n * Props for {@link SearchPaginationBase}.\n * @public\n */\nexport type SearchPaginationBaseProps = {\n /**\n * The component class name.\n */\n className?: string;\n /**\n * The total number of results.\n * For an unknown number of items, provide -1.\n * Defaults to -1.\n */\n total?: number;\n /**\n * The cursor for the current page.\n */\n cursor?: string;\n /**\n * Whether a next page exists\n */\n hasNextPage?: boolean;\n /**\n * Callback fired when the current page cursor is changed.\n */\n onCursorChange?: (pageCursor: string) => void;\n /**\n * The limit of results per page.\n * Set -1 to display all the results.\n */\n limit?: number;\n /**\n * Customize the results per page label.\n * Defaults to \"Results per page:\".\n */\n limitLabel?: ReactNode;\n /**\n * Customize the results per page text.\n * Defaults to \"(\\{ from, to, count \\}) =\\> count \\> 0 ? `of $\\{count\\}` : `$\\{from\\}-$\\{to\\}`\".\n */\n limitText?: SearchPaginationLimitText;\n /**\n * Options for setting how many results show per page.\n * If less than two options are available, no select field will be displayed.\n * Use -1 for the value with a custom label to show all the results.\n * Defaults to [10, 25, 50, 100].\n */\n limitOptions?: SearchPaginationLimitOption[];\n /**\n * Callback fired when the number of results per page is changed.\n */\n onLimitChange?: (value: number) => void;\n};\n\n/**\n * A component with controls for search results pagination.\n * @param props - See {@link SearchPaginationBaseProps}.\n * @public\n */\nexport const SearchPaginationBase = (props: SearchPaginationBaseProps) => {\n const {\n total: count = -1,\n cursor: pageCursor,\n hasNextPage,\n onCursorChange: onPageCursorChange,\n limit: rowsPerPage = 25,\n limitLabel: labelRowsPerPage = '
|
|
1
|
+
{"version":3,"file":"SearchPagination.esm.js","sources":["../../../src/components/SearchPagination/SearchPagination.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ReactNode,\n ChangeEvent,\n MouseEvent,\n useCallback,\n useMemo,\n} from 'react';\nimport TablePagination from '@material-ui/core/TablePagination';\nimport { useSearch } from '../../context';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\nimport { searchReactTranslationRef } from '../../translation';\n\nconst encodePageCursor = (pageCursor: number): string => {\n return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64');\n};\n\nconst decodePageCursor = (pageCursor?: string): number => {\n if (!pageCursor) return 0;\n return Number(Buffer.from(pageCursor, 'base64').toString('utf-8'));\n};\n\n/**\n * A page limit option, this value must not be greater than 100.\n * @public\n */\nexport type SearchPaginationLimitOption<\n Current extends number = 101,\n Accumulator extends number[] = [],\n> = Accumulator['length'] extends Current\n ? Accumulator[number]\n : SearchPaginationLimitOption<\n Current,\n [...Accumulator, Accumulator['length']]\n >;\n\n/**\n * A page limit text, this function is called with a \"\\{ from, to, page, count \\}\" object.\n * @public\n */\nexport type SearchPaginationLimitText = (params: {\n from: number;\n to: number;\n page: number;\n count: number;\n}) => ReactNode;\n\n/**\n * Props for {@link SearchPaginationBase}.\n * @public\n */\nexport type SearchPaginationBaseProps = {\n /**\n * The component class name.\n */\n className?: string;\n /**\n * The total number of results.\n * For an unknown number of items, provide -1.\n * Defaults to -1.\n */\n total?: number;\n /**\n * The cursor for the current page.\n */\n cursor?: string;\n /**\n * Whether a next page exists\n */\n hasNextPage?: boolean;\n /**\n * Callback fired when the current page cursor is changed.\n */\n onCursorChange?: (pageCursor: string) => void;\n /**\n * The limit of results per page.\n * Set -1 to display all the results.\n */\n limit?: number;\n /**\n * Customize the results per page label.\n * Defaults to \"Results per page:\".\n */\n limitLabel?: ReactNode;\n /**\n * Customize the results per page text.\n * Defaults to \"(\\{ from, to, count \\}) =\\> count \\> 0 ? `of $\\{count\\}` : `$\\{from\\}-$\\{to\\}`\".\n */\n limitText?: SearchPaginationLimitText;\n /**\n * Options for setting how many results show per page.\n * If less than two options are available, no select field will be displayed.\n * Use -1 for the value with a custom label to show all the results.\n * Defaults to [10, 25, 50, 100].\n */\n limitOptions?: SearchPaginationLimitOption[];\n /**\n * Callback fired when the number of results per page is changed.\n */\n onLimitChange?: (value: number) => void;\n};\n\n/**\n * A component with controls for search results pagination.\n * @param props - See {@link SearchPaginationBaseProps}.\n * @public\n */\nexport const SearchPaginationBase = (props: SearchPaginationBaseProps) => {\n const { t } = useTranslationRef(searchReactTranslationRef);\n const {\n total: count = -1,\n cursor: pageCursor,\n hasNextPage,\n onCursorChange: onPageCursorChange,\n limit: rowsPerPage = 25,\n limitLabel: labelRowsPerPage = t('searchPagination.limitLabel'),\n limitText: labelDisplayedRows = ({ from, to }) =>\n count > 0\n ? t('searchPagination.limitText', { num: `${count}` })\n : `${from}-${to}`,\n limitOptions: rowsPerPageOptions,\n onLimitChange: onPageLimitChange,\n ...rest\n } = props;\n\n const page = useMemo(() => decodePageCursor(pageCursor), [pageCursor]);\n\n const handlePageChange = useCallback(\n (_: MouseEvent<HTMLButtonElement> | null, newValue: number) => {\n onPageCursorChange?.(encodePageCursor(newValue));\n },\n [onPageCursorChange],\n );\n\n const handleRowsPerPageChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {\n const newValue = e.target.value;\n onPageLimitChange?.(parseInt(newValue, 10));\n },\n [onPageLimitChange],\n );\n\n return (\n <TablePagination\n {...rest}\n component=\"div\"\n count={count}\n page={page}\n nextIconButtonProps={{\n ...(hasNextPage !== undefined && { disabled: !hasNextPage }),\n }}\n onPageChange={handlePageChange}\n rowsPerPage={rowsPerPage}\n labelRowsPerPage={labelRowsPerPage}\n labelDisplayedRows={labelDisplayedRows}\n rowsPerPageOptions={rowsPerPageOptions}\n onRowsPerPageChange={handleRowsPerPageChange}\n />\n );\n};\n\n/**\n * Props for {@link SearchPagination}.\n * @public\n */\nexport type SearchPaginationProps = Omit<\n SearchPaginationBaseProps,\n | 'pageLimit'\n | 'onLimitChange'\n | 'pageCursor'\n | 'onPageCursorChange'\n | 'hasNextPage'\n>;\n\n/**\n * A component for setting the search context page limit and cursor.\n * @param props - See {@link SearchPaginationProps}.\n * @public\n */\nexport const SearchPagination = (props: SearchPaginationProps) => {\n const { pageLimit, setPageLimit, pageCursor, setPageCursor, fetchNextPage } =\n useSearch();\n\n const handlePageLimitChange = useCallback(\n (newPageLimit: number) => {\n setPageLimit(newPageLimit);\n setPageCursor(undefined);\n },\n [setPageLimit, setPageCursor],\n );\n\n return (\n <SearchPaginationBase\n {...props}\n hasNextPage={!!fetchNextPage}\n limit={pageLimit}\n onLimitChange={handlePageLimitChange}\n cursor={pageCursor}\n onCursorChange={setPageCursor}\n />\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import useAsync from 'react-use/esm/useAsync';
|
|
3
3
|
import { isFunction } from 'lodash';
|
|
4
|
-
import { Progress, ResponseErrorPanel
|
|
4
|
+
import { EmptyState, Progress, ResponseErrorPanel } from '@backstage/core-components';
|
|
5
5
|
import { useApi, AnalyticsContext } from '@backstage/core-plugin-api';
|
|
6
6
|
import { searchApiRef } from '../../api.esm.js';
|
|
7
7
|
import { useSearch } from '../../context/SearchContext.esm.js';
|
|
8
8
|
import { SearchResultListItemExtensions } from '../../extensions.esm.js';
|
|
9
|
+
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
|
10
|
+
import { searchReactTranslationRef } from '../../translation.esm.js';
|
|
9
11
|
|
|
10
12
|
const SearchResultContext = (props) => {
|
|
11
13
|
const { children } = props;
|
|
@@ -27,10 +29,11 @@ const SearchResultState = (props) => {
|
|
|
27
29
|
return query ? /* @__PURE__ */ jsx(SearchResultApi, { query, children }) : /* @__PURE__ */ jsx(SearchResultContext, { children });
|
|
28
30
|
};
|
|
29
31
|
const SearchResultComponent = (props) => {
|
|
32
|
+
const { t } = useTranslationRef(searchReactTranslationRef);
|
|
30
33
|
const {
|
|
31
34
|
query,
|
|
32
35
|
children,
|
|
33
|
-
noResultsComponent = /* @__PURE__ */ jsx(EmptyState, { missing: "data", title: "
|
|
36
|
+
noResultsComponent = /* @__PURE__ */ jsx(EmptyState, { missing: "data", title: t("noResultsDescription") }),
|
|
34
37
|
...rest
|
|
35
38
|
} = props;
|
|
36
39
|
return /* @__PURE__ */ jsx(SearchResultState, { query, children: ({ loading, error, value }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchResult.esm.js","sources":["../../../src/components/SearchResult/SearchResult.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactNode } from 'react';\nimport useAsync, { AsyncState } from 'react-use/esm/useAsync';\nimport { isFunction } from 'lodash';\n\nimport {\n Progress,\n EmptyState,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { useApi, AnalyticsContext } from '@backstage/core-plugin-api';\nimport { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';\n\nimport { searchApiRef } from '../../api';\nimport { useSearch } from '../../context';\nimport {\n SearchResultListItemExtensions,\n SearchResultListItemExtensionsProps,\n} from '../../extensions';\n\n/**\n * Props for {@link SearchResultContext}\n * @public\n */\nexport type SearchResultContextProps = {\n /**\n * A child function that receives an asynchronous result set and returns a react element.\n */\n children: (\n state: AsyncState<SearchResultSet>,\n query: Partial<SearchQuery>,\n ) => JSX.Element | null;\n};\n\n/**\n * Provides context-based results to a child function.\n * @param props - see {@link SearchResultContextProps}.\n * @example\n * ```\n * <SearchResultContext>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultContext>\n * ```\n * @public\n */\nexport const SearchResultContext = (props: SearchResultContextProps) => {\n const { children } = props;\n const context = useSearch();\n const { result: state, ...query } = context;\n return children(state, query);\n};\n\n/**\n * Props for {@link SearchResultApi}\n * @public\n */\nexport type SearchResultApiProps = SearchResultContextProps & {\n query: Partial<SearchQuery>;\n};\n\n/**\n * Request results through the search api and provide them to a child function.\n * @param props - see {@link SearchResultApiProps}.\n * @example\n * ```\n * <SearchResultApi>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultApi>\n * ```\n * @public\n */\nexport const SearchResultApi = (props: SearchResultApiProps) => {\n const { query, children } = props;\n const searchApi = useApi(searchApiRef);\n\n const state = useAsync(() => {\n const { term = '', types = [], filters = {}, ...rest } = query;\n return searchApi.query({ ...rest, term, types, filters });\n }, [query]);\n\n return children(state, query);\n};\n\n/**\n * Props for {@link SearchResultState}\n * @public\n */\nexport type SearchResultStateProps = SearchResultContextProps &\n Partial<SearchResultApiProps>;\n\n/**\n * Call a child render function passing a search state as an argument.\n * @remarks By default, results are taken from context, but when a \"query\" prop is set, results are requested from the search api.\n * @param props - see {@link SearchResultStateProps}.\n * @example\n * Consuming results from context:\n * ```\n * <SearchResultState>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultState>\n * ```\n * @example\n * Requesting results using the search api:\n * ```\n * <SearchResultState query={{ term: 'documentation' }}>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultState>\n * ```\n * @public\n */\nexport const SearchResultState = (props: SearchResultStateProps) => {\n const { query, children } = props;\n\n return query ? (\n <SearchResultApi query={query}>{children}</SearchResultApi>\n ) : (\n <SearchResultContext>{children}</SearchResultContext>\n );\n};\n\n/**\n * Props for {@link SearchResult}\n * @public\n */\nexport type SearchResultProps = Pick<SearchResultStateProps, 'query'> &\n Omit<SearchResultListItemExtensionsProps, 'results' | 'children'> & {\n children?: ReactNode | ((resultSet: SearchResultSet) => JSX.Element);\n noResultsComponent?: JSX.Element;\n };\n\n/**\n * Renders results from a parent search context or api.\n * @remarks default components for loading, error and empty variants are returned.\n * @param props - see {@link SearchResultProps}.\n * @public\n */\nexport const SearchResultComponent = (props: SearchResultProps) => {\n const {\n query,\n children,\n noResultsComponent = (\n <EmptyState missing=\"data\" title
|
|
1
|
+
{"version":3,"file":"SearchResult.esm.js","sources":["../../../src/components/SearchResult/SearchResult.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactNode } from 'react';\nimport useAsync, { AsyncState } from 'react-use/esm/useAsync';\nimport { isFunction } from 'lodash';\n\nimport {\n Progress,\n EmptyState,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { useApi, AnalyticsContext } from '@backstage/core-plugin-api';\nimport { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';\n\nimport { searchApiRef } from '../../api';\nimport { useSearch } from '../../context';\nimport {\n SearchResultListItemExtensions,\n SearchResultListItemExtensionsProps,\n} from '../../extensions';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\nimport { searchReactTranslationRef } from '../../translation';\n\n/**\n * Props for {@link SearchResultContext}\n * @public\n */\nexport type SearchResultContextProps = {\n /**\n * A child function that receives an asynchronous result set and returns a react element.\n */\n children: (\n state: AsyncState<SearchResultSet>,\n query: Partial<SearchQuery>,\n ) => JSX.Element | null;\n};\n\n/**\n * Provides context-based results to a child function.\n * @param props - see {@link SearchResultContextProps}.\n * @example\n * ```\n * <SearchResultContext>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultContext>\n * ```\n * @public\n */\nexport const SearchResultContext = (props: SearchResultContextProps) => {\n const { children } = props;\n const context = useSearch();\n const { result: state, ...query } = context;\n return children(state, query);\n};\n\n/**\n * Props for {@link SearchResultApi}\n * @public\n */\nexport type SearchResultApiProps = SearchResultContextProps & {\n query: Partial<SearchQuery>;\n};\n\n/**\n * Request results through the search api and provide them to a child function.\n * @param props - see {@link SearchResultApiProps}.\n * @example\n * ```\n * <SearchResultApi>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultApi>\n * ```\n * @public\n */\nexport const SearchResultApi = (props: SearchResultApiProps) => {\n const { query, children } = props;\n const searchApi = useApi(searchApiRef);\n\n const state = useAsync(() => {\n const { term = '', types = [], filters = {}, ...rest } = query;\n return searchApi.query({ ...rest, term, types, filters });\n }, [query]);\n\n return children(state, query);\n};\n\n/**\n * Props for {@link SearchResultState}\n * @public\n */\nexport type SearchResultStateProps = SearchResultContextProps &\n Partial<SearchResultApiProps>;\n\n/**\n * Call a child render function passing a search state as an argument.\n * @remarks By default, results are taken from context, but when a \"query\" prop is set, results are requested from the search api.\n * @param props - see {@link SearchResultStateProps}.\n * @example\n * Consuming results from context:\n * ```\n * <SearchResultState>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultState>\n * ```\n * @example\n * Requesting results using the search api:\n * ```\n * <SearchResultState query={{ term: 'documentation' }}>\n * {({ loading, error, value }) => (\n * <List>\n * {value?.map(({ document }) => (\n * <DefaultSearchResultListItem\n * key={document.location}\n * result={document}\n * />\n * ))}\n * </List>\n * )}\n * </SearchResultState>\n * ```\n * @public\n */\nexport const SearchResultState = (props: SearchResultStateProps) => {\n const { query, children } = props;\n\n return query ? (\n <SearchResultApi query={query}>{children}</SearchResultApi>\n ) : (\n <SearchResultContext>{children}</SearchResultContext>\n );\n};\n\n/**\n * Props for {@link SearchResult}\n * @public\n */\nexport type SearchResultProps = Pick<SearchResultStateProps, 'query'> &\n Omit<SearchResultListItemExtensionsProps, 'results' | 'children'> & {\n children?: ReactNode | ((resultSet: SearchResultSet) => JSX.Element);\n noResultsComponent?: JSX.Element;\n };\n\n/**\n * Renders results from a parent search context or api.\n * @remarks default components for loading, error and empty variants are returned.\n * @param props - see {@link SearchResultProps}.\n * @public\n */\nexport const SearchResultComponent = (props: SearchResultProps) => {\n const { t } = useTranslationRef(searchReactTranslationRef);\n const {\n query,\n children,\n noResultsComponent = (\n <EmptyState missing=\"data\" title={t('noResultsDescription')} />\n ),\n ...rest\n } = props;\n\n return (\n <SearchResultState query={query}>\n {({ loading, error, value }) => {\n if (loading) {\n return <Progress />;\n }\n\n if (error) {\n return (\n <ResponseErrorPanel\n title=\"Error encountered while fetching search results\"\n error={error}\n />\n );\n }\n\n if (!value?.results.length) {\n return noResultsComponent;\n }\n\n if (isFunction(children)) {\n return children(value);\n }\n\n return (\n <SearchResultListItemExtensions {...rest} results={value.results}>\n {children}\n </SearchResultListItemExtensions>\n );\n }}\n </SearchResultState>\n );\n};\n\n/**\n * A component returning the search result from a parent search context or api.\n * @param props - see {@link SearchResultProps}.\n * @public\n */\nexport const SearchResult = (props: SearchResultProps) => (\n <AnalyticsContext\n attributes={{\n pluginId: 'search',\n extension: 'SearchResult',\n }}\n >\n <SearchResultComponent {...props} />\n </AnalyticsContext>\n);\n"],"names":[],"mappings":";;;;;;;;;;;AAuEa,MAAA,mBAAA,GAAsB,CAAC,KAAoC,KAAA;AACtE,EAAM,MAAA,EAAE,UAAa,GAAA,KAAA;AACrB,EAAA,MAAM,UAAU,SAAU,EAAA;AAC1B,EAAA,MAAM,EAAE,MAAA,EAAQ,KAAO,EAAA,GAAG,OAAU,GAAA,OAAA;AACpC,EAAO,OAAA,QAAA,CAAS,OAAO,KAAK,CAAA;AAC9B;AA8Ba,MAAA,eAAA,GAAkB,CAAC,KAAgC,KAAA;AAC9D,EAAM,MAAA,EAAE,KAAO,EAAA,QAAA,EAAa,GAAA,KAAA;AAC5B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA;AAErC,EAAM,MAAA,KAAA,GAAQ,SAAS,MAAM;AAC3B,IAAM,MAAA,EAAE,IAAO,GAAA,EAAA,EAAI,KAAQ,GAAA,EAAI,EAAA,OAAA,GAAU,EAAC,EAAG,GAAG,IAAA,EAAS,GAAA,KAAA;AACzD,IAAO,OAAA,SAAA,CAAU,MAAM,EAAE,GAAG,MAAM,IAAM,EAAA,KAAA,EAAO,SAAS,CAAA;AAAA,GAC1D,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAO,OAAA,QAAA,CAAS,OAAO,KAAK,CAAA;AAC9B;AA+Ca,MAAA,iBAAA,GAAoB,CAAC,KAAkC,KAAA;AAClE,EAAM,MAAA,EAAE,KAAO,EAAA,QAAA,EAAa,GAAA,KAAA;AAE5B,EAAO,OAAA,KAAA,uBACJ,eAAgB,EAAA,EAAA,KAAA,EAAe,UAAS,CAEzC,mBAAA,GAAA,CAAC,uBAAqB,QAAS,EAAA,CAAA;AAEnC;AAkBa,MAAA,qBAAA,GAAwB,CAAC,KAA6B,KAAA;AACjE,EAAA,MAAM,EAAE,CAAA,EAAM,GAAA,iBAAA,CAAkB,yBAAyB,CAAA;AACzD,EAAM,MAAA;AAAA,IACJ,KAAA;AAAA,IACA,QAAA;AAAA,IACA,kBAAA,uBACG,UAAW,EAAA,EAAA,OAAA,EAAQ,QAAO,KAAO,EAAA,CAAA,CAAE,sBAAsB,CAAG,EAAA,CAAA;AAAA,IAE/D,GAAG;AAAA,GACD,GAAA,KAAA;AAEJ,EACE,uBAAA,GAAA,CAAC,qBAAkB,KAChB,EAAA,QAAA,EAAA,CAAC,EAAE,OAAS,EAAA,KAAA,EAAO,OAAY,KAAA;AAC9B,IAAA,IAAI,OAAS,EAAA;AACX,MAAA,2BAAQ,QAAS,EAAA,EAAA,CAAA;AAAA;AAGnB,IAAA,IAAI,KAAO,EAAA;AACT,MACE,uBAAA,GAAA;AAAA,QAAC,kBAAA;AAAA,QAAA;AAAA,UACC,KAAM,EAAA,iDAAA;AAAA,UACN;AAAA;AAAA,OACF;AAAA;AAIJ,IAAI,IAAA,CAAC,KAAO,EAAA,OAAA,CAAQ,MAAQ,EAAA;AAC1B,MAAO,OAAA,kBAAA;AAAA;AAGT,IAAI,IAAA,UAAA,CAAW,QAAQ,CAAG,EAAA;AACxB,MAAA,OAAO,SAAS,KAAK,CAAA;AAAA;AAGvB,IAAA,2BACG,8BAAgC,EAAA,EAAA,GAAG,MAAM,OAAS,EAAA,KAAA,CAAM,SACtD,QACH,EAAA,CAAA;AAAA,GAGN,EAAA,CAAA;AAEJ;AAOa,MAAA,YAAA,GAAe,CAAC,KAC3B,qBAAA,GAAA;AAAA,EAAC,gBAAA;AAAA,EAAA;AAAA,IACC,UAAY,EAAA;AAAA,MACV,QAAU,EAAA,QAAA;AAAA,MACV,SAAW,EAAA;AAAA,KACb;AAAA,IAEA,QAAA,kBAAA,GAAA,CAAC,qBAAuB,EAAA,EAAA,GAAG,KAAO,EAAA;AAAA;AACpC;;;;"}
|
|
@@ -17,6 +17,8 @@ import { AnalyticsContext } from '@backstage/core-plugin-api';
|
|
|
17
17
|
import { useSearchResultListItemExtensions } from '../../extensions.esm.js';
|
|
18
18
|
import { DefaultResultListItem as HigherOrderDefaultResultListItem } from '../DefaultResultListItem/DefaultResultListItem.esm.js';
|
|
19
19
|
import { SearchResultState } from '../SearchResult/SearchResult.esm.js';
|
|
20
|
+
import { searchReactTranslationRef } from '../../translation.esm.js';
|
|
21
|
+
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
|
20
22
|
|
|
21
23
|
const useStyles = makeStyles((theme) => ({
|
|
22
24
|
listSubheader: {
|
|
@@ -147,6 +149,7 @@ const SearchResultGroupSelectFilterField = (props) => {
|
|
|
147
149
|
function SearchResultGroupLayout(props) {
|
|
148
150
|
const classes = useStyles();
|
|
149
151
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
152
|
+
const { t } = useTranslationRef(searchReactTranslationRef);
|
|
150
153
|
const {
|
|
151
154
|
error,
|
|
152
155
|
loading,
|
|
@@ -154,7 +157,7 @@ function SearchResultGroupLayout(props) {
|
|
|
154
157
|
title,
|
|
155
158
|
titleProps = {},
|
|
156
159
|
link = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
157
|
-
"
|
|
160
|
+
t("searchResultGroup.linkTitle"),
|
|
158
161
|
/* @__PURE__ */ jsx(ArrowRightIcon, { className: classes.listSubheaderLinkIcon })
|
|
159
162
|
] }),
|
|
160
163
|
linkProps = {},
|
|
@@ -171,7 +174,7 @@ function SearchResultGroupLayout(props) {
|
|
|
171
174
|
resultItem.document.location
|
|
172
175
|
),
|
|
173
176
|
disableRenderingWithNoResults,
|
|
174
|
-
noResultsComponent = disableRenderingWithNoResults ? null : /* @__PURE__ */ jsx(EmptyState, { missing: "data", title: "
|
|
177
|
+
noResultsComponent = disableRenderingWithNoResults ? null : /* @__PURE__ */ jsx(EmptyState, { missing: "data", title: t("noResultsDescription") }),
|
|
175
178
|
...rest
|
|
176
179
|
} = props;
|
|
177
180
|
const handleClick = useCallback((e) => {
|
|
@@ -214,7 +217,7 @@ function SearchResultGroupLayout(props) {
|
|
|
214
217
|
component: "button",
|
|
215
218
|
icon: /* @__PURE__ */ jsx(AddIcon, {}),
|
|
216
219
|
variant: "outlined",
|
|
217
|
-
label: "
|
|
220
|
+
label: t("searchResultGroup.addFilterButtonTitle"),
|
|
218
221
|
"aria-controls": "filters-menu",
|
|
219
222
|
"aria-haspopup": "true",
|
|
220
223
|
onClick: handleClick
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchResultGroup.esm.js","sources":["../../../src/components/SearchResultGroup/SearchResultGroup.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MouseEvent,\n ChangeEvent,\n PropsWithChildren,\n ReactNode,\n useCallback,\n useState,\n} from 'react';\nimport qs from 'qs';\n\nimport List, { ListProps } from '@material-ui/core/List';\nimport ListSubheader from '@material-ui/core/ListSubheader';\nimport Menu from '@material-ui/core/Menu';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport InputBase from '@material-ui/core/InputBase';\nimport Select from '@material-ui/core/Select';\nimport Chip from '@material-ui/core/Chip';\nimport Typography, { TypographyProps } from '@material-ui/core/Typography';\nimport { makeStyles, Theme } from '@material-ui/core/styles';\nimport AddIcon from '@material-ui/icons/Add';\nimport ArrowRightIcon from '@material-ui/icons/ArrowForwardIos';\n\nimport { JsonValue } from '@backstage/types';\nimport {\n Link,\n LinkProps,\n Progress,\n EmptyState,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { AnalyticsContext } from '@backstage/core-plugin-api';\nimport { SearchResult } from '@backstage/plugin-search-common';\n\nimport { useSearchResultListItemExtensions } from '../../extensions';\n\nimport { DefaultResultListItem } from '../DefaultResultListItem';\nimport { SearchResultState, SearchResultStateProps } from '../SearchResult';\n\nconst useStyles = makeStyles((theme: Theme) => ({\n listSubheader: {\n display: 'flex',\n alignItems: 'center',\n },\n listSubheaderName: {\n marginLeft: theme.spacing(1),\n textTransform: 'uppercase',\n },\n listSubheaderChip: {\n color: theme.palette.text.secondary,\n margin: theme.spacing(0, 0, 0, 1.5),\n },\n listSubheaderFilter: {\n display: 'flex',\n color: theme.palette.text.secondary,\n margin: theme.spacing(0, 0, 0, 1.5),\n },\n listSubheaderLink: {\n marginLeft: 'auto',\n display: 'flex',\n alignItems: 'center',\n },\n listSubheaderLinkIcon: {\n fontSize: 'inherit',\n marginLeft: theme.spacing(0.5),\n },\n}));\n\n/**\n * Props for {@link SearchResultGroupFilterFieldLayout}\n * @public\n */\nexport type SearchResultGroupFilterFieldLayoutProps = PropsWithChildren<{\n label: string;\n value?: JsonValue;\n onDelete: () => void;\n}>;\n\n/**\n * Default layout for a search group filter field.\n * @param props - See {@link SearchResultGroupFilterFieldLayoutProps}.\n * @public\n */\nexport const SearchResultGroupFilterFieldLayout = (\n props: SearchResultGroupFilterFieldLayoutProps,\n) => {\n const classes = useStyles();\n const { label, children, ...rest } = props;\n\n return (\n <Chip\n {...rest}\n className={classes.listSubheaderFilter}\n variant=\"outlined\"\n label={\n <>\n {label}: {children}\n </>\n }\n />\n );\n};\n\nconst NullIcon = () => null;\n\n/**\n * Common props for a result group filter field.\n * @public\n */\nexport type SearchResultGroupFilterFieldPropsWith<T> = T &\n SearchResultGroupFilterFieldLayoutProps & {\n onChange: (value: JsonValue) => void;\n };\n\nconst useSearchResultGroupTextFilterStyles = makeStyles((theme: Theme) => ({\n root: {\n fontSize: 'inherit',\n '&:focus': {\n outline: 'none',\n background: theme.palette.common.white,\n },\n '&:not(:focus)': {\n cursor: 'pointer',\n color: theme.palette.primary.main,\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n },\n}));\n\n/**\n * Props for {@link SearchResultGroupTextFilterField}.\n * @public\n */\nexport type SearchResultGroupTextFilterFieldProps =\n SearchResultGroupFilterFieldPropsWith<{}>;\n\n/**\n * A text field that can be used as filter on search result groups.\n * @param props - See {@link SearchResultGroupTextFilterFieldProps}.\n * @example\n * ```\n * <SearchResultGroupTextFilterField\n * id=\"lifecycle\"\n * label=\"Lifecycle\"\n * value={value}\n * onChange={handleChangeFilter}\n * onDelete={handleDeleteFilter}\n * />\n * ```\n * @public\n */\nexport const SearchResultGroupTextFilterField = (\n props: SearchResultGroupTextFilterFieldProps,\n) => {\n const classes = useSearchResultGroupTextFilterStyles();\n const { label, value = 'None', onChange, onDelete } = props;\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n onChange(e.target.value);\n },\n [onChange],\n );\n\n return (\n <SearchResultGroupFilterFieldLayout label={label} onDelete={onDelete}>\n <Typography\n role=\"textbox\"\n component=\"span\"\n className={classes.root}\n onChange={handleChange}\n contentEditable\n suppressContentEditableWarning\n >\n {value?.toString()}\n </Typography>\n </SearchResultGroupFilterFieldLayout>\n );\n};\n\nconst useSearchResultGroupSelectFilterStyles = makeStyles((theme: Theme) => ({\n root: {\n fontSize: 'inherit',\n '&:not(:focus)': {\n cursor: 'pointer',\n color: theme.palette.primary.main,\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n '&:focus': {\n outline: 'none',\n },\n '&>div:first-child': {\n padding: 0,\n },\n },\n}));\n\n/**\n * Props for {@link SearchResultGroupTextFilterField}.\n * @public\n */\nexport type SearchResultGroupSelectFilterFieldProps =\n SearchResultGroupFilterFieldPropsWith<{\n children: ReactNode;\n }>;\n\n/**\n * A select field that can be used as filter on search result groups.\n * @param props - See {@link SearchResultGroupSelectFilterFieldProps}.\n * @example\n * ```\n * <SearchResultGroupSelectFilterField\n * id=\"lifecycle\"\n * label=\"Lifecycle\"\n * value={filters.lifecycle}\n * onChange={handleChangeFilter}\n * onDelete={handleDeleteFilter}\n * >\n * <MenuItem value=\"experimental\">Experimental</MenuItem>\n * <MenuItem value=\"production\">Production</MenuItem>\n * </SearchResultGroupSelectFilterField>\n * ```\n * @public\n */\nexport const SearchResultGroupSelectFilterField = (\n props: SearchResultGroupSelectFilterFieldProps,\n) => {\n const classes = useSearchResultGroupSelectFilterStyles();\n const { label, value = 'none', onChange, onDelete, children } = props;\n\n const handleChange = useCallback(\n (e: ChangeEvent<{ value: unknown }>) => {\n onChange(e.target.value as JsonValue);\n },\n [onChange],\n );\n\n return (\n <SearchResultGroupFilterFieldLayout label={label} onDelete={onDelete}>\n <Select\n className={classes.root}\n value={value}\n onChange={handleChange}\n input={<InputBase />}\n IconComponent={NullIcon}\n >\n <MenuItem value=\"none\">None</MenuItem>\n {children}\n </Select>\n </SearchResultGroupFilterFieldLayout>\n );\n};\n\n/**\n * Props for {@link SearchResultGroupLayout}\n * @public\n */\nexport type SearchResultGroupLayoutProps<FilterOption> = ListProps & {\n /**\n * If defined, will render a default error panel.\n */\n error?: Error;\n /**\n * If defined, will render a default loading progress.\n */\n loading?: boolean;\n /**\n * Icon that representing a result group.\n */\n icon: JSX.Element;\n /**\n * The results group title content, it could be a text or an element.\n */\n title: ReactNode;\n /**\n * Props for the results group title.\n */\n titleProps?: Partial<TypographyProps>;\n /**\n * The results group link content, it could be a text or an element.\n */\n link?: ReactNode;\n /**\n * Props for the results group link, the \"to\" prop defaults to \"/search\".\n */\n linkProps?: Partial<LinkProps>;\n /**\n * A generic filter options that is rendered on the \"Add filter\" dropdown.\n */\n filterOptions?: FilterOption[];\n /**\n * Function to customize how filter options are rendered.\n * @remarks Defaults to a menu item where its value and label bounds to the option string.\n */\n renderFilterOption?: (\n value: FilterOption,\n index: number,\n array: FilterOption[],\n ) => JSX.Element | null;\n /**\n * A list of search filter keys, also known as filter field names.\n */\n filterFields?: string[];\n /**\n * Function to customize how filter chips are rendered.\n */\n renderFilterField?: (key: string) => JSX.Element | null;\n /**\n * Search results to be rendered as a group.\n */\n resultItems?: SearchResult[];\n /**\n * Function to customize how result items are rendered.\n */\n renderResultItem?: (\n value: SearchResult,\n index: number,\n array: SearchResult[],\n ) => JSX.Element | null;\n /**\n * Optional component to render when no results. Default to <EmptyState /> component.\n */\n noResultsComponent?: ReactNode;\n /**\n * Optional property to provide if component should not render the component when no results are found.\n */\n disableRenderingWithNoResults?: boolean;\n};\n\n/**\n * Default layout for rendering search results in a group.\n * @param props - See {@link SearchResultGroupLayoutProps}.\n * @public\n */\nexport function SearchResultGroupLayout<FilterOption>(\n props: SearchResultGroupLayoutProps<FilterOption>,\n) {\n const classes = useStyles();\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n\n const {\n error,\n loading,\n icon,\n title,\n titleProps = {},\n link = (\n <>\n See all\n <ArrowRightIcon className={classes.listSubheaderLinkIcon} />\n </>\n ),\n linkProps = {},\n filterOptions,\n renderFilterOption = filterOption => (\n <MenuItem key={String(filterOption)} value={String(filterOption)}>\n {String(filterOption)}\n </MenuItem>\n ),\n filterFields,\n renderFilterField,\n resultItems,\n renderResultItem = resultItem => (\n <DefaultResultListItem\n key={resultItem.document.location}\n result={resultItem.document}\n />\n ),\n disableRenderingWithNoResults,\n noResultsComponent = disableRenderingWithNoResults ? null : (\n <EmptyState missing=\"data\" title=\"Sorry, no results were found\" />\n ),\n ...rest\n } = props;\n\n const handleClick = useCallback((e: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(e.currentTarget);\n }, []);\n\n const handleClose = useCallback(() => {\n setAnchorEl(null);\n }, []);\n\n if (loading) {\n return <Progress />;\n }\n\n if (error) {\n return (\n <ResponseErrorPanel\n title=\"Error encountered while fetching search results\"\n error={error}\n />\n );\n }\n\n if (!resultItems?.length) {\n return <>{noResultsComponent}</>;\n }\n\n return (\n <List {...rest}>\n <ListSubheader className={classes.listSubheader}>\n {icon}\n <Typography\n className={classes.listSubheaderName}\n component=\"strong\"\n {...titleProps}\n >\n {title}\n </Typography>\n {filterOptions ? (\n <Chip\n className={classes.listSubheaderChip}\n component=\"button\"\n icon={<AddIcon />}\n variant=\"outlined\"\n label=\"Add filter\"\n aria-controls=\"filters-menu\"\n aria-haspopup=\"true\"\n onClick={handleClick}\n />\n ) : null}\n {filterOptions ? (\n <Menu\n id=\"filters-menu\"\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={handleClose}\n onClick={handleClose}\n keepMounted\n >\n {filterOptions.map(renderFilterOption)}\n </Menu>\n ) : null}\n {filterFields?.map(\n filterField => renderFilterField?.(filterField) ?? null,\n )}\n <Link className={classes.listSubheaderLink} to=\"/search\" {...linkProps}>\n {link}\n </Link>\n </ListSubheader>\n {resultItems.map(renderResultItem)}\n </List>\n );\n}\n\n/**\n * Props for {@link SearchResultGroup}.\n * @public\n */\nexport type SearchResultGroupProps<FilterOption> = Pick<\n SearchResultStateProps,\n 'query'\n> &\n Omit<\n SearchResultGroupLayoutProps<FilterOption>,\n 'loading' | 'error' | 'resultItems' | 'filterFields'\n >;\n\n/**\n * Given a query, search for results and render them as a group.\n * @param props - See {@link SearchResultGroupProps}.\n * @public\n */\nexport function SearchResultGroup<FilterOption>(\n props: SearchResultGroupProps<FilterOption>,\n) {\n const { query, children, renderResultItem, linkProps = {}, ...rest } = props;\n\n const defaultRenderResultItem = useSearchResultListItemExtensions(children);\n\n return (\n <AnalyticsContext\n attributes={{\n pluginId: 'search',\n extension: 'SearchResultGroup',\n }}\n >\n <SearchResultState query={query}>\n {(\n { loading, error, value },\n { term, types, pageCursor, filters = {} },\n ) => {\n const to = `/search?${qs.stringify(\n { term, types, filters, pageCursor, query: term },\n { arrayFormat: 'brackets' },\n )}`;\n\n return (\n <SearchResultGroupLayout\n {...rest}\n error={error}\n loading={loading}\n linkProps={{ to, ...linkProps }}\n filterFields={Object.keys(filters)}\n resultItems={value?.results}\n renderResultItem={renderResultItem ?? defaultRenderResultItem}\n />\n );\n }}\n </SearchResultState>\n </AnalyticsContext>\n );\n}\n"],"names":["DefaultResultListItem"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SearchResultGroup.esm.js","sources":["../../../src/components/SearchResultGroup/SearchResultGroup.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MouseEvent,\n ChangeEvent,\n PropsWithChildren,\n ReactNode,\n useCallback,\n useState,\n} from 'react';\nimport qs from 'qs';\n\nimport List, { ListProps } from '@material-ui/core/List';\nimport ListSubheader from '@material-ui/core/ListSubheader';\nimport Menu from '@material-ui/core/Menu';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport InputBase from '@material-ui/core/InputBase';\nimport Select from '@material-ui/core/Select';\nimport Chip from '@material-ui/core/Chip';\nimport Typography, { TypographyProps } from '@material-ui/core/Typography';\nimport { makeStyles, Theme } from '@material-ui/core/styles';\nimport AddIcon from '@material-ui/icons/Add';\nimport ArrowRightIcon from '@material-ui/icons/ArrowForwardIos';\n\nimport { JsonValue } from '@backstage/types';\nimport {\n Link,\n LinkProps,\n Progress,\n EmptyState,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { AnalyticsContext } from '@backstage/core-plugin-api';\nimport { SearchResult } from '@backstage/plugin-search-common';\n\nimport { useSearchResultListItemExtensions } from '../../extensions';\n\nimport { DefaultResultListItem } from '../DefaultResultListItem';\nimport { SearchResultState, SearchResultStateProps } from '../SearchResult';\nimport { searchReactTranslationRef } from '../../translation';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\n\nconst useStyles = makeStyles((theme: Theme) => ({\n listSubheader: {\n display: 'flex',\n alignItems: 'center',\n },\n listSubheaderName: {\n marginLeft: theme.spacing(1),\n textTransform: 'uppercase',\n },\n listSubheaderChip: {\n color: theme.palette.text.secondary,\n margin: theme.spacing(0, 0, 0, 1.5),\n },\n listSubheaderFilter: {\n display: 'flex',\n color: theme.palette.text.secondary,\n margin: theme.spacing(0, 0, 0, 1.5),\n },\n listSubheaderLink: {\n marginLeft: 'auto',\n display: 'flex',\n alignItems: 'center',\n },\n listSubheaderLinkIcon: {\n fontSize: 'inherit',\n marginLeft: theme.spacing(0.5),\n },\n}));\n\n/**\n * Props for {@link SearchResultGroupFilterFieldLayout}\n * @public\n */\nexport type SearchResultGroupFilterFieldLayoutProps = PropsWithChildren<{\n label: string;\n value?: JsonValue;\n onDelete: () => void;\n}>;\n\n/**\n * Default layout for a search group filter field.\n * @param props - See {@link SearchResultGroupFilterFieldLayoutProps}.\n * @public\n */\nexport const SearchResultGroupFilterFieldLayout = (\n props: SearchResultGroupFilterFieldLayoutProps,\n) => {\n const classes = useStyles();\n const { label, children, ...rest } = props;\n\n return (\n <Chip\n {...rest}\n className={classes.listSubheaderFilter}\n variant=\"outlined\"\n label={\n <>\n {label}: {children}\n </>\n }\n />\n );\n};\n\nconst NullIcon = () => null;\n\n/**\n * Common props for a result group filter field.\n * @public\n */\nexport type SearchResultGroupFilterFieldPropsWith<T> = T &\n SearchResultGroupFilterFieldLayoutProps & {\n onChange: (value: JsonValue) => void;\n };\n\nconst useSearchResultGroupTextFilterStyles = makeStyles((theme: Theme) => ({\n root: {\n fontSize: 'inherit',\n '&:focus': {\n outline: 'none',\n background: theme.palette.common.white,\n },\n '&:not(:focus)': {\n cursor: 'pointer',\n color: theme.palette.primary.main,\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n },\n}));\n\n/**\n * Props for {@link SearchResultGroupTextFilterField}.\n * @public\n */\nexport type SearchResultGroupTextFilterFieldProps =\n SearchResultGroupFilterFieldPropsWith<{}>;\n\n/**\n * A text field that can be used as filter on search result groups.\n * @param props - See {@link SearchResultGroupTextFilterFieldProps}.\n * @example\n * ```\n * <SearchResultGroupTextFilterField\n * id=\"lifecycle\"\n * label=\"Lifecycle\"\n * value={value}\n * onChange={handleChangeFilter}\n * onDelete={handleDeleteFilter}\n * />\n * ```\n * @public\n */\nexport const SearchResultGroupTextFilterField = (\n props: SearchResultGroupTextFilterFieldProps,\n) => {\n const classes = useSearchResultGroupTextFilterStyles();\n const { label, value = 'None', onChange, onDelete } = props;\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n onChange(e.target.value);\n },\n [onChange],\n );\n\n return (\n <SearchResultGroupFilterFieldLayout label={label} onDelete={onDelete}>\n <Typography\n role=\"textbox\"\n component=\"span\"\n className={classes.root}\n onChange={handleChange}\n contentEditable\n suppressContentEditableWarning\n >\n {value?.toString()}\n </Typography>\n </SearchResultGroupFilterFieldLayout>\n );\n};\n\nconst useSearchResultGroupSelectFilterStyles = makeStyles((theme: Theme) => ({\n root: {\n fontSize: 'inherit',\n '&:not(:focus)': {\n cursor: 'pointer',\n color: theme.palette.primary.main,\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n '&:focus': {\n outline: 'none',\n },\n '&>div:first-child': {\n padding: 0,\n },\n },\n}));\n\n/**\n * Props for {@link SearchResultGroupTextFilterField}.\n * @public\n */\nexport type SearchResultGroupSelectFilterFieldProps =\n SearchResultGroupFilterFieldPropsWith<{\n children: ReactNode;\n }>;\n\n/**\n * A select field that can be used as filter on search result groups.\n * @param props - See {@link SearchResultGroupSelectFilterFieldProps}.\n * @example\n * ```\n * <SearchResultGroupSelectFilterField\n * id=\"lifecycle\"\n * label=\"Lifecycle\"\n * value={filters.lifecycle}\n * onChange={handleChangeFilter}\n * onDelete={handleDeleteFilter}\n * >\n * <MenuItem value=\"experimental\">Experimental</MenuItem>\n * <MenuItem value=\"production\">Production</MenuItem>\n * </SearchResultGroupSelectFilterField>\n * ```\n * @public\n */\nexport const SearchResultGroupSelectFilterField = (\n props: SearchResultGroupSelectFilterFieldProps,\n) => {\n const classes = useSearchResultGroupSelectFilterStyles();\n const { label, value = 'none', onChange, onDelete, children } = props;\n\n const handleChange = useCallback(\n (e: ChangeEvent<{ value: unknown }>) => {\n onChange(e.target.value as JsonValue);\n },\n [onChange],\n );\n\n return (\n <SearchResultGroupFilterFieldLayout label={label} onDelete={onDelete}>\n <Select\n className={classes.root}\n value={value}\n onChange={handleChange}\n input={<InputBase />}\n IconComponent={NullIcon}\n >\n <MenuItem value=\"none\">None</MenuItem>\n {children}\n </Select>\n </SearchResultGroupFilterFieldLayout>\n );\n};\n\n/**\n * Props for {@link SearchResultGroupLayout}\n * @public\n */\nexport type SearchResultGroupLayoutProps<FilterOption> = ListProps & {\n /**\n * If defined, will render a default error panel.\n */\n error?: Error;\n /**\n * If defined, will render a default loading progress.\n */\n loading?: boolean;\n /**\n * Icon that representing a result group.\n */\n icon: JSX.Element;\n /**\n * The results group title content, it could be a text or an element.\n */\n title: ReactNode;\n /**\n * Props for the results group title.\n */\n titleProps?: Partial<TypographyProps>;\n /**\n * The results group link content, it could be a text or an element.\n */\n link?: ReactNode;\n /**\n * Props for the results group link, the \"to\" prop defaults to \"/search\".\n */\n linkProps?: Partial<LinkProps>;\n /**\n * A generic filter options that is rendered on the \"Add filter\" dropdown.\n */\n filterOptions?: FilterOption[];\n /**\n * Function to customize how filter options are rendered.\n * @remarks Defaults to a menu item where its value and label bounds to the option string.\n */\n renderFilterOption?: (\n value: FilterOption,\n index: number,\n array: FilterOption[],\n ) => JSX.Element | null;\n /**\n * A list of search filter keys, also known as filter field names.\n */\n filterFields?: string[];\n /**\n * Function to customize how filter chips are rendered.\n */\n renderFilterField?: (key: string) => JSX.Element | null;\n /**\n * Search results to be rendered as a group.\n */\n resultItems?: SearchResult[];\n /**\n * Function to customize how result items are rendered.\n */\n renderResultItem?: (\n value: SearchResult,\n index: number,\n array: SearchResult[],\n ) => JSX.Element | null;\n /**\n * Optional component to render when no results. Default to <EmptyState /> component.\n */\n noResultsComponent?: ReactNode;\n /**\n * Optional property to provide if component should not render the component when no results are found.\n */\n disableRenderingWithNoResults?: boolean;\n};\n\n/**\n * Default layout for rendering search results in a group.\n * @param props - See {@link SearchResultGroupLayoutProps}.\n * @public\n */\nexport function SearchResultGroupLayout<FilterOption>(\n props: SearchResultGroupLayoutProps<FilterOption>,\n) {\n const classes = useStyles();\n const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);\n const { t } = useTranslationRef(searchReactTranslationRef);\n\n const {\n error,\n loading,\n icon,\n title,\n titleProps = {},\n link = (\n <>\n {t('searchResultGroup.linkTitle')}\n <ArrowRightIcon className={classes.listSubheaderLinkIcon} />\n </>\n ),\n linkProps = {},\n filterOptions,\n renderFilterOption = filterOption => (\n <MenuItem key={String(filterOption)} value={String(filterOption)}>\n {String(filterOption)}\n </MenuItem>\n ),\n filterFields,\n renderFilterField,\n resultItems,\n renderResultItem = resultItem => (\n <DefaultResultListItem\n key={resultItem.document.location}\n result={resultItem.document}\n />\n ),\n disableRenderingWithNoResults,\n noResultsComponent = disableRenderingWithNoResults ? null : (\n <EmptyState missing=\"data\" title={t('noResultsDescription')} />\n ),\n ...rest\n } = props;\n\n const handleClick = useCallback((e: MouseEvent<HTMLButtonElement>) => {\n setAnchorEl(e.currentTarget);\n }, []);\n\n const handleClose = useCallback(() => {\n setAnchorEl(null);\n }, []);\n\n if (loading) {\n return <Progress />;\n }\n\n if (error) {\n return (\n <ResponseErrorPanel\n title=\"Error encountered while fetching search results\"\n error={error}\n />\n );\n }\n\n if (!resultItems?.length) {\n return <>{noResultsComponent}</>;\n }\n\n return (\n <List {...rest}>\n <ListSubheader className={classes.listSubheader}>\n {icon}\n <Typography\n className={classes.listSubheaderName}\n component=\"strong\"\n {...titleProps}\n >\n {title}\n </Typography>\n {filterOptions ? (\n <Chip\n className={classes.listSubheaderChip}\n component=\"button\"\n icon={<AddIcon />}\n variant=\"outlined\"\n label={t('searchResultGroup.addFilterButtonTitle')}\n aria-controls=\"filters-menu\"\n aria-haspopup=\"true\"\n onClick={handleClick}\n />\n ) : null}\n {filterOptions ? (\n <Menu\n id=\"filters-menu\"\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={handleClose}\n onClick={handleClose}\n keepMounted\n >\n {filterOptions.map(renderFilterOption)}\n </Menu>\n ) : null}\n {filterFields?.map(\n filterField => renderFilterField?.(filterField) ?? null,\n )}\n <Link className={classes.listSubheaderLink} to=\"/search\" {...linkProps}>\n {link}\n </Link>\n </ListSubheader>\n {resultItems.map(renderResultItem)}\n </List>\n );\n}\n\n/**\n * Props for {@link SearchResultGroup}.\n * @public\n */\nexport type SearchResultGroupProps<FilterOption> = Pick<\n SearchResultStateProps,\n 'query'\n> &\n Omit<\n SearchResultGroupLayoutProps<FilterOption>,\n 'loading' | 'error' | 'resultItems' | 'filterFields'\n >;\n\n/**\n * Given a query, search for results and render them as a group.\n * @param props - See {@link SearchResultGroupProps}.\n * @public\n */\nexport function SearchResultGroup<FilterOption>(\n props: SearchResultGroupProps<FilterOption>,\n) {\n const { query, children, renderResultItem, linkProps = {}, ...rest } = props;\n\n const defaultRenderResultItem = useSearchResultListItemExtensions(children);\n\n return (\n <AnalyticsContext\n attributes={{\n pluginId: 'search',\n extension: 'SearchResultGroup',\n }}\n >\n <SearchResultState query={query}>\n {(\n { loading, error, value },\n { term, types, pageCursor, filters = {} },\n ) => {\n const to = `/search?${qs.stringify(\n { term, types, filters, pageCursor, query: term },\n { arrayFormat: 'brackets' },\n )}`;\n\n return (\n <SearchResultGroupLayout\n {...rest}\n error={error}\n loading={loading}\n linkProps={{ to, ...linkProps }}\n filterFields={Object.keys(filters)}\n resultItems={value?.results}\n renderResultItem={renderResultItem ?? defaultRenderResultItem}\n />\n );\n }}\n </SearchResultState>\n </AnalyticsContext>\n );\n}\n"],"names":["DefaultResultListItem"],"mappings":"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"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import List from '@material-ui/core/List';
|
|
3
|
-
import { Progress, ResponseErrorPanel
|
|
3
|
+
import { EmptyState, Progress, ResponseErrorPanel } from '@backstage/core-components';
|
|
4
4
|
import { AnalyticsContext } from '@backstage/core-plugin-api';
|
|
5
5
|
import { useSearchResultListItemExtensions } from '../../extensions.esm.js';
|
|
6
6
|
import { DefaultResultListItem as HigherOrderDefaultResultListItem } from '../DefaultResultListItem/DefaultResultListItem.esm.js';
|
|
7
7
|
import { SearchResultState } from '../SearchResult/SearchResult.esm.js';
|
|
8
|
+
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
|
9
|
+
import { searchReactTranslationRef } from '../../translation.esm.js';
|
|
8
10
|
|
|
9
11
|
const SearchResultListLayout = (props) => {
|
|
12
|
+
const { t } = useTranslationRef(searchReactTranslationRef);
|
|
10
13
|
const {
|
|
11
14
|
error,
|
|
12
15
|
loading,
|
|
@@ -19,7 +22,7 @@ const SearchResultListLayout = (props) => {
|
|
|
19
22
|
resultItem.document.location
|
|
20
23
|
),
|
|
21
24
|
disableRenderingWithNoResults,
|
|
22
|
-
noResultsComponent = disableRenderingWithNoResults ? null : /* @__PURE__ */ jsx(EmptyState, { missing: "data", title: "
|
|
25
|
+
noResultsComponent = disableRenderingWithNoResults ? null : /* @__PURE__ */ jsx(EmptyState, { missing: "data", title: t("noResultsDescription") }),
|
|
23
26
|
...rest
|
|
24
27
|
} = props;
|
|
25
28
|
if (loading) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchResultList.esm.js","sources":["../../../src/components/SearchResultList/SearchResultList.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactNode } from 'react';\n\nimport List, { ListProps } from '@material-ui/core/List';\n\nimport {\n Progress,\n EmptyState,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { AnalyticsContext } from '@backstage/core-plugin-api';\nimport { SearchResult } from '@backstage/plugin-search-common';\n\nimport { useSearchResultListItemExtensions } from '../../extensions';\n\nimport { DefaultResultListItem } from '../DefaultResultListItem';\nimport { SearchResultState, SearchResultStateProps } from '../SearchResult';\n\n/**\n * Props for {@link SearchResultListLayout}\n * @public\n */\nexport type SearchResultListLayoutProps = ListProps & {\n /**\n * If defined, will render a default error panel.\n */\n error?: Error;\n /**\n * If defined, will render a default loading progress.\n */\n loading?: boolean;\n /**\n * Search results to be rendered as a list.\n */\n resultItems?: SearchResult[];\n /**\n * Function to customize how result items are rendered.\n */\n renderResultItem?: (\n value: SearchResult,\n index: number,\n array: SearchResult[],\n ) => JSX.Element | null;\n /**\n * Optional component to render when no results. Default to <EmptyState /> component.\n */\n noResultsComponent?: ReactNode;\n /**\n * Optional property to provide if component should not render the component when no results are found.\n */\n disableRenderingWithNoResults?: boolean;\n};\n\n/**\n * Default layout for rendering search results in a list.\n * @param props - See {@link SearchResultListLayoutProps}.\n * @public\n */\nexport const SearchResultListLayout = (props: SearchResultListLayoutProps) => {\n const {\n error,\n loading,\n resultItems,\n renderResultItem = resultItem => (\n <DefaultResultListItem\n key={resultItem.document.location}\n result={resultItem.document}\n />\n ),\n disableRenderingWithNoResults,\n noResultsComponent = disableRenderingWithNoResults ? null : (\n <EmptyState missing=\"data\" title
|
|
1
|
+
{"version":3,"file":"SearchResultList.esm.js","sources":["../../../src/components/SearchResultList/SearchResultList.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactNode } from 'react';\n\nimport List, { ListProps } from '@material-ui/core/List';\n\nimport {\n Progress,\n EmptyState,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { AnalyticsContext } from '@backstage/core-plugin-api';\nimport { SearchResult } from '@backstage/plugin-search-common';\n\nimport { useSearchResultListItemExtensions } from '../../extensions';\n\nimport { DefaultResultListItem } from '../DefaultResultListItem';\nimport { SearchResultState, SearchResultStateProps } from '../SearchResult';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\nimport { searchReactTranslationRef } from '../../translation';\n\n/**\n * Props for {@link SearchResultListLayout}\n * @public\n */\nexport type SearchResultListLayoutProps = ListProps & {\n /**\n * If defined, will render a default error panel.\n */\n error?: Error;\n /**\n * If defined, will render a default loading progress.\n */\n loading?: boolean;\n /**\n * Search results to be rendered as a list.\n */\n resultItems?: SearchResult[];\n /**\n * Function to customize how result items are rendered.\n */\n renderResultItem?: (\n value: SearchResult,\n index: number,\n array: SearchResult[],\n ) => JSX.Element | null;\n /**\n * Optional component to render when no results. Default to <EmptyState /> component.\n */\n noResultsComponent?: ReactNode;\n /**\n * Optional property to provide if component should not render the component when no results are found.\n */\n disableRenderingWithNoResults?: boolean;\n};\n\n/**\n * Default layout for rendering search results in a list.\n * @param props - See {@link SearchResultListLayoutProps}.\n * @public\n */\nexport const SearchResultListLayout = (props: SearchResultListLayoutProps) => {\n const { t } = useTranslationRef(searchReactTranslationRef);\n const {\n error,\n loading,\n resultItems,\n renderResultItem = resultItem => (\n <DefaultResultListItem\n key={resultItem.document.location}\n result={resultItem.document}\n />\n ),\n disableRenderingWithNoResults,\n noResultsComponent = disableRenderingWithNoResults ? null : (\n <EmptyState missing=\"data\" title={t('noResultsDescription')} />\n ),\n ...rest\n } = props;\n\n if (loading) {\n return <Progress />;\n }\n\n if (error) {\n return (\n <ResponseErrorPanel\n title=\"Error encountered while fetching search results\"\n error={error}\n />\n );\n }\n\n if (!resultItems?.length) {\n return <>{noResultsComponent}</>;\n }\n\n return <List {...rest}>{resultItems.map(renderResultItem)}</List>;\n};\n\n/**\n * Props for {@link SearchResultList}.\n * @public\n */\nexport type SearchResultListProps = Pick<SearchResultStateProps, 'query'> &\n Omit<SearchResultListLayoutProps, 'loading' | 'error' | 'resultItems'>;\n\n/**\n * Given a query, search for results and render them as a list.\n * @param props - See {@link SearchResultListProps}.\n * @public\n */\nexport const SearchResultList = (props: SearchResultListProps) => {\n const { query, renderResultItem, children, ...rest } = props;\n\n const defaultRenderResultItem = useSearchResultListItemExtensions(children);\n\n return (\n <AnalyticsContext\n attributes={{\n pluginId: 'search',\n extension: 'SearchResultList',\n }}\n >\n <SearchResultState query={query}>\n {({ loading, error, value }) => (\n <SearchResultListLayout\n {...rest}\n error={error}\n loading={loading}\n resultItems={value?.results}\n renderResultItem={renderResultItem ?? defaultRenderResultItem}\n />\n )}\n </SearchResultState>\n </AnalyticsContext>\n );\n};\n"],"names":["DefaultResultListItem"],"mappings":";;;;;;;;;;AA2Ea,MAAA,sBAAA,GAAyB,CAAC,KAAuC,KAAA;AAC5E,EAAA,MAAM,EAAE,CAAA,EAAM,GAAA,iBAAA,CAAkB,yBAAyB,CAAA;AACzD,EAAM,MAAA;AAAA,IACJ,KAAA;AAAA,IACA,OAAA;AAAA,IACA,WAAA;AAAA,IACA,mBAAmB,CACjB,UAAA,qBAAA,GAAA;AAAA,MAACA,gCAAA;AAAA,MAAA;AAAA,QAEC,QAAQ,UAAW,CAAA;AAAA,OAAA;AAAA,MADd,WAAW,QAAS,CAAA;AAAA,KAE3B;AAAA,IAEF,6BAAA;AAAA,IACA,kBAAA,GAAqB,6BAAgC,GAAA,IAAA,mBAClD,GAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,MAAO,EAAA,KAAA,EAAO,CAAE,CAAA,sBAAsB,CAAG,EAAA,CAAA;AAAA,IAE/D,GAAG;AAAA,GACD,GAAA,KAAA;AAEJ,EAAA,IAAI,OAAS,EAAA;AACX,IAAA,2BAAQ,QAAS,EAAA,EAAA,CAAA;AAAA;AAGnB,EAAA,IAAI,KAAO,EAAA;AACT,IACE,uBAAA,GAAA;AAAA,MAAC,kBAAA;AAAA,MAAA;AAAA,QACC,KAAM,EAAA,iDAAA;AAAA,QACN;AAAA;AAAA,KACF;AAAA;AAIJ,EAAI,IAAA,CAAC,aAAa,MAAQ,EAAA;AACxB,IAAA,uCAAU,QAAmB,EAAA,kBAAA,EAAA,CAAA;AAAA;AAG/B,EAAA,2BAAQ,IAAM,EAAA,EAAA,GAAG,MAAO,QAAY,EAAA,WAAA,CAAA,GAAA,CAAI,gBAAgB,CAAE,EAAA,CAAA;AAC5D;AAca,MAAA,gBAAA,GAAmB,CAAC,KAAiC,KAAA;AAChE,EAAA,MAAM,EAAE,KAAO,EAAA,gBAAA,EAAkB,QAAU,EAAA,GAAG,MAAS,GAAA,KAAA;AAEvD,EAAM,MAAA,uBAAA,GAA0B,kCAAkC,QAAQ,CAAA;AAE1E,EACE,uBAAA,GAAA;AAAA,IAAC,gBAAA;AAAA,IAAA;AAAA,MACC,UAAY,EAAA;AAAA,QACV,QAAU,EAAA,QAAA;AAAA,QACV,SAAW,EAAA;AAAA,OACb;AAAA,MAEA,QAAA,kBAAA,GAAA,CAAC,qBAAkB,KAChB,EAAA,QAAA,EAAA,CAAC,EAAE,OAAS,EAAA,KAAA,EAAO,OAClB,qBAAA,GAAA;AAAA,QAAC,sBAAA;AAAA,QAAA;AAAA,UACE,GAAG,IAAA;AAAA,UACJ,KAAA;AAAA,UACA,OAAA;AAAA,UACA,aAAa,KAAO,EAAA,OAAA;AAAA,UACpB,kBAAkB,gBAAoB,IAAA;AAAA;AAAA,OAG5C,EAAA;AAAA;AAAA,GACF;AAEJ;;;;"}
|
|
@@ -4,6 +4,8 @@ import { makeStyles } from '@material-ui/core/styles';
|
|
|
4
4
|
import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
|
|
5
5
|
import ArrowRightIcon from '@material-ui/icons/ArrowForwardIos';
|
|
6
6
|
import { useSearch } from '../../context/SearchContext.esm.js';
|
|
7
|
+
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
|
8
|
+
import { searchReactTranslationRef } from '../../translation.esm.js';
|
|
7
9
|
|
|
8
10
|
const useStyles = makeStyles((theme) => ({
|
|
9
11
|
root: {
|
|
@@ -16,6 +18,7 @@ const useStyles = makeStyles((theme) => ({
|
|
|
16
18
|
const SearchResultPager = () => {
|
|
17
19
|
const { fetchNextPage, fetchPreviousPage } = useSearch();
|
|
18
20
|
const classes = useStyles();
|
|
21
|
+
const { t } = useTranslationRef(searchReactTranslationRef);
|
|
19
22
|
if (!fetchNextPage && !fetchPreviousPage) {
|
|
20
23
|
return /* @__PURE__ */ jsx(Fragment, {});
|
|
21
24
|
}
|
|
@@ -27,7 +30,7 @@ const SearchResultPager = () => {
|
|
|
27
30
|
disabled: !fetchPreviousPage,
|
|
28
31
|
onClick: fetchPreviousPage,
|
|
29
32
|
startIcon: /* @__PURE__ */ jsx(ArrowBackIosIcon, {}),
|
|
30
|
-
children: "
|
|
33
|
+
children: t("searchResultPager.previous")
|
|
31
34
|
}
|
|
32
35
|
),
|
|
33
36
|
/* @__PURE__ */ jsx(
|
|
@@ -37,7 +40,7 @@ const SearchResultPager = () => {
|
|
|
37
40
|
disabled: !fetchNextPage,
|
|
38
41
|
onClick: fetchNextPage,
|
|
39
42
|
endIcon: /* @__PURE__ */ jsx(ArrowRightIcon, {}),
|
|
40
|
-
children: "
|
|
43
|
+
children: t("searchResultPager.next")
|
|
41
44
|
}
|
|
42
45
|
)
|
|
43
46
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchResultPager.esm.js","sources":["../../../src/components/SearchResultPager/SearchResultPager.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Button from '@material-ui/core/Button';\nimport { makeStyles } from '@material-ui/core/styles';\nimport ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';\nimport ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';\n\nimport { useSearch } from '../../context';\n\nconst useStyles = makeStyles(theme => ({\n root: {\n display: 'flex',\n justifyContent: 'space-between',\n gap: theme.spacing(2),\n margin: theme.spacing(2, 0),\n },\n}));\n\n/**\n * @public\n */\nexport const SearchResultPager = () => {\n const { fetchNextPage, fetchPreviousPage } = useSearch();\n const classes = useStyles();\n\n if (!fetchNextPage && !fetchPreviousPage) {\n return <></>;\n }\n\n return (\n <nav aria-label=\"pagination navigation\" className={classes.root}>\n <Button\n aria-label=\"previous page\"\n disabled={!fetchPreviousPage}\n onClick={fetchPreviousPage}\n startIcon={<ArrowBackIosIcon />}\n >\n
|
|
1
|
+
{"version":3,"file":"SearchResultPager.esm.js","sources":["../../../src/components/SearchResultPager/SearchResultPager.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Button from '@material-ui/core/Button';\nimport { makeStyles } from '@material-ui/core/styles';\nimport ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';\nimport ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';\n\nimport { useSearch } from '../../context';\nimport { useTranslationRef } from '@backstage/frontend-plugin-api';\nimport { searchReactTranslationRef } from '../../translation';\n\nconst useStyles = makeStyles(theme => ({\n root: {\n display: 'flex',\n justifyContent: 'space-between',\n gap: theme.spacing(2),\n margin: theme.spacing(2, 0),\n },\n}));\n\n/**\n * @public\n */\nexport const SearchResultPager = () => {\n const { fetchNextPage, fetchPreviousPage } = useSearch();\n const classes = useStyles();\n const { t } = useTranslationRef(searchReactTranslationRef);\n\n if (!fetchNextPage && !fetchPreviousPage) {\n return <></>;\n }\n\n return (\n <nav aria-label=\"pagination navigation\" className={classes.root}>\n <Button\n aria-label=\"previous page\"\n disabled={!fetchPreviousPage}\n onClick={fetchPreviousPage}\n startIcon={<ArrowBackIosIcon />}\n >\n {t('searchResultPager.previous')}\n </Button>\n\n <Button\n aria-label=\"next page\"\n disabled={!fetchNextPage}\n onClick={fetchNextPage}\n endIcon={<ArrowForwardIosIcon />}\n >\n {t('searchResultPager.next')}\n </Button>\n </nav>\n );\n};\n"],"names":["ArrowForwardIosIcon"],"mappings":";;;;;;;;;AAyBA,MAAM,SAAA,GAAY,WAAW,CAAU,KAAA,MAAA;AAAA,EACrC,IAAM,EAAA;AAAA,IACJ,OAAS,EAAA,MAAA;AAAA,IACT,cAAgB,EAAA,eAAA;AAAA,IAChB,GAAA,EAAK,KAAM,CAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACpB,MAAQ,EAAA,KAAA,CAAM,OAAQ,CAAA,CAAA,EAAG,CAAC;AAAA;AAE9B,CAAE,CAAA,CAAA;AAKK,MAAM,oBAAoB,MAAM;AACrC,EAAA,MAAM,EAAE,aAAA,EAAe,iBAAkB,EAAA,GAAI,SAAU,EAAA;AACvD,EAAA,MAAM,UAAU,SAAU,EAAA;AAC1B,EAAA,MAAM,EAAE,CAAA,EAAM,GAAA,iBAAA,CAAkB,yBAAyB,CAAA;AAEzD,EAAI,IAAA,CAAC,aAAiB,IAAA,CAAC,iBAAmB,EAAA;AACxC,IAAA,uBAAS,GAAA,CAAA,QAAA,EAAA,EAAA,CAAA;AAAA;AAGX,EAAA,4BACG,KAAI,EAAA,EAAA,YAAA,EAAW,uBAAwB,EAAA,SAAA,EAAW,QAAQ,IACzD,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,YAAW,EAAA,eAAA;AAAA,QACX,UAAU,CAAC,iBAAA;AAAA,QACX,OAAS,EAAA,iBAAA;AAAA,QACT,SAAA,sBAAY,gBAAiB,EAAA,EAAA,CAAA;AAAA,QAE5B,YAAE,4BAA4B;AAAA;AAAA,KACjC;AAAA,oBAEA,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,YAAW,EAAA,WAAA;AAAA,QACX,UAAU,CAAC,aAAA;AAAA,QACX,OAAS,EAAA,aAAA;AAAA,QACT,OAAA,sBAAUA,cAAoB,EAAA,EAAA,CAAA;AAAA,QAE7B,YAAE,wBAAwB;AAAA;AAAA;AAC7B,GACF,EAAA,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createTranslationRef } from '@backstage/core-plugin-api/alpha';
|
|
2
|
+
|
|
3
|
+
const searchReactTranslationRef = createTranslationRef({
|
|
4
|
+
id: "search-react",
|
|
5
|
+
messages: {
|
|
6
|
+
searchBar: {
|
|
7
|
+
title: "Search",
|
|
8
|
+
placeholder: "Search in {{org}}"
|
|
9
|
+
},
|
|
10
|
+
searchFilter: {
|
|
11
|
+
allOptionTitle: "All"
|
|
12
|
+
},
|
|
13
|
+
searchPagination: {
|
|
14
|
+
limitLabel: "Results per page:",
|
|
15
|
+
limitText: "of {{num}}"
|
|
16
|
+
},
|
|
17
|
+
noResultsDescription: "Sorry, no results were found",
|
|
18
|
+
searchResultGroup: {
|
|
19
|
+
linkTitle: "See All",
|
|
20
|
+
addFilterButtonTitle: "Add filter"
|
|
21
|
+
},
|
|
22
|
+
searchResultPager: {
|
|
23
|
+
previous: "Previous",
|
|
24
|
+
next: "Next"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export { searchReactTranslationRef };
|
|
30
|
+
//# sourceMappingURL=translation.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translation.esm.js","sources":["../src/translation.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createTranslationRef } from '@backstage/core-plugin-api/alpha';\n\n/**\n * @alpha\n */\nexport const searchReactTranslationRef = createTranslationRef({\n id: 'search-react',\n messages: {\n searchBar: {\n title: 'Search',\n placeholder: 'Search in {{org}}',\n },\n searchFilter: {\n allOptionTitle: 'All',\n },\n searchPagination: {\n limitLabel: 'Results per page:',\n limitText: 'of {{num}}',\n },\n noResultsDescription: 'Sorry, no results were found',\n searchResultGroup: {\n linkTitle: 'See All',\n addFilterButtonTitle: 'Add filter',\n },\n searchResultPager: {\n previous: 'Previous',\n next: 'Next',\n },\n },\n});\n"],"names":[],"mappings":";;AAqBO,MAAM,4BAA4B,oBAAqB,CAAA;AAAA,EAC5D,EAAI,EAAA,cAAA;AAAA,EACJ,QAAU,EAAA;AAAA,IACR,SAAW,EAAA;AAAA,MACT,KAAO,EAAA,QAAA;AAAA,MACP,WAAa,EAAA;AAAA,KACf;AAAA,IACA,YAAc,EAAA;AAAA,MACZ,cAAgB,EAAA;AAAA,KAClB;AAAA,IACA,gBAAkB,EAAA;AAAA,MAChB,UAAY,EAAA,mBAAA;AAAA,MACZ,SAAW,EAAA;AAAA,KACb;AAAA,IACA,oBAAsB,EAAA,8BAAA;AAAA,IACtB,iBAAmB,EAAA;AAAA,MACjB,SAAW,EAAA,SAAA;AAAA,MACX,oBAAsB,EAAA;AAAA,KACxB;AAAA,IACA,iBAAmB,EAAA;AAAA,MACjB,QAAU,EAAA,UAAA;AAAA,MACV,IAAM,EAAA;AAAA;AACR;AAEJ,CAAC;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-search-react",
|
|
3
|
-
"version": "1.9.0-next.
|
|
3
|
+
"version": "1.9.0-next.2",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "web-library",
|
|
6
6
|
"pluginId": "search",
|
|
@@ -80,11 +80,11 @@
|
|
|
80
80
|
"uuid": "^11.0.2"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@backstage/cli": "0.32.1-next.
|
|
84
|
-
"@backstage/core-app-api": "1.
|
|
85
|
-
"@backstage/frontend-app-api": "0.11.2-next.
|
|
86
|
-
"@backstage/frontend-test-utils": "0.3.2-next.
|
|
87
|
-
"@backstage/test-utils": "1.7.8-next.
|
|
83
|
+
"@backstage/cli": "0.32.1-next.3",
|
|
84
|
+
"@backstage/core-app-api": "1.17.0-next.1",
|
|
85
|
+
"@backstage/frontend-app-api": "0.11.2-next.3",
|
|
86
|
+
"@backstage/frontend-test-utils": "0.3.2-next.3",
|
|
87
|
+
"@backstage/test-utils": "1.7.8-next.2",
|
|
88
88
|
"@testing-library/dom": "^10.0.0",
|
|
89
89
|
"@testing-library/jest-dom": "^6.0.0",
|
|
90
90
|
"@testing-library/react": "^16.0.0",
|