96 lines
2.6 KiB
TypeScript
96 lines
2.6 KiB
TypeScript
import {
|
|
Autocomplete,
|
|
Checkbox,
|
|
CircularProgress,
|
|
TextField,
|
|
} from "@mui/material";
|
|
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
|
|
import CheckBoxIcon from "@mui/icons-material/CheckBox";
|
|
|
|
export interface MultiSelectOption<TValue = number | string> {
|
|
readonly value: TValue;
|
|
readonly label: string;
|
|
}
|
|
|
|
interface MultiSelectProps<TValue = number | string> {
|
|
readonly options: MultiSelectOption<TValue>[];
|
|
readonly value: TValue[];
|
|
readonly onChange: (values: TValue[]) => void;
|
|
readonly label?: string;
|
|
readonly placeholder?: string;
|
|
readonly loading?: boolean;
|
|
readonly disabled?: boolean;
|
|
readonly error?: boolean;
|
|
readonly helperText?: string;
|
|
readonly size?: "small" | "medium";
|
|
readonly fullWidth?: boolean;
|
|
}
|
|
|
|
export function MultiSelect<TValue = number | string>({
|
|
options,
|
|
value,
|
|
onChange,
|
|
label,
|
|
placeholder,
|
|
loading = false,
|
|
disabled = false,
|
|
error = false,
|
|
helperText,
|
|
size = "small",
|
|
fullWidth = true,
|
|
}: MultiSelectProps<TValue>) {
|
|
const selectedOptions = options.filter((opt) => value.includes(opt.value));
|
|
|
|
return (
|
|
<Autocomplete
|
|
multiple
|
|
disableCloseOnSelect
|
|
fullWidth={fullWidth}
|
|
size={size}
|
|
disabled={disabled}
|
|
loading={loading}
|
|
options={options}
|
|
value={selectedOptions}
|
|
getOptionLabel={(option) => option.label}
|
|
isOptionEqualToValue={(option, selected) => option.value === selected.value}
|
|
onChange={(_, newSelected) => {
|
|
onChange(newSelected.map((opt) => opt.value));
|
|
}}
|
|
renderOption={(props, option, { selected }) => {
|
|
const { key, ...rest } = props as React.HTMLAttributes<HTMLLIElement> & { key: React.Key };
|
|
return (
|
|
<li key={key} {...rest}>
|
|
<Checkbox
|
|
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
|
|
checkedIcon={<CheckBoxIcon fontSize="small" />}
|
|
style={{ marginRight: 8 }}
|
|
checked={selected}
|
|
/>
|
|
{option.label}
|
|
</li>
|
|
);
|
|
}}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
label={label}
|
|
placeholder={selectedOptions.length === 0 ? placeholder : undefined}
|
|
error={error}
|
|
helperText={helperText}
|
|
slotProps={{
|
|
input: {
|
|
...params.InputProps,
|
|
endAdornment: (
|
|
<>
|
|
{loading && <CircularProgress color="inherit" size={16} />}
|
|
{params.InputProps.endAdornment}
|
|
</>
|
|
),
|
|
},
|
|
}}
|
|
/>
|
|
)}
|
|
/>
|
|
);
|
|
}
|