blob: 65a9f22d968a64bb341b1fae5cc80e2a3b67d412 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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, { useState, useEffect } from 'react';
import { MenuItem, Checkbox, Intent } from '@blueprintjs/core';
import { MultiSelect2 } from '@blueprintjs/select';
interface Props<T> {
placeholder?: string;
loading?: boolean;
items: T[];
disabledItems?: T[];
getKey?: (item: T) => string | number;
getName?: (item: T) => string;
getIcon?: (item: T) => string;
selectedItems?: T[];
onChangeItems?: (selectedItems: T[]) => void;
noResult?: string;
onQueryChange?: (query: string) => void;
}
export const MultiSelector = <T,>({
placeholder,
loading = false,
items,
disabledItems = [],
getKey = (it) => it as string,
getName = (it) => it as string,
getIcon,
onChangeItems,
noResult,
onQueryChange,
...props
}: Props<T>) => {
const [selectedItems, setSelectedItems] = useState<T[]>([]);
useEffect(() => {
setSelectedItems(props.selectedItems ?? []);
}, [props.selectedItems]);
const tagRenderer = (item: T) => {
const name = getName(item);
return <span>{name}</span>;
};
const itemRenderer = (item: T, { handleClick }: any) => {
const key = getKey(item);
const name = getName(item);
const icon = getIcon?.(item);
const selected = !!selectedItems.find((it) => getKey(it) === key);
const disabled = !!disabledItems.find((it) => getKey(it) === key);
return (
<MenuItem
key={key}
disabled={selected || disabled}
onClick={(e) => {
e.preventDefault();
handleClick();
}}
labelElement={icon ? <img src={icon} width={16} alt="" /> : null}
text={<Checkbox disabled={selected || disabled} checked={selected || disabled} readOnly label={name} />}
/>
);
};
const handleItemSelect = (item: T) => {
const newSelectedItems = [...selectedItems, item];
if (onChangeItems) {
onChangeItems(newSelectedItems);
} else {
setSelectedItems(newSelectedItems);
}
};
const handleItemRemove = (item: T) => {
const newSelectedItems = selectedItems.filter((it) => getKey(it) !== getKey(item));
if (onChangeItems) {
onChangeItems(newSelectedItems);
} else {
setSelectedItems(newSelectedItems);
}
};
return (
<MultiSelect2
fill
resetOnSelect
placeholder={placeholder ?? 'Select...'}
items={items}
// https://github.com/palantir/blueprint/issues/3596
// set activeItem to null will fixed the scrollBar to top when the selectedItems changed
activeItem={null}
selectedItems={selectedItems}
itemRenderer={itemRenderer}
tagRenderer={tagRenderer}
tagInputProps={{
tagProps: {
intent: Intent.PRIMARY,
minimal: true,
},
}}
onItemSelect={handleItemSelect}
onRemove={handleItemRemove}
onQueryChange={onQueryChange}
noResults={<MenuItem disabled={true} text={loading ? 'Fetching...' : noResult} />}
/>
);
};