diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 5456b0d38dd..a5155bbdd95 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -597,14 +597,6 @@ "count": 5 } }, - "packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx": { - "no-restricted-syntax": { - "count": 1 - }, - "react-hooks/rules-of-hooks": { - "count": 5 - } - }, "packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx index b3794bf3fa9..581cb6223bf 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx @@ -1,7 +1,7 @@ import { action } from '@storybook/addon-actions'; import { useArgs, useEffect, useState } from '@storybook/preview-api'; import type { Meta, StoryFn, StoryObj } from '@storybook/react'; -import { ComponentProps } from 'react'; +import { ComponentProps, useId } from 'react'; import { Field } from '../Forms/Field'; @@ -17,8 +17,6 @@ const meta: Meta = { docs: { page: mdx, }, - // TODO fix a11y issue in story and remove this - a11y: { test: 'off' }, }, }; @@ -48,22 +46,47 @@ type ManyOptionsArgs = storyArgs & { numberOfOptions?: number }; type Story = StoryObj; -export const Basic: Story = { - args: commonArgs, - render: (args) => { - const [{ value }, setArgs] = useArgs(); +const BasicStory: StoryFn = (args) => { + const [{ value }, setArgs] = useArgs(); + const comboboxId = useId(); - return ( + return ( + { onChangeAction(val); setArgs({ value: val }); }} /> - ); - }, + + ); +}; + +export const Basic: Story = { + args: commonArgs, + render: BasicStory, +}; + +const WithInfoOptionStory: StoryFn = (args) => { + const [{ value }, setArgs] = useArgs(); + const comboboxId = useId(); + + return ( + + { + onChangeAction(val); + setArgs({ value: val }); + }} + /> + + ); }; export const WithInfoOption: Story = { @@ -75,44 +98,37 @@ export const WithInfoOption: Story = { { label: 'Can’t find your country? Select “Other” or contact an admin', value: '__INFO__', infoOption: true }, ], }, - render: (args) => { - const [{ value }, setArgs] = useArgs(); - - return ( - { - onChangeAction(val); - setArgs({ value: val }); - }} - /> - ); - }, + render: WithInfoOptionStory, }; -export const AutoSize: Story = { - args: { ...commonArgs, width: 'auto', minWidth: 20 }, - render: (args) => { - const [{ value }, setArgs] = useArgs(); +const AutoSizeStory: StoryFn = (args) => { + const [{ value }, setArgs] = useArgs(); + const comboboxId = useId(); - return ( + return ( + { action('onChange')(val); setArgs({ value: val }); }} /> - ); - }, + + ); +}; + +export const AutoSize: Story = { + args: { ...commonArgs, width: 'auto', minWidth: 20 }, + render: AutoSizeStory, }; const ManyOptionsStory: StoryFn = ({ numberOfOptions = 1e4, ...args }) => { const [dynamicArgs, setArgs] = useArgs(); - const [options, setOptions] = useState([]); + const comboboxId = useId(); useEffect(() => { setTimeout(async () => { @@ -123,15 +139,18 @@ const ManyOptionsStory: StoryFn = ({ numberOfOptions = 1e4, ... const { onChange, ...rest } = args; return ( - { - setArgs({ value: opts }); - onChangeAction(opts); - }} - /> + + { + setArgs({ value: opts }); + onChangeAction(opts); + }} + /> + ); }; @@ -146,8 +165,8 @@ export const ManyOptions: StoryObj = { const ManyOptionsGroupedStory: StoryFn = ({ numberOfOptions = 1e5, ...args }) => { const [dynamicArgs, setArgs] = useArgs(); - const [options, setOptions] = useState([]); + const comboboxId = useId(); useEffect(() => { setTimeout(async () => { @@ -157,15 +176,18 @@ const ManyOptionsGroupedStory: StoryFn = ({ numberOfOptions = 1 }, [numberOfOptions]); const { onChange, ...rest } = args; return ( - { - setArgs({ value: opts }); - onChangeAction(opts); - }} - /> + + { + setArgs({ value: opts }); + onChangeAction(opts); + }} + /> + ); }; @@ -183,6 +205,28 @@ function loadOptionsWithLabels(inputValue: string) { return fakeSearchAPI(`http://example.com/search?errorOnQuery=break&query=${inputValue}`); } +const AsyncOptionsWithLabelsStory: StoryFn = (args) => { + const [dynamicArgs, setArgs] = useArgs(); + const comboboxId = useId(); + + return ( + + { + onChangeAction(val); + setArgs({ value: val }); + }} + /> + + ); +}; + export const AsyncOptionsWithLabels: Story = { name: 'Async - options returns labels', args: { @@ -190,25 +234,7 @@ export const AsyncOptionsWithLabels: Story = { value: [{ label: 'Option 69', value: '69' }], placeholder: 'Select an option', }, - render: (args) => { - const [dynamicArgs, setArgs] = useArgs(); - - return ( - - { - onChangeAction(val); - setArgs({ value: val }); - }} - /> - - ); - }, + render: AsyncOptionsWithLabelsStory, }; function loadOptionsOnlyValues(inputValue: string) { @@ -218,6 +244,28 @@ function loadOptionsOnlyValues(inputValue: string) { ); } +const AsyncOptionsWithOnlyValuesStory: StoryFn = (args) => { + const [dynamicArgs, setArgs] = useArgs(); + const comboboxId = useId(); + + return ( + + { + onChangeAction(val); + setArgs({ value: val }); + }} + /> + + ); +}; + export const AsyncOptionsWithOnlyValues: Story = { name: 'Async - options returns only values', args: { @@ -225,23 +273,5 @@ export const AsyncOptionsWithOnlyValues: Story = { value: [{ value: 'Option 69' }], placeholder: 'Select an option', }, - render: (args) => { - const [dynamicArgs, setArgs] = useArgs(); - - return ( - - { - onChangeAction(val); - setArgs({ value: val }); - }} - /> - - ); - }, + render: AsyncOptionsWithOnlyValuesStory, }; diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx index 3a1e2856a68..82759befffb 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx @@ -57,6 +57,7 @@ export const MultiCombobox = (props: MultiComboboxPro 'data-testid': dataTestId, portalContainer, prefixIcon, + id, } = props; const styles = useStyles2(getComboboxStyles); @@ -152,17 +153,10 @@ export const MultiCombobox = (props: MultiComboboxPro }, }); - const { - getToggleButtonProps, - //getLabelProps, - isOpen, - highlightedIndex, - getMenuProps, - getInputProps, - getItemProps, - } = useCombobox({ + const { isOpen, highlightedIndex, getMenuProps, getInputProps, getItemProps } = useCombobox({ items: options, itemToString, + inputId: id, inputValue, selectedItem: null, stateReducer: (state, actionAndChanges) => { @@ -325,7 +319,7 @@ export const MultiCombobox = (props: MultiComboboxPro })} /> -
+
{isClearable && selectedItems.length > 0 && (