import CircularProgress from "@material-ui/core/CircularProgress"; import IconButton from "@material-ui/core/IconButton"; import MenuItem from "@material-ui/core/MenuItem"; import Paper from "@material-ui/core/Paper"; import { Theme } from "@material-ui/core/styles"; import { fade } from "@material-ui/core/styles/colorManipulator"; import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; import CloseIcon from "@material-ui/icons/Close"; import makeStyles from "@material-ui/styles/makeStyles"; import Downshift, { ControllerStateAndHelpers } from "downshift"; import React from "react"; import { compareTwoStrings } from "string-similarity"; import Checkbox from "../Checkbox"; import Hr from "../Hr"; import ArrowDropdownIcon from "../icons/ArrowDropdown"; import Debounce, { DebounceProps } from "../utils/Debounce"; export interface MultiAutocompleteChoiceType { label: string; value: string; } const useStyles = makeStyles((theme: Theme) => ({ chip: { width: "100%" }, chipClose: { height: 32, padding: 0, width: 32 }, chipContainer: { display: "flex", flexDirection: "column", marginTop: theme.spacing() }, chipInner: { "& svg": { color: theme.palette.primary.contrastText, marginTop: -theme.spacing(0.5) }, alignItems: "center", background: fade(theme.palette.primary.main, 0.6), borderRadius: 24, color: theme.palette.primary.contrastText, display: "flex", justifyContent: "space-between", margin: theme.spacing(1, 0), paddingLeft: theme.spacing(2), paddingRight: theme.spacing() }, chipLabel: { color: `${theme.palette.primary.contrastText} !important` }, container: { flexGrow: 1, position: "relative" }, hr: { margin: theme.spacing(1, 0) }, paper: { left: 0, marginTop: theme.spacing(), padding: theme.spacing(), position: "absolute", right: 0, zIndex: 2 } })); export type MultiAutocompleteSelectFieldOptionalLabelKeys = | "label" | "helperText" | "placeholder"; export type MultiAutocompleteSelectFieldRequiredLabelKeys = "noResultsLabel"; export type MultiAutocompleteSelectFieldLabels = Record< MultiAutocompleteSelectFieldRequiredLabelKeys, string > & Partial< Record & { addCustomValueText: (value: string) => React.ReactNode; } >; export interface MultiAutocompleteSelectFieldProps extends MultiAutocompleteSelectFieldLabels { allowCustomValues?: boolean; displayChips: MultiAutocompleteChoiceType[]; name: string; choices: MultiAutocompleteChoiceType[]; value: string[]; loading?: boolean; fetchChoices?: (value: string) => void; onChange: (event: React.ChangeEvent) => void; } const DebounceAutocomplete: React.ComponentType> = Debounce; export const MultiAutocompleteSelectFieldComponent: React.FC = props => { const { addCustomValueText, allowCustomValues, choices, displayChips, fetchChoices, helperText, label, loading, name, noResultsLabel, placeholder, value, onChange } = props; const classes = useStyles(props); const handleSelect = ( item: string, downshiftOpts?: ControllerStateAndHelpers ) => { if (downshiftOpts) { downshiftOpts.reset({ inputValue: "" }); } onChange({ target: { name, value: item } } as any); }; const suggestions = choices.filter(choice => !value.includes(choice.value)); return ( <> ""} > {({ getInputProps, getItemProps, isOpen, toggleMenu, highlightedIndex, inputValue }) => (
{loading ? ( ) : ( toggleMenu()} /> )}
), id: undefined, onClick: toggleMenu }} helperText={helperText} label={label} fullWidth={true} /> {isOpen && (!!inputValue || !!choices.length) && ( {choices.length > 0 || displayChips.length > 0 || allowCustomValues ? ( <> {displayChips.map(value => ( {value.label} ))} {displayChips.length > 0 && suggestions.length > 0 && (
)} {suggestions.map((suggestion, index) => ( {suggestion.label} ))} {allowCustomValues && inputValue && !choices.find( choice => choice.label.toLowerCase() === inputValue.toLowerCase() ) && ( {addCustomValueText(inputValue)} )} ) : ( !loading && ( {noResultsLabel} ) )}
)} )}
{displayChips.map(value => (
{value.label} handleSelect(value.value)} >
))}
); }; const MultiAutocompleteSelectField: React.FC = ({ choices, fetchChoices, ...props }) => { const [query, setQuery] = React.useState(""); if (fetchChoices) { return ( {debounceFn => ( )} ); } const sortedChoices = choices.sort((a, b) => { const ratingA = compareTwoStrings(query, a.label); const ratingB = compareTwoStrings(query, b.label); if (ratingA > ratingB) { return -1; } if (ratingA < ratingB) { return 1; } return 0; }); return ( setQuery(q || "")} choices={sortedChoices} {...props} /> ); }; MultiAutocompleteSelectField.displayName = "MultiAutocompleteSelectField"; export default MultiAutocompleteSelectField;