saleor-dashboard/src/components/AutocompleteSelectMenu/AutocompleteSelectMenu.tsx

188 lines
5.7 KiB
TypeScript
Raw Normal View History

2019-06-19 14:40:52 +00:00
import CircularProgress from "@material-ui/core/CircularProgress";
import MenuItem from "@material-ui/core/MenuItem";
import Paper from "@material-ui/core/Paper";
2019-10-30 14:34:24 +00:00
import { makeStyles } from "@material-ui/core/styles";
2019-06-19 14:40:52 +00:00
import TextField from "@material-ui/core/TextField";
import ArrowBack from "@material-ui/icons/ArrowBack";
import Downshift from "downshift";
2019-08-09 10:26:22 +00:00
import React from "react";
import { FormattedMessage } from "react-intl";
2019-06-19 14:40:52 +00:00
import { buttonMessages } from "@saleor/intl";
2019-06-19 14:40:52 +00:00
import {
getMenuItemByPath,
IMenu,
validateMenuOptions
} from "../../utils/menu";
import Debounce, { DebounceProps } from "../Debounce";
export interface AutocompleteSelectMenuProps {
disabled: boolean;
displayValue: string;
error: boolean;
helperText: string;
label: string;
loading: boolean;
name: string;
options: IMenu;
placeholder: string;
onChange: (event: React.ChangeEvent<any>) => void;
onInputChange?: (value: string) => void;
}
const validationError: Error = new Error(
"Values supplied to AutocompleteSelectMenu should be unique"
);
const DebounceAutocomplete: React.ComponentType<
DebounceProps<string>
> = Debounce;
2019-10-30 14:34:24 +00:00
const useStyles = makeStyles(theme => ({
container: {
flexGrow: 1,
position: "relative"
},
menuBack: {
marginLeft: -theme.spacing(0.5),
marginRight: theme.spacing(1)
},
paper: {
left: 0,
marginTop: theme.spacing(),
padding: theme.spacing(),
position: "absolute",
right: 0,
zIndex: 2
},
root: {}
}));
const AutocompleteSelectMenu: React.FC<AutocompleteSelectMenuProps> = props => {
const {
2019-06-19 14:40:52 +00:00
disabled,
displayValue,
error,
helperText,
label,
loading,
name,
options,
placeholder,
onChange,
onInputChange
2019-10-30 14:34:24 +00:00
} = props;
const classes = useStyles(props);
2019-06-19 14:40:52 +00:00
2019-10-30 14:34:24 +00:00
const [inputValue, setInputValue] = React.useState(displayValue || "");
const [menuPath, setMenuPath] = React.useState<number[]>([]);
2019-06-19 14:40:52 +00:00
2019-10-30 14:34:24 +00:00
const handleChange = (value: string) =>
onChange({
target: {
name,
value
2019-06-19 14:40:52 +00:00
}
2019-10-30 14:34:24 +00:00
} as any);
2019-06-19 14:40:52 +00:00
2019-10-30 14:34:24 +00:00
// Validate if option values are duplicated
React.useEffect(() => {
if (!validateMenuOptions(options)) {
throw validationError;
}
}, []);
2019-06-19 14:40:52 +00:00
2019-10-30 14:34:24 +00:00
// Navigate back to main menu after input field change
React.useEffect(() => setMenuPath([]), [options]);
2019-06-19 14:40:52 +00:00
2019-10-30 14:34:24 +00:00
// Reset input value after displayValue change
React.useEffect(() => setInputValue(displayValue), [displayValue]);
return (
<DebounceAutocomplete debounceFn={onInputChange}>
{debounceFn => (
<Downshift
itemToString={item => (item ? item.label : "")}
onSelect={handleChange}
>
{({ getItemProps, isOpen, openMenu, closeMenu, selectItem }) => {
return (
<div className={classes.container}>
<TextField
InputProps={{
endAdornment: loading && <CircularProgress size={16} />,
id: undefined,
onBlur: () => {
closeMenu();
setMenuPath([]);
setInputValue(displayValue);
},
onChange: event => {
debounceFn(event.target.value);
setInputValue(event.target.value);
},
onFocus: () => openMenu(),
placeholder
}}
disabled={disabled}
error={error}
helperText={helperText}
label={label}
fullWidth={true}
value={inputValue}
/>
{isOpen && (
<Paper className={classes.paper} square>
{options.length ? (
<>
{menuPath.length > 0 && (
<MenuItem
component="div"
{...getItemProps({
item: null
})}
onClick={() =>
setMenuPath(
menuPath.slice(0, menuPath.length - 2)
)
}
>
<ArrowBack className={classes.menuBack} />
<FormattedMessage {...buttonMessages.back} />
</MenuItem>
)}
{(menuPath.length
? getMenuItemByPath(options, menuPath).children
: options
).map((suggestion, index) => (
<MenuItem
key={suggestion.value}
component="div"
{...getItemProps({ item: suggestion })}
onClick={() =>
suggestion.value
? selectItem(suggestion.value)
: setMenuPath([...menuPath, index])
}
>
{suggestion.label}
</MenuItem>
))}
</>
) : (
<MenuItem disabled component="div">
<FormattedMessage defaultMessage="No results" />
</MenuItem>
)}
</Paper>
)}
</div>
);
}}
</Downshift>
)}
</DebounceAutocomplete>
);
};
2019-06-19 14:40:52 +00:00
AutocompleteSelectMenu.displayName = "AutocompleteSelectMenu";
export default AutocompleteSelectMenu;