@@ -159,7 +201,6 @@ export const MultiCombobox =
(props: MultiComboboxPro
style={{ width: width === 'auto' ? undefined : width }}
className={cx(multiStyles.wrapper, { [multiStyles.disabled]: disabled })}
ref={measureRef}
- onClick={() => !disabled && selectedItems.length > 0 && setIsOpen(!isOpen)}
>
{visibleItems.map((item, index) => (
@@ -174,7 +215,7 @@ export const MultiCombobox = (props: MultiComboboxPro
{itemToString(item)}
))}
- {selectedItems.length > shownItems && !isOpen && (
+ {selectedItems.length > visibleItems.length && (
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
...
@@ -182,7 +223,7 @@ export const MultiCombobox = (props: MultiComboboxPro
interactive
content={
<>
- {selectedItems.slice(shownItems).map((item) => (
+ {selectedItems.slice(visibleItems.length).map((item) => (
{itemToString(item)}
))}
>
@@ -193,15 +234,13 @@ export const MultiCombobox = (props: MultiComboboxPro
)}
0,
- })}
+ className={multiStyles.input}
{...getInputProps(
getDropdownProps({
disabled,
preventKeyAction: isOpen,
placeholder: selectedItems.length > 0 ? undefined : placeholder,
- onFocus: () => setIsOpen(true),
+ onFocus: () => !disabled && setIsOpen(true),
})
)}
/>
@@ -227,6 +266,10 @@ export const MultiCombobox = (props: MultiComboboxPro
const itemProps = getItemProps({ item, index });
const isSelected = isOptionSelected(item);
const id = 'multicombobox-option-' + item.value.toString();
+ const isAll = item.value === ALL_OPTION_VALUE;
+ const allItemsSelected =
+ items[0]?.value === ALL_OPTION_VALUE && selectedItems.length === items.length - 1;
+
return (
(props: MultiComboboxPro
0 && !allItemsSelected}
aria-labelledby={id}
onClick={(e) => {
e.stopPropagation();
}}
/>
-
+
);
@@ -262,31 +315,29 @@ function getSelectedItemsFromValue(
value: T[] | Array>,
options: Array>
) {
- if (!isComboboxOptions(value)) {
- const resultingItems: Array | undefined> = [];
+ if (isComboboxOptions(value)) {
+ return value;
+ }
+ const valueMap = new Map(value.map((val, index) => [val, index]));
+ const resultingItems: Array> = [];
- for (const item of options) {
- for (const [index, val] of value.entries()) {
- if (val === item.value) {
- resultingItems[index] = item;
- }
- }
- if (resultingItems.length === value.length && !resultingItems.includes(undefined)) {
- // We found all items for the values
- break;
- }
+ for (const option of options) {
+ const index = valueMap.get(option.value);
+ if (index !== undefined) {
+ resultingItems[index] = option;
+ valueMap.delete(option.value);
}
-
- // Handle values that are not in options
- for (const [index, val] of value.entries()) {
- if (resultingItems[index] === undefined) {
- resultingItems[index] = { value: val };
- }
+ if (valueMap.size === 0) {
+ // We found all values
+ break;
}
- return resultingItems.filter((item) => item !== undefined); // TODO: Not actually needed, but TS complains
}
- return value;
+ // Handle items that are not in options
+ for (const [val, index] of valueMap) {
+ resultingItems[index] = { value: val };
+ }
+ return resultingItems;
}
function isComboboxOptions(
diff --git a/packages/grafana-ui/src/components/Combobox/OptionListItem.tsx b/packages/grafana-ui/src/components/Combobox/OptionListItem.tsx
index d80c70e01d2..436a662e204 100644
--- a/packages/grafana-ui/src/components/Combobox/OptionListItem.tsx
+++ b/packages/grafana-ui/src/components/Combobox/OptionListItem.tsx
@@ -1,21 +1,21 @@
import { useStyles2 } from '../../themes';
-import { ComboboxOption } from './Combobox';
import { getComboboxStyles } from './getComboboxStyles';
interface Props {
- option: ComboboxOption;
+ label: string;
+ description?: string;
id: string;
}
-export const OptionListItem = ({ option, id }: Props) => {
+export const OptionListItem = ({ label, description, id }: Props) => {
const styles = useStyles2(getComboboxStyles);
return (
- {option.label ?? option.value}
+ {label}
- {option.description && {option.description}}
+ {description && {description}}
);
};
diff --git a/packages/grafana-ui/src/components/Combobox/filter.ts b/packages/grafana-ui/src/components/Combobox/filter.ts
index 09ab4653051..dbf86c062d6 100644
--- a/packages/grafana-ui/src/components/Combobox/filter.ts
+++ b/packages/grafana-ui/src/components/Combobox/filter.ts
@@ -1,4 +1,5 @@
import { ComboboxOption } from './Combobox';
+import { ALL_OPTION_VALUE } from './MultiCombobox';
export function itemToString(item?: ComboboxOption | null) {
if (!item) {
@@ -17,7 +18,8 @@ export function itemFilter(inputValue: string) {
return (
!inputValue ||
item.label?.toLowerCase().includes(lowerCasedInputValue) ||
- item.value?.toString().toLowerCase().includes(lowerCasedInputValue)
+ item.value?.toString().toLowerCase().includes(lowerCasedInputValue) ||
+ item.value.toString() === ALL_OPTION_VALUE
);
};
}
diff --git a/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts b/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts
index 107a881cf5c..4347acdb0e7 100644
--- a/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts
+++ b/packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts
@@ -39,12 +39,7 @@ export const getMultiComboboxStyles = (
outline: 'none',
},
}),
- inputClosed: css({
- width: 0,
- flexGrow: 0,
- paddingLeft: 0,
- paddingRight: 0,
- }),
+
pillWrapper: css({
display: 'inline-flex',
flexWrap: isOpen ? 'wrap' : 'nowrap',
diff --git a/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts b/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts
index 79a7722df4d..4ff65a7d41f 100644
--- a/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts
+++ b/packages/grafana-ui/src/components/Combobox/useMeasureMulti.ts
@@ -8,6 +8,7 @@ import { ComboboxOption } from './Combobox';
const FONT_SIZE = 12;
const EXTRA_PILL_SIZE = 50;
const EXTRA_PILL_DISABLED_SIZE = 10;
+export const MAX_SHOWN_ITEMS = 15;
/**
* Updates the number of shown items in the multi combobox based on the available width.
@@ -34,8 +35,8 @@ export function useMeasureMulti(
(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
- setShownItems(i || 1);
+ // but always show at least 1 item. Cap at maximum number of items.
+ setShownItems(Math.min(i, MAX_SHOWN_ITEMS) || 1);
break;
}
if (i === selectedItems.length - 1) {
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index cef1901e04c..f68a771ff6b 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -2049,6 +2049,12 @@
"title": "Why host with Grafana?"
}
},
+ "multicombobox": {
+ "all": {
+ "title": "All",
+ "title-filtered": "All (filtered)"
+ }
+ },
"nav": {
"add-new-connections": {
"title": "Add new connection"
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 60ef9dc7484..88ca996b76a 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -2049,6 +2049,12 @@
"title": "Ŵĥy ĥőşŧ ŵįŧĥ Ğřäƒäʼnä?"
}
},
+ "multicombobox": {
+ "all": {
+ "title": "Åľľ",
+ "title-filtered": "Åľľ (ƒįľŧęřęđ)"
+ }
+ },
"nav": {
"add-new-connections": {
"title": "Åđđ ʼnęŵ čőʼnʼnęčŧįőʼn"