Add initial <Search.Autocomplete /> implementation.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import { Chip, TextField } from '@material-ui/core';
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteGetTagProps,
|
||||
AutocompleteRenderInputParams,
|
||||
} from '@material-ui/lab';
|
||||
import { useSearch } from '../SearchContext';
|
||||
import { useAsyncFilterValues } from './hooks';
|
||||
import { SearchFilterComponentProps } from './SearchFilter';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type SearchAutocompleteFilterProps = SearchFilterComponentProps & {
|
||||
multiple?: boolean;
|
||||
limitTags?: number;
|
||||
};
|
||||
|
||||
export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => {
|
||||
const {
|
||||
asyncValues,
|
||||
asyncDebounce,
|
||||
className,
|
||||
multiple,
|
||||
name,
|
||||
values: givenValues,
|
||||
label,
|
||||
limitTags,
|
||||
} = props;
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const { value: values, loading } = useAsyncFilterValues(
|
||||
asyncValues,
|
||||
inputValue,
|
||||
givenValues,
|
||||
asyncDebounce,
|
||||
);
|
||||
const { setFilters } = useSearch();
|
||||
|
||||
// Set new filter values on input change.
|
||||
const handleChange = (
|
||||
_: ChangeEvent<{}>,
|
||||
newValue: string | string[] | null,
|
||||
) => {
|
||||
setFilters(prevState => {
|
||||
const { [name]: filter, ...others } = prevState;
|
||||
|
||||
if (newValue) {
|
||||
return { ...others, [name]: newValue };
|
||||
}
|
||||
return { ...others };
|
||||
});
|
||||
};
|
||||
|
||||
// Provide the input field.
|
||||
const renderInput = (params: AutocompleteRenderInputParams) => (
|
||||
<TextField
|
||||
{...params}
|
||||
name="search"
|
||||
variant="outlined"
|
||||
label={label || name}
|
||||
fullWidth
|
||||
/>
|
||||
);
|
||||
|
||||
// Render tags as primary-colored chips.
|
||||
const renderTags = (
|
||||
tagValue: string[],
|
||||
getTagProps: AutocompleteGetTagProps,
|
||||
) =>
|
||||
tagValue.map((option: string, index: number) => (
|
||||
<Chip label={option} color="primary" {...getTagProps({ index })} />
|
||||
));
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
multiple={multiple}
|
||||
className={className}
|
||||
id={`${multiple ?? 'multi-'}select-filter-${name}--select`}
|
||||
options={values || []}
|
||||
loading={loading}
|
||||
limitTags={limitTags}
|
||||
onChange={handleChange}
|
||||
onInputChange={(_, newValue) => setInputValue(newValue)}
|
||||
renderInput={renderInput}
|
||||
renderTags={renderTags}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -72,3 +72,49 @@ export const AsyncSelectFilter = () => {
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const Autocomplete = () => {
|
||||
return (
|
||||
<Paper style={{ padding: 10 }}>
|
||||
<SearchFilter.Autocomplete
|
||||
name="autocomplete"
|
||||
label="Single-Select Autocomplete Filter"
|
||||
values={['abba', 'cadaver']}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const MultiSelectAutocomplete = () => {
|
||||
return (
|
||||
<Paper style={{ padding: 10 }}>
|
||||
<SearchFilter.Autocomplete
|
||||
multiple
|
||||
name="autocomplete"
|
||||
label="Multi-Select Autocomplete Filter"
|
||||
values={['abba', 'cadaver']}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const AsyncMultiSelectAutocomplete = () => {
|
||||
return (
|
||||
<Paper style={{ padding: 10 }}>
|
||||
<SearchFilter.Autocomplete
|
||||
multiple
|
||||
name="starwarsPerson"
|
||||
label="Starwars Character"
|
||||
asyncValues={async partial => {
|
||||
if (partial === '') return [];
|
||||
const response = await fetch(
|
||||
`https://swapi.dev/api/people?search=${partial}`,
|
||||
);
|
||||
const json: { results: Array<{ name: string }> } =
|
||||
await response.json();
|
||||
return json.results.map(r => r.name);
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,10 @@ import {
|
||||
FormLabel,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import {
|
||||
AutocompleteFilter,
|
||||
SearchAutocompleteFilterProps,
|
||||
} from './SearchFilter.Autocomplete';
|
||||
import { useSearch } from '../SearchContext';
|
||||
import { useAsyncFilterValues } from './hooks';
|
||||
|
||||
@@ -196,6 +200,16 @@ SearchFilter.Select = (
|
||||
SearchFilterComponentProps,
|
||||
) => <SearchFilter {...props} component={SelectFilter} />;
|
||||
|
||||
/**
|
||||
* A control surface for a given filter field name, rendered as an autocomplete
|
||||
* textfield. A hard-coded list of values may be provided, or an async function
|
||||
* which returns values may be provided instead.
|
||||
* @public
|
||||
*/
|
||||
SearchFilter.Autocomplete = (props: SearchAutocompleteFilterProps) => (
|
||||
<SearchFilter {...props} component={AutocompleteFilter} />
|
||||
);
|
||||
|
||||
/**
|
||||
* @deprecated This component was used for rapid prototyping of the Backstage
|
||||
* Search platform. Now that the API has stabilized, you should use the
|
||||
|
||||
@@ -19,3 +19,4 @@ export type {
|
||||
SearchFilterComponentProps,
|
||||
SearchFilterWrapperProps,
|
||||
} from './SearchFilter';
|
||||
export type { SearchAutocompleteFilterProps } from './SearchFilter.Autocomplete';
|
||||
|
||||
@@ -34,6 +34,7 @@ export type {
|
||||
export { SearchContextProvider, useSearch } from './components/SearchContext';
|
||||
export { SearchFilter, SearchFilterNext } from './components/SearchFilter';
|
||||
export type {
|
||||
SearchAutocompleteFilterProps,
|
||||
SearchFilterComponentProps,
|
||||
SearchFilterWrapperProps,
|
||||
} from './components/SearchFilter';
|
||||
|
||||
Reference in New Issue
Block a user