Chore: Fix some more a11y checks in stories (#110531)

fix more a11y checks in stories
This commit is contained in:
Ashley Harrison
2025-09-04 09:57:02 +01:00
committed by GitHub
parent 513971119a
commit 9272a1c19a
7 changed files with 142 additions and 102 deletions
-12
View File
@@ -25,9 +25,6 @@ exports[`better eslint`] = {
"e2e/utils/support/types.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector/ContactPointSelector.story.tsx:5381": [
[0, 0, 0, "Skipping a11y tests is not allowed. Please fix the component or story instead.", "0"]
],
"packages/grafana-alerting/src/grafana/notificationPolicies/utils.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
@@ -725,9 +722,6 @@ exports[`better eslint`] = {
"packages/grafana-ui/src/components/Forms/Checkbox.story.tsx:5381": [
[0, 0, 0, "Skipping a11y tests is not allowed. Please fix the component or story instead.", "0"]
],
"packages/grafana-ui/src/components/Forms/FieldArray.story.tsx:5381": [
[0, 0, 0, "Skipping a11y tests is not allowed. Please fix the component or story instead.", "0"]
],
"packages/grafana-ui/src/components/Forms/Form.story.tsx:5381": [
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"],
@@ -836,9 +830,6 @@ exports[`better eslint`] = {
"packages/grafana-ui/src/components/QueryField/QueryField.story.tsx:5381": [
[0, 0, 0, "Skipping a11y tests is not allowed. Please fix the component or story instead.", "0"]
],
"packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.story.tsx:5381": [
[0, 0, 0, "Skipping a11y tests is not allowed. Please fix the component or story instead.", "0"]
],
"packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "0"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "1"]
@@ -860,9 +851,6 @@ exports[`better eslint`] = {
"packages/grafana-ui/src/components/Segment/SegmentSelect.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-ui/src/components/Select/Select.story.tsx:5381": [
[0, 0, 0, "Skipping a11y tests is not allowed. Please fix the component or story instead.", "0"]
],
"packages/grafana-ui/src/components/Select/SelectBase.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
@@ -1,8 +1,11 @@
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import { useId } from 'react';
import { Field } from '@grafana/ui';
import { defaultDecorators } from '../../../../../tests/story-utils';
import { ContactPointSelector } from './ContactPointSelector';
import { ContactPointSelector, ContactPointSelectorProps } from './ContactPointSelector';
import mdx from './ContactPointSelector.mdx';
import { simpleContactPointsListScenario, withErrorScenario } from './ContactPointSelector.test.scenario';
@@ -14,11 +17,18 @@ const meta: Meta<typeof ContactPointSelector> = {
docs: {
page: mdx,
},
// TODO fix a11y issue in story and remove this
a11y: { test: 'off' },
},
};
const StoryRenderFn: StoryFn<ContactPointSelectorProps> = (args) => {
const id = useId();
return (
<Field noMargin label="Select contact point">
<ContactPointSelector {...args} id={id} />
</Field>
);
};
export default meta;
type Story = StoryObj<typeof ContactPointSelector>;
@@ -28,6 +38,7 @@ export const Basic: Story = {
handlers: simpleContactPointsListScenario,
},
},
render: StoryRenderFn,
};
export const WithError: Story = {
@@ -36,4 +47,5 @@ export const WithError: Story = {
handlers: withErrorScenario,
},
},
render: StoryRenderFn,
};
@@ -6,6 +6,7 @@ import { Button } from '../Button/Button';
import { Input } from '../Input/Input';
import { Stack } from '../Layout/Stack/Stack';
import { Field } from './Field';
import { FieldArray } from './FieldArray';
import mdx from './FieldArray.mdx';
import { Form } from './Form';
@@ -21,8 +22,6 @@ const meta: Meta = {
controls: {
exclude: ['name', 'keyName', 'control', 'shouldUnregister'],
},
// TODO fix a11y issue in story and remove this
a11y: { test: 'off' },
},
argTypes: {
containerWidth: { control: { type: 'range', min: 100, max: 500, step: 10 } },
@@ -46,16 +45,22 @@ export const Simple: StoryFn = (args) => {
<div style={{ marginBottom: '1rem' }}>
{fields.map((field, index) => (
<Stack key={field.id}>
<Input
key={field.id}
{...register(`people.${index}.firstName` as const)}
defaultValue={field.firstName}
/>
<Input
key={field.id}
{...register(`people.${index}.lastName` as const)}
defaultValue={field.lastName}
/>
<Field noMargin label="First name">
<Input
key={field.id}
{...register(`people.${index}.firstName` as const)}
defaultValue={field.firstName}
id={`${field.id}-first-name`}
/>
</Field>
<Field noMargin label="Last name">
<Input
key={field.id}
{...register(`people.${index}.lastName` as const)}
defaultValue={field.lastName}
id={`${field.id}-last-name`}
/>
</Field>
</Stack>
))}
</div>
@@ -285,7 +285,6 @@ const getStyles = (theme: GrafanaTheme2) => {
gap: theme.spacing(1),
marginLeft: theme.spacing(2),
color: theme.colors.text.secondary,
opacity: 0.7,
}),
description: css({
...theme.typography.bodySmall,
@@ -14,8 +14,6 @@ const meta: Meta<typeof ScrollContainer> = {
docs: {
page: mdx,
},
// TODO fix a11y issue in story and remove this
a11y: { test: 'off' },
},
argTypes: {},
args: {
@@ -40,7 +40,10 @@ export const ScrollContainer = forwardRef<HTMLDivElement, PropsWithChildren<Prop
return (
<Box {...boxProps} display="flex" direction="column" flex={1} position="relative">
<div onScroll={onScroll} className={styles.scroller} ref={ref}>
{/* scrollable containers need tabindex set so keyboard users can focus them to scroll */}
{/* see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/a7d1a12a6198d546c4a06477b385b4fde03b762e/docs/rules/no-noninteractive-tabindex.md#:~:text=If%20you%20know,scroll%20containers%22. */}
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
<div tabIndex={0} onScroll={onScroll} className={styles.scroller} ref={ref}>
{showScrollIndicators ? <ScrollIndicators>{children}</ScrollIndicators> : children}
</div>
</Box>
@@ -2,12 +2,13 @@ import { auto } from '@popperjs/core';
import { action } from '@storybook/addon-actions';
import { Meta, StoryFn } from '@storybook/react';
import Chance from 'chance';
import { useState } from 'react';
import { useId, useState } from 'react';
import { SelectableValue, toIconName } from '@grafana/data';
import { getAvailableIcons } from '../../types/icon';
import { Alert } from '../Alert/Alert';
import { Field } from '../Forms/Field';
import { Icon } from '../Icon/Icon';
import { AsyncMultiSelect, AsyncSelect, MultiSelect, Select } from './Select';
@@ -72,8 +73,6 @@ const meta: Meta = {
'value',
],
},
// TODO fix a11y issue in story and remove this
a11y: { test: 'off' },
},
args: {
width: 0,
@@ -113,10 +112,12 @@ interface StoryProps extends Partial<SelectCommonProps<string>> {
export const Basic: StoryFn<StoryProps> = (args) => {
const [value, setValue] = useState<SelectableValue<string>>();
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<Select
inputId={id}
options={generateOptions()}
value={value}
onChange={(v) => {
@@ -125,16 +126,18 @@ export const Basic: StoryFn<StoryProps> = (args) => {
}}
{...args}
/>
</>
</Field>
);
};
export const BasicVirtualizedList: StoryFn<StoryProps> = (args) => {
const [value, setValue] = useState<SelectableValue<string>>();
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<Select
inputId={id}
options={generateThousandsOfOptions()}
virtualized
value={value}
@@ -144,7 +147,7 @@ export const BasicVirtualizedList: StoryFn<StoryProps> = (args) => {
}}
{...args}
/>
</>
</Field>
);
};
@@ -153,9 +156,11 @@ export const BasicVirtualizedList: StoryFn<StoryProps> = (args) => {
*/
export const BasicSelectPlainValue: StoryFn<StoryProps> = (args) => {
const [value, setValue] = useState<string>();
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<Select
inputId={id}
options={generateOptions()}
value={value}
onChange={(v) => {
@@ -165,7 +170,7 @@ export const BasicSelectPlainValue: StoryFn<StoryProps> = (args) => {
prefix={getPrefix(args.icon)}
{...args}
/>
</>
</Field>
);
};
@@ -186,10 +191,12 @@ export const SelectWithOptionDescriptions: StoryFn = (args) => {
imgUrl: 'https://placekitten.com/40/40',
},
];
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<Select
inputId={id}
options={options}
value={value}
onChange={(v) => {
@@ -199,7 +206,7 @@ export const SelectWithOptionDescriptions: StoryFn = (args) => {
prefix={getPrefix(args.icon)}
{...args}
/>
</>
</Field>
);
};
@@ -208,10 +215,12 @@ export const SelectWithOptionDescriptions: StoryFn = (args) => {
*/
export const MultiPlainValue: StoryFn = (args) => {
const [value, setValue] = useState<string[]>();
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<MultiSelect
inputId={id}
options={generateOptions()}
value={value}
onChange={(v) => {
@@ -220,16 +229,18 @@ export const MultiPlainValue: StoryFn = (args) => {
prefix={getPrefix(args.icon)}
{...args}
/>
</>
</Field>
);
};
export const MultiSelectWithOptionGroups: StoryFn = (args) => {
const [value, setValue] = useState<string[]>();
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<MultiSelect
inputId={id}
options={[
{ label: 'Foo', value: '1' },
{
@@ -262,16 +273,18 @@ export const MultiSelectWithOptionGroups: StoryFn = (args) => {
prefix={getPrefix(args.icon)}
{...args}
/>
</>
</Field>
);
};
export const MultiSelectWithOptionGroupsVirtualized: StoryFn = (args) => {
const [value, setValue] = useState<string[]>();
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<MultiSelect
inputId={id}
options={manyGroupedOptions}
virtualized
value={value}
@@ -282,25 +295,29 @@ export const MultiSelectWithOptionGroupsVirtualized: StoryFn = (args) => {
prefix={getPrefix(args.icon)}
{...args}
/>
</>
</Field>
);
};
export const MultiSelectBasic: StoryFn = (args) => {
const [value, setValue] = useState<Array<SelectableValue<string>>>([]);
const id = useId();
return (
<div style={{ maxWidth: '450px' }}>
<MultiSelect
options={generateOptions()}
value={value}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
<Field noMargin label="Select an option">
<MultiSelect
inputId={id}
options={generateOptions()}
value={value}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
</Field>
</div>
);
};
@@ -314,20 +331,24 @@ MultiSelectBasic.args = {
export const MultiSelectBasicWithSelectAll: StoryFn = (args) => {
const [value, setValue] = useState<Array<SelectableValue<string>>>([]);
const id = useId();
return (
<div style={{ maxWidth: '450px' }}>
<MultiSelect
options={generateOptions()}
value={value}
toggleAllOptions={{ enabled: true }}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
<Field noMargin label="Select an option">
<MultiSelect
inputId={id}
options={generateOptions()}
value={value}
toggleAllOptions={{ enabled: true }}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
</Field>
</div>
);
};
@@ -341,19 +362,23 @@ MultiSelectBasicWithSelectAll.args = {
export const MultiSelectAsync: StoryFn = (args) => {
const [value, setValue] = useState<Array<SelectableValue<string>>>();
const id = useId();
return (
<AsyncMultiSelect
loadOptions={loadAsyncOptions}
defaultOptions
value={value}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
<Field noMargin label="Select an option">
<AsyncMultiSelect
inputId={id}
loadOptions={loadAsyncOptions}
defaultOptions
value={value}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
</Field>
);
};
MultiSelectAsync.args = {
@@ -362,29 +387,35 @@ MultiSelectAsync.args = {
export const BasicSelectAsync: StoryFn = (args) => {
const [value, setValue] = useState<SelectableValue<string>>();
const id = useId();
return (
<AsyncSelect
loadOptions={loadAsyncOptions}
defaultOptions
value={value}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
<Field noMargin label="Select an option">
<AsyncSelect
inputId={id}
loadOptions={loadAsyncOptions}
defaultOptions
value={value}
onChange={(v) => {
setValue(v);
action('onChange')(v);
}}
prefix={getPrefix(args.icon)}
{...args}
/>
</Field>
);
};
export const AutoMenuPlacement: StoryFn = (args) => {
const [value, setValue] = useState<SelectableValue<string>>();
const id = useId();
return (
<>
<div style={{ width: '100%', height: 'calc(95vh - 118px)', display: 'flex', alignItems: 'flex-end' }}>
<div style={{ width: '100%', height: 'calc(95vh - 118px)', display: 'flex', alignItems: 'flex-end' }}>
<Field noMargin label="Select an option">
<Select
inputId={id}
options={generateOptions()}
value={value}
onChange={(v) => {
@@ -394,8 +425,8 @@ export const AutoMenuPlacement: StoryFn = (args) => {
prefix={getPrefix(args.icon)}
{...args}
/>
</div>
</>
</Field>
</div>
);
};
AutoMenuPlacement.args = {
@@ -404,11 +435,13 @@ AutoMenuPlacement.args = {
export const WidthAuto: StoryFn = (args) => {
const [value, setValue] = useState<SelectableValue<string>>();
const id = useId();
return (
<>
<div style={{ width: '100%' }}>
<div style={{ width: '100%' }}>
<Field noMargin label="Select an option">
<Select
inputId={id}
options={generateOptions()}
value={value}
onChange={(v) => {
@@ -419,8 +452,8 @@ export const WidthAuto: StoryFn = (args) => {
{...args}
width="auto"
/>
</div>
</>
</Field>
</div>
);
};
@@ -428,9 +461,11 @@ export const CustomValueCreation: StoryFn = (args) => {
const [value, setValue] = useState<SelectableValue<string>>();
const [customOptions, setCustomOptions] = useState<Array<SelectableValue<string>>>([]);
const options = generateOptions();
const id = useId();
return (
<>
<Field noMargin label="Select an option">
<Select
inputId={id}
options={[...options, ...customOptions]}
value={value}
onChange={(v) => {
@@ -447,7 +482,7 @@ export const CustomValueCreation: StoryFn = (args) => {
prefix={getPrefix(args.icon)}
{...args}
/>
</>
</Field>
);
};
CustomValueCreation.args = {