MultiCombobox: Add `Clear all` button (#99668)

pull/99799/head
Laura Fernández 5 months ago committed by GitHub
parent c2b44a5da1
commit 2df05505db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 26
      packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx
  2. 36
      packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx
  3. 5
      packages/grafana-ui/src/components/Combobox/getMultiComboboxStyles.ts
  4. 3
      public/locales/en-US/grafana.json
  5. 3
      public/locales/pseudo-LOCALE/grafana.json

@ -128,6 +128,32 @@ describe('MultiCombobox', () => {
expect(await screen.findByText('d')).toBeInTheDocument();
});
it('should remove value when clicking on the close icon of the pill', async () => {
const options = [
{ label: 'A', value: 'a' },
{ label: 'B', value: 'b' },
{ label: 'C', value: 'c' },
];
const onChange = jest.fn();
render(<MultiCombobox width={200} options={options} value={['a', 'b', 'c']} onChange={onChange} />);
const fistPillRemoveButton = await screen.findByRole('button', { name: 'Remove A' });
await user.click(fistPillRemoveButton);
expect(onChange).toHaveBeenCalledWith(options.filter((o) => o.value !== 'a'));
});
it('should remove all selected items when clicking on clear all button', async () => {
const options = [
{ label: 'A', value: 'a' },
{ label: 'B', value: 'b' },
{ label: 'C', value: 'c' },
];
const onChange = jest.fn();
render(<MultiCombobox width={200} options={options} value={['a', 'b', 'c']} onChange={onChange} isClearable />);
const clearAllButton = await screen.findByTitle('Clear all');
await user.click(clearAllButton);
expect(onChange).toHaveBeenCalledWith([]);
});
describe('all option', () => {
it('should render all option', async () => {
const options = [

@ -6,6 +6,7 @@ import { useCallback, useMemo, useState } from 'react';
import { useStyles2 } from '../../themes';
import { t } from '../../utils/i18n';
import { Checkbox } from '../Forms/Checkbox';
import { Icon } from '../Icon/Icon';
import { Box } from '../Layout/Box/Box';
import { Stack } from '../Layout/Stack/Stack';
import { Portal } from '../Portal/Portal';
@ -36,7 +37,8 @@ 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 { placeholder, onChange, value, width, enableAllOption, invalid, disabled, minWidth, maxWidth } = props;
const { placeholder, onChange, value, width, enableAllOption, invalid, disabled, minWidth, maxWidth, isClearable } =
props;
const styles = useStyles2(getComboboxStyles);
const [inputValue, setInputValue] = useState('');
@ -80,7 +82,7 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro
[selectedItems]
);
const { getSelectedItemProps, getDropdownProps, setSelectedItems, addSelectedItem, removeSelectedItem } =
const { getSelectedItemProps, getDropdownProps, setSelectedItems, addSelectedItem, removeSelectedItem, reset } =
useMultipleSelection({
selectedItems, // initally selected items,
onStateChange: ({ type, selectedItems: newSelectedItems }) => {
@ -91,6 +93,7 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro
case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem:
case useMultipleSelection.stateChangeTypes.FunctionAddSelectedItem:
case useMultipleSelection.stateChangeTypes.FunctionSetSelectedItems:
case useMultipleSelection.stateChangeTypes.FunctionReset:
// Unclear why newSelectedItems would be undefined, but this seems logical
onChange(newSelectedItems ?? []);
break;
@ -220,7 +223,16 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro
});
const { inputRef: containerRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(options, isOpen);
const multiStyles = useStyles2(getMultiComboboxStyles, isOpen, invalid, disabled, width, minWidth, maxWidth);
const multiStyles = useStyles2(
getMultiComboboxStyles,
isOpen,
invalid,
disabled,
width,
minWidth,
maxWidth,
isClearable
);
const virtualizerOptions = {
count: options.length,
@ -284,6 +296,24 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro
/>
<div className={multiStyles.suffix} ref={suffixMeasureRef} {...getToggleButtonProps()}>
{isClearable && selectedItems.length > 0 && (
<Icon
name="times"
className={styles.clear}
title={t('multicombobox.clear.title', 'Clear all')}
tabIndex={0}
role="button"
onClick={(e) => {
e.stopPropagation();
reset();
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
reset();
}
}}
/>
)}
<SuffixIcon isLoading={loading || false} isOpen={isOpen} />
</div>
</span>

@ -12,7 +12,8 @@ export const getMultiComboboxStyles = (
disabled?: boolean,
width?: number | 'auto',
minWidth?: number,
maxWidth?: number
maxWidth?: number,
isClearable?: boolean
) => {
const inputStyles = getInputStyles({ theme, invalid });
const focusStyles = getFocusStyles(theme);
@ -35,7 +36,7 @@ export const getMultiComboboxStyles = (
width: '100%',
gap: theme.spacing(0.5),
padding: theme.spacing(0.5),
paddingRight: 28, // Account for suffix
paddingRight: isClearable ? theme.spacing(5) : 28, // Account for suffix
'&:focus-within': {
...focusStyles,
},

@ -2072,6 +2072,9 @@
"all": {
"title": "All",
"title-filtered": "All (filtered)"
},
"clear": {
"title": "Clear all"
}
},
"nav": {

@ -2072,6 +2072,9 @@
"all": {
"title": "Åľľ",
"title-filtered": "Åľľ (ƒįľŧęřęđ)"
},
"clear": {
"title": "Cľęäř äľľ"
}
},
"nav": {

Loading…
Cancel
Save