diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx index 2dfc72663b6..e511ee0b032 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx @@ -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 extends Omit = MultiComboboxBaseProps & AutoSizeConditionals; export const MultiCombobox = (props: MultiComboboxProps) => { - 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 = (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) => selectedItems.some((opt) => opt.value === item.value), @@ -157,13 +162,14 @@ export const MultiCombobox = (props: MultiComboboxPro
selectedItems.length > 0 && setIsOpen(!isOpen)} + onClick={() => !disabled && selectedItems.length > 0 && setIsOpen(!isOpen)} > {visibleItems.map((item, index) => ( { removeSelectedItem(item); }} @@ -174,7 +180,7 @@ export const MultiCombobox = (props: MultiComboboxPro ))} {selectedItems.length > shownItems && !isOpen && ( - + {/* eslint-disable-next-line @grafana/no-untranslated-strings */} ... (props: MultiComboboxPro })} {...getInputProps( getDropdownProps({ + disabled, preventKeyAction: isOpen, placeholder: selectedItems.length > 0 ? undefined : placeholder, onFocus: () => setIsOpen(true), }) )} /> + {loading && ( +
+ +
+ )}
diff --git a/packages/grafana-ui/src/components/Combobox/ValuePill.tsx b/packages/grafana-ui/src/components/Combobox/ValuePill.tsx index 789436938b8..96809d281b7 100644 --- a/packages/grafana-ui/src/components/Combobox/ValuePill.tsx +++ b/packages/grafana-ui/src/components/Combobox/ValuePill.tsx @@ -9,28 +9,35 @@ import { IconButton } from '../IconButton/IconButton'; interface ValuePillProps { children: string; onRemove: () => void; + disabled?: boolean; } -export const ValuePill = forwardRef(({ children, onRemove, ...rest }, ref) => { - const styles = useStyles2(getValuePillStyles); - return ( - - {children} - - { - e.stopPropagation(); - onRemove(); - }} - /> - - ); -}); +export const ValuePill = forwardRef( + ({ children, onRemove, disabled, ...rest }, ref) => { + const styles = useStyles2(getValuePillStyles, disabled); + return ( + + {children} + {!disabled && ( + <> + + { + e.stopPropagation(); + onRemove(); + }} + /> + + )} + + ); + } +); -const getValuePillStyles = (theme: GrafanaTheme2) => ({ +const getValuePillStyles = (theme: GrafanaTheme2, disabled?: boolean) => ({ wrapper: css({ display: 'inline-flex', gap: theme.spacing(0.5), @@ -38,6 +45,7 @@ const getValuePillStyles = (theme: GrafanaTheme2) => ({ color: theme.colors.text.primary, background: theme.colors.background.secondary, padding: theme.spacing(0.25), + border: disabled ? `1px solid ${theme.colors.border.weak}` : 'none', fontSize: theme.typography.bodySmall.fontSize, flexShrink: 0, minWidth: '50px', diff --git a/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts b/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts index 4f7551ddf2e..107a881cf5c 100644 --- a/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts +++ b/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts @@ -5,8 +5,13 @@ import { GrafanaTheme2 } from '@grafana/data'; import { getFocusStyles } from '../../themes/mixins'; import { getInputStyles } from '../Input/Input'; -export const getMultiComboboxStyles = (theme: GrafanaTheme2, isOpen: boolean) => { - const inputStyles = getInputStyles({ theme }); +export const getMultiComboboxStyles = ( + theme: GrafanaTheme2, + isOpen: boolean, + invalid?: boolean, + disabled?: boolean +) => { + const inputStyles = getInputStyles({ theme, invalid }); const focusStyles = getFocusStyles(theme); return { @@ -52,6 +57,7 @@ export const getMultiComboboxStyles = (theme: GrafanaTheme2, isOpen: boolean) => alignItems: 'center', justifyContent: 'center', padding: theme.spacing(0, 1), + border: disabled ? `1px solid ${theme.colors.border.weak}` : 'none', borderRadius: theme.shape.radius.default, backgroundColor: theme.colors.background.secondary, cursor: 'pointer', @@ -59,5 +65,7 @@ export const getMultiComboboxStyles = (theme: GrafanaTheme2, isOpen: boolean) => backgroundColor: theme.colors.action.hover, }, }), + suffix: inputStyles.suffix, + disabled: inputStyles.inputDisabled, }; }; diff --git a/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts b/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts index 83a5b0e2871..79a7722df4d 100644 --- a/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts +++ b/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts @@ -7,26 +7,31 @@ import { ComboboxOption } from './Combobox'; const FONT_SIZE = 12; const EXTRA_PILL_SIZE = 50; +const EXTRA_PILL_DISABLED_SIZE = 10; /** * Updates the number of shown items in the multi combobox based on the available width. */ export function useMeasureMulti( selectedItems: Array>, - width?: number | 'auto' + width?: number | 'auto', + disabled?: boolean ) { const [shownItems, setShownItems] = useState(selectedItems.length); const [measureRef, { width: containerWidth }] = useMeasure(); + const [counterMeasureRef, { width: counterWidth }] = useMeasure(); const [suffixMeasureRef, { width: suffixWidth }] = useMeasure(); const finalWidth = width && width !== 'auto' ? width : containerWidth; useEffect(() => { - const maxWidth = finalWidth - suffixWidth; + const maxWidth = finalWidth - counterWidth - suffixWidth; let currWidth = 0; for (let i = 0; i < selectedItems.length; i++) { // Measure text width and add size of padding, separator and close button - currWidth += measureText(selectedItems[i].label || '', FONT_SIZE).width + EXTRA_PILL_SIZE; + currWidth += + measureText(selectedItems[i].label || '', FONT_SIZE).width + + (disabled ? EXTRA_PILL_DISABLED_SIZE : EXTRA_PILL_SIZE); if (currWidth > maxWidth) { // If there is no space for that item, show the current number of items, // but always show at least 1 item @@ -38,7 +43,7 @@ export function useMeasureMulti( setShownItems(selectedItems.length); } } - }, [finalWidth, suffixWidth, selectedItems, setShownItems]); + }, [finalWidth, counterWidth, suffixWidth, selectedItems, setShownItems, disabled]); - return { measureRef, suffixMeasureRef, shownItems }; + return { measureRef, counterMeasureRef, suffixMeasureRef, shownItems }; }