|
|
|
@ -9,6 +9,7 @@ import { Box } from '../Layout/Box/Box'; |
|
|
|
|
import { Stack } from '../Layout/Stack/Stack'; |
|
|
|
|
import { Portal } from '../Portal/Portal'; |
|
|
|
|
import { ScrollContainer } from '../ScrollContainer/ScrollContainer'; |
|
|
|
|
import { Spinner } from '../Spinner/Spinner'; |
|
|
|
|
import { Text } from '../Text/Text'; |
|
|
|
|
import { Tooltip } from '../Tooltip'; |
|
|
|
|
|
|
|
|
@ -34,7 +35,7 @@ interface MultiComboboxBaseProps<T extends string | number> extends Omit<Combobo |
|
|
|
|
export type MultiComboboxProps<T extends string | number> = MultiComboboxBaseProps<T> & AutoSizeConditionals; |
|
|
|
|
|
|
|
|
|
export const MultiCombobox = <T extends string | number>(props: MultiComboboxProps<T>) => { |
|
|
|
|
const { options, placeholder, onChange, value, width } = props; |
|
|
|
|
const { options, placeholder, onChange, value, width, invalid, loading, disabled } = props; |
|
|
|
|
const isAsync = typeof options === 'function'; |
|
|
|
|
|
|
|
|
|
const selectedItems = useMemo(() => { |
|
|
|
@ -59,9 +60,13 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro |
|
|
|
|
|
|
|
|
|
const { inputRef: containerRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(items, isOpen); |
|
|
|
|
|
|
|
|
|
const multiStyles = useStyles2(getMultiComboboxStyles, isOpen); |
|
|
|
|
const multiStyles = useStyles2(getMultiComboboxStyles, isOpen, invalid, disabled); |
|
|
|
|
|
|
|
|
|
const { measureRef, suffixMeasureRef, shownItems } = useMeasureMulti(selectedItems, width); |
|
|
|
|
const { measureRef, counterMeasureRef, suffixMeasureRef, shownItems } = useMeasureMulti( |
|
|
|
|
selectedItems, |
|
|
|
|
width, |
|
|
|
|
disabled |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const isOptionSelected = useCallback( |
|
|
|
|
(item: ComboboxOption<T>) => selectedItems.some((opt) => opt.value === item.value), |
|
|
|
@ -157,13 +162,14 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro |
|
|
|
|
<div ref={containerRef}> |
|
|
|
|
<div |
|
|
|
|
style={{ width: width === 'auto' ? undefined : width }} |
|
|
|
|
className={multiStyles.wrapper} |
|
|
|
|
className={cx(multiStyles.wrapper, { [multiStyles.disabled]: disabled })} |
|
|
|
|
ref={measureRef} |
|
|
|
|
onClick={() => selectedItems.length > 0 && setIsOpen(!isOpen)} |
|
|
|
|
onClick={() => !disabled && selectedItems.length > 0 && setIsOpen(!isOpen)} |
|
|
|
|
> |
|
|
|
|
<span className={multiStyles.pillWrapper}> |
|
|
|
|
{visibleItems.map((item, index) => ( |
|
|
|
|
<ValuePill |
|
|
|
|
disabled={disabled} |
|
|
|
|
onRemove={() => { |
|
|
|
|
removeSelectedItem(item); |
|
|
|
|
}} |
|
|
|
@ -174,7 +180,7 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro |
|
|
|
|
</ValuePill> |
|
|
|
|
))} |
|
|
|
|
{selectedItems.length > shownItems && !isOpen && ( |
|
|
|
|
<Box display="flex" direction="row" marginLeft={0.5} gap={1} ref={suffixMeasureRef}> |
|
|
|
|
<Box display="flex" direction="row" marginLeft={0.5} gap={1} ref={counterMeasureRef}> |
|
|
|
|
{/* eslint-disable-next-line @grafana/no-untranslated-strings */} |
|
|
|
|
<Text>...</Text> |
|
|
|
|
<Tooltip |
|
|
|
@ -197,12 +203,18 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro |
|
|
|
|
})} |
|
|
|
|
{...getInputProps( |
|
|
|
|
getDropdownProps({ |
|
|
|
|
disabled, |
|
|
|
|
preventKeyAction: isOpen, |
|
|
|
|
placeholder: selectedItems.length > 0 ? undefined : placeholder, |
|
|
|
|
onFocus: () => setIsOpen(true), |
|
|
|
|
}) |
|
|
|
|
)} |
|
|
|
|
/> |
|
|
|
|
{loading && ( |
|
|
|
|
<div className={multiStyles.suffix} ref={suffixMeasureRef}> |
|
|
|
|
<Spinner inline={true} /> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
<Portal> |
|
|
|
|