205 lines
5.8 KiB
TypeScript
205 lines
5.8 KiB
TypeScript
import { Autocomplete, Avatar, Box, CircularProgress, createFilterOptions } from "@mui/material";
|
|
import TextField from "@mui/material/TextField";
|
|
import { makeStyles } from "@mui/styles";
|
|
import { isArray } from "lodash";
|
|
import React, { CSSProperties } from "react";
|
|
|
|
const useStyles = makeStyles(() => ({
|
|
input: {
|
|
minWidth: `0 !important`
|
|
}})
|
|
);
|
|
|
|
export interface Option {
|
|
label: string;
|
|
value: any;
|
|
group?: string;
|
|
new?: boolean;
|
|
icon?: string;
|
|
defaultCableID?: number;
|
|
}
|
|
|
|
interface Props {
|
|
selected?: Option | Option[] | null;
|
|
changeSelection?: (newSelected: Option | null) => void;
|
|
changeMulti?: (newSelected: Option[] | null) => void;
|
|
options: Option[];
|
|
label?: string;
|
|
group?: boolean;
|
|
creatable?: boolean;
|
|
loading?: boolean;
|
|
disabled?: boolean;
|
|
multiple?: boolean;
|
|
variant?: "filled" | "outlined" | "standard";
|
|
style?: CSSProperties;
|
|
getOptionSelected?: any;
|
|
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
|
onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
}
|
|
|
|
export default function SearchSelect(props: Props) {
|
|
const {
|
|
options,
|
|
changeSelection,
|
|
changeMulti,
|
|
selected,
|
|
label,
|
|
creatable,
|
|
loading,
|
|
group,
|
|
disabled,
|
|
multiple,
|
|
variant,
|
|
style,
|
|
// getOptionSelected,
|
|
onChange,
|
|
onKeyDown
|
|
} = props;
|
|
const classes = useStyles();
|
|
const filter = createFilterOptions<Option>({
|
|
stringify: (option: Option) => option.label + option.group
|
|
});
|
|
|
|
const stringToOption = (s: string): Option => {
|
|
return { value: s, label: s } as Option;
|
|
};
|
|
|
|
// const anyToLabel = (o: any): string => {
|
|
// if (o.text && o.value) {
|
|
// return o.text + " - " + o.value
|
|
// } else if (o.value) {
|
|
// return o.value
|
|
// } else if (o.label) {
|
|
// return o.label
|
|
// }
|
|
// return o.toString();
|
|
// };
|
|
|
|
const optionSelected = (newValue: string | Option | (string | Option)[] | null) => {
|
|
if (multiple === true) {
|
|
if (changeMulti) {
|
|
if (Array.isArray(newValue)) {
|
|
changeMulti(newValue.map(v => (typeof v === "string" ? stringToOption(v) : v)));
|
|
} else {
|
|
changeMulti(
|
|
newValue
|
|
? [typeof newValue === "string" ? stringToOption(newValue) : newValue]
|
|
: null
|
|
);
|
|
}
|
|
} else {
|
|
console.warn("SearchSelect requires changeMulti to be defined when 'multiple' is true");
|
|
}
|
|
} else {
|
|
if (changeSelection) {
|
|
if (Array.isArray(newValue)) {
|
|
console.warn("changeSelection requires a single element but received an array");
|
|
} else {
|
|
changeSelection(typeof newValue === "string" ? stringToOption(newValue) : newValue);
|
|
}
|
|
} else {
|
|
console.warn(
|
|
"SearchSelect requires changeSelection to be defined when 'multiple' is false"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Autocomplete
|
|
id={label + "-autocomplete"}
|
|
value={selected === undefined ? null : selected}
|
|
multiple={multiple}
|
|
limitTags={2}
|
|
autoHighlight
|
|
disabled={disabled}
|
|
onChange={(_, newValue) => {
|
|
optionSelected(newValue)
|
|
}}
|
|
options={options.sort((a, b) => {
|
|
let aGroup = a.group ? a.group : "";
|
|
let bGroup = b.group ? b.group : "";
|
|
if (bGroup > aGroup) return -1;
|
|
if (aGroup > bGroup) return 1;
|
|
return b.label > a.label ? -1 : 1;
|
|
})}
|
|
filterOptions={(options, params) => {
|
|
const filtered = filter(options as Option[], params);
|
|
|
|
if (creatable && params.inputValue !== "") {
|
|
filtered.push({
|
|
value: params.inputValue,
|
|
label: params.inputValue,
|
|
new: true
|
|
});
|
|
}
|
|
|
|
return filtered;
|
|
}}
|
|
// getOptionLabel={option => {
|
|
// // return anyToLabel(option)
|
|
// // console.log(option)
|
|
// // console.log("Is Option: ",option instanceof Option)
|
|
// if (option instanceof Option) {
|
|
// return option.text + " - " + option.value
|
|
// // } else if (option.text && option.value) {
|
|
|
|
// } else {
|
|
// return anyToLabel(option)
|
|
// }
|
|
// }}
|
|
// getOptionSelected={
|
|
// getOptionSelected ? getOptionSelected : (o: { value: any; }, v: { value: any; }) => (o && v ? o.value === v.value : false)
|
|
// }
|
|
groupBy={group ? option => (option && option.group ? option.group : "") : undefined}
|
|
renderInput={params => (
|
|
<TextField
|
|
{...params}
|
|
label={label}
|
|
variant={variant ? variant : "outlined"}
|
|
onChange={onChange}
|
|
onKeyDown={onKeyDown}
|
|
InputProps={{
|
|
...params.InputProps,
|
|
startAdornment:
|
|
!isArray(selected) && selected?.icon ? <Avatar src={selected?.icon} /> : null,
|
|
endAdornment: (
|
|
<React.Fragment>
|
|
{loading ? <CircularProgress color="inherit" size={20} /> : null}
|
|
{params.InputProps.endAdornment}
|
|
</React.Fragment>
|
|
)
|
|
}}
|
|
/>
|
|
)}
|
|
renderOption={(props, option) => {
|
|
const { key, ...optionProps } = props;
|
|
return (
|
|
<Box
|
|
key={key + ":" + option.value}
|
|
component="li"
|
|
sx={{ '& > img': { mr: 2, flexShrink: 0 } }}
|
|
{...optionProps}
|
|
>
|
|
<Box style={{ marginTop: "auto", marginBottom: "auto" }}>
|
|
{!isArray(option) && option?.icon ? <Avatar src={option?.icon} /> : ""}
|
|
</Box>
|
|
<Box style={{ marginTop: "auto", marginBottom: "auto", marginLeft: "1rem" }}>
|
|
{option.label}
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}}
|
|
loading={loading}
|
|
selectOnFocus
|
|
clearOnBlur
|
|
freeSolo={creatable}
|
|
handleHomeEndKeys
|
|
fullWidth
|
|
classes={{
|
|
input: classes.input
|
|
}}
|
|
style={style}
|
|
/>
|
|
);
|
|
}
|