A11y/Dashboard: Fix misc. fastpass issues (#40296)

* A11y/Dashboard: Fix misc. fastpass issues
See #39429
This commit is contained in:
kay delaney 2021-10-12 13:26:01 +01:00 committed by GitHub
parent 0ac81e9e47
commit c443f244a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 74 additions and 27 deletions

View File

@ -14,7 +14,7 @@ export const smokeTestScenario = {
e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer()
.should('be.visible')
.within(() => {
e2e.components.Select.input().should('be.visible').click();
e2e().get('input[id*="scenario-input-"]').should('be.visible').click();
});
cy.contains('CSV Metric Values').scrollIntoView().should('be.visible').click();

View File

@ -14,7 +14,7 @@ e2e.scenario({
e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer()
.should('be.visible')
.within(() => {
e2e.components.Select.input().should('be.visible').click();
e2e().get('input[id*="scenario-input-"]').should('be.visible').click();
});
cy.contains('CSV Metric Values').scrollIntoView().should('be.visible').click();

View File

@ -50,7 +50,7 @@ e2e.scenario({
e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer()
.should('be.visible')
.within(() => {
e2e.components.Select.input().eq(0).should('be.visible').click();
e2e().get('input[id*="scenario-input-"]').eq(0).should('be.visible').click();
});
cy.contains('CSV Metric Values').scrollIntoView().should('be.visible').eq(0).click();

View File

@ -39,6 +39,7 @@ export interface SliderFieldConfigSettings {
min: number;
max: number;
step?: number;
ariaLabelForHandle?: string;
}
export interface DataLinksFieldConfigSettings {}

View File

@ -19,6 +19,7 @@ export interface StandardEditorProps<TValue = any, TSettings = any, TOptions = a
onChange: (value?: TValue) => void;
item: StandardEditorsRegistryItem<TValue, TSettings>;
context: StandardEditorContext<TOptions, TState>;
id?: string;
}
export interface StandardEditorsRegistryItem<TValue = any, TSettings = any> extends RegistryItem {
editor: ComponentType<StandardEditorProps<TValue, TSettings>>;

View File

@ -17,6 +17,8 @@ export interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
tooltip?: string;
/** For image icons */
imgSrc?: string;
/** Alt text for imgSrc */
imgAlt?: string;
/** if true or false will show angle-down/up */
isOpen?: boolean;
/** Controls flex-grow: 1 */
@ -39,6 +41,7 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
className,
children,
imgSrc,
imgAlt,
fullWidth,
isOpen,
narrow,
@ -72,12 +75,12 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
<button
ref={ref}
className={buttonStyles}
aria-label={getButttonAriaLabel(ariaLabel, tooltip)}
aria-label={getButtonAriaLabel(ariaLabel, tooltip)}
aria-expanded={isOpen}
{...rest}
>
{renderIcon(icon)}
{imgSrc && <img className={styles.img} src={imgSrc} />}
{imgSrc && <img className={styles.img} src={imgSrc} alt={imgAlt ?? ''} />}
{children && !iconOnly && <div className={contentStyles}>{children}</div>}
{isOpen === false && <Icon name="angle-down" />}
{isOpen === true && <Icon name="angle-up" />}
@ -94,7 +97,7 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
}
);
function getButttonAriaLabel(ariaLabel: string | undefined, tooltip: string | undefined) {
function getButtonAriaLabel(ariaLabel: string | undefined, tooltip: string | undefined) {
return ariaLabel ? ariaLabel : tooltip ? selectors.components.PageToolbar.item(tooltip) : undefined;
}

View File

@ -22,6 +22,7 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
value,
onChange,
item,
id,
}) => {
const theme = useTheme2();
const styles = useStyles2(getStyles);
@ -81,6 +82,7 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
value={mode}
onChange={onModeChange}
className={styles.select}
inputId={id}
/>
<ColorValueEditor value={value?.fixedColor} onChange={onColorChange} />
</div>
@ -97,7 +99,14 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
return (
<>
<div style={{ marginBottom: theme.spacing(2) }}>
<Select menuShouldPortal minMenuHeight={200} options={options} value={mode} onChange={onModeChange} />
<Select
menuShouldPortal
minMenuHeight={200}
options={options}
value={mode}
onChange={onModeChange}
inputId={id}
/>
</div>
<Field label="Color series by">
<RadioButtonGroup value={value?.seriesBy ?? 'last'} options={seriesModes} onChange={onSeriesModeChange} />
@ -106,7 +115,9 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
);
}
return <Select menuShouldPortal minMenuHeight={200} options={options} value={mode} onChange={onModeChange} />;
return (
<Select menuShouldPortal minMenuHeight={200} options={options} value={mode} onChange={onModeChange} inputId={id} />
);
};
interface ModeProps {

View File

@ -17,6 +17,7 @@ export const SliderValueEditor: React.FC<FieldConfigEditorProps<number, SliderFi
max={settings?.max || 100}
step={settings?.step}
onChange={onChange}
ariaLabelForHandle={settings?.ariaLabelForHandle}
/>
);
};

View File

@ -6,6 +6,7 @@ export const StatsPickerEditor: React.FC<FieldConfigEditorProps<string[], StatsP
value,
onChange,
item,
id,
}) => {
return (
<StatsPicker
@ -13,6 +14,7 @@ export const StatsPickerEditor: React.FC<FieldConfigEditorProps<string[], StatsP
onChange={onChange}
allowMultiple={!!item.settings?.allowMultiple}
defaultStat={item.settings?.defaultStat}
inputId={id}
/>
);
};

View File

@ -19,6 +19,7 @@ export const Slider: FunctionComponent<SliderProps> = ({
reverse,
step,
value,
ariaLabelForHandle,
}) => {
const isHorizontal = orientation === 'horizontal';
const theme = useTheme2();
@ -79,6 +80,7 @@ export const Slider: FunctionComponent<SliderProps> = ({
onAfterChange={onAfterChange}
vertical={!isHorizontal}
reverse={reverse}
ariaLabelForHandle={ariaLabelForHandle}
/>
{/* Uses text input so that the number spinners are not shown */}
<Input

View File

@ -12,6 +12,7 @@ export interface SliderProps {
formatTooltipResult?: (value: number) => number;
onChange?: (value: number) => void;
onAfterChange?: (value?: number) => void;
ariaLabelForHandle?: string;
}
export interface RangeSliderProps {

View File

@ -15,6 +15,7 @@ export interface Props {
className?: string;
width?: number;
menuPlacement?: 'auto' | 'bottom' | 'top';
inputId?: string;
}
export class StatsPicker extends PureComponent<Props> {
@ -63,7 +64,7 @@ export class StatsPicker extends PureComponent<Props> {
};
render() {
const { stats, allowMultiple, defaultStat, placeholder, className, menuPlacement, width } = this.props;
const { stats, allowMultiple, defaultStat, placeholder, className, menuPlacement, width, inputId } = this.props;
const select = fieldReducers.selectOptions(stats);
return (
@ -79,6 +80,7 @@ export class StatsPicker extends PureComponent<Props> {
placeholder={placeholder}
onChange={this.onSelectionChange}
menuPlacement={menuPlacement}
inputId={inputId}
/>
);
}

View File

@ -151,11 +151,13 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
renderInput(threshold: ThresholdWithKey, styles: ThresholdStyles, idx: number) {
const isPercent = this.props.thresholds.mode === ThresholdsMode.Percentage;
const ariaLabel = `Threshold ${idx + 1}`;
if (!isFinite(threshold.value)) {
return (
<Input
type="text"
value={'Base'}
aria-label={ariaLabel}
disabled
prefix={
<div className={styles.colorPicker}>
@ -177,6 +179,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
key={isPercent.toString()}
onChange={(event: ChangeEvent<HTMLInputElement>) => this.onChangeThresholdValue(event, threshold)}
value={threshold.value}
aria-label={ariaLabel}
ref={idx === 0 ? this.latestThresholdInputRef : null}
onBlur={this.onBlur}
prefix={

View File

@ -225,7 +225,7 @@ const AddPanelWidgetHandle: React.FC<AddPanelWidgetHandleProps> = ({ children, o
)}
{children && <span>{children}</span>}
<div className="flex-grow-1" />
<IconButton name="times" onClick={onCancel} surface="header" />
<IconButton aria-label="Close 'Add Panel' widget" name="times" onClick={onCancel} surface="header" />
</div>
);
};

View File

@ -83,9 +83,7 @@ export const OptionsPaneCategory: FC<OptionsPaneCategoryProps> = React.memo(
<div className={cx(styles.toggle, 'editor-options-group-toggle')}>
<Icon name={isExpanded ? 'angle-down' : 'angle-right'} />
</div>
<div className={styles.title} role="heading">
{renderTitle(isExpanded)}
</div>
<h6 className={styles.title}>{renderTitle(isExpanded)}</h6>
</div>
{isExpanded && <div className={bodyStyles}>{children}</div>}
</div>
@ -108,6 +106,10 @@ const getStyles = (theme: GrafanaTheme2) => {
title: css`
flex-grow: 1;
overflow: hidden;
line-height: 1.5;
font-size: 1rem;
font-weight: ${theme.typography.fontWeightMedium};
margin: 0;
`,
header: css`
display: flex;

View File

@ -39,6 +39,7 @@ export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPane
render: function renderDescription() {
return (
<TextArea
id="description-text-area"
defaultValue={panel.description}
onBlur={(e) => onPanelConfigChange('description', e.currentTarget.value)}
/>
@ -96,6 +97,7 @@ export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPane
render: function renderRepeatOptions() {
return (
<RepeatRowSelect
id="repeat-by-variable-select"
repeat={panel.repeat}
onChange={(value?: string | null) => {
onPanelConfigChange('repeat', value);

View File

@ -98,7 +98,7 @@ export function getVizualizationOptions(props: OptionPaneRenderProps): OptionsPa
);
};
return <Editor value={value} onChange={onChange} item={fieldOption} context={context} />;
return <Editor value={value} onChange={onChange} item={fieldOption} context={context} id={fieldOption.id} />;
},
})
);
@ -161,6 +161,7 @@ export function fillOptionsPaneItems(
}}
item={pluginOption}
context={context}
id={pluginOption.id}
/>
);
},

View File

@ -7,11 +7,12 @@ import { getVariables } from '../../../variables/state/selectors';
import { StoreState } from '../../../../types';
export interface Props {
id?: string;
repeat?: string | null;
onChange: (name: string | null) => void;
}
export const RepeatRowSelect: FC<Props> = ({ repeat, onChange }) => {
export const RepeatRowSelect: FC<Props> = ({ repeat, onChange, id }) => {
const variables = useSelector((state: StoreState) => getVariables(state));
const variableOptions = useMemo(() => {
@ -36,5 +37,5 @@ export const RepeatRowSelect: FC<Props> = ({ repeat, onChange }) => {
const onSelectChange = useCallback((option: SelectableValue<string | null>) => onChange(option.value!), [onChange]);
return <Select menuShouldPortal value={repeat} onChange={onSelectChange} options={variableOptions} />;
return <Select inputId={id} menuShouldPortal value={repeat} onChange={onSelectChange} options={variableOptions} />;
};

View File

@ -114,6 +114,7 @@ export class ShareLink extends PureComponent<Props, State> {
<Field label="Link URL">
<Input
id="link-url-input"
value={shareUrl}
readOnly
addonAfter={

View File

@ -41,7 +41,7 @@ export const PanelTypeCard: React.FC<Props> = ({
onClick={disabled ? undefined : onClick}
title={isCurrent ? 'Click again to close this section' : plugin.name}
>
<img className={styles.img} src={plugin.info.logos.small} alt={`${plugin.name} logo`} />
<img className={styles.img} src={plugin.info.logos.small} alt="" />
<div className={styles.itemContent}>
<div className={styles.name}>{title}</div>

View File

@ -203,7 +203,9 @@ export class QueryGroup extends PureComponent<Props, State> {
return (
<div>
<div className={styles.dataSourceRow}>
<InlineFormLabel width={'auto'}>Data source</InlineFormLabel>
<InlineFormLabel htmlFor="data-source-picker" width={'auto'}>
Data source
</InlineFormLabel>
<div className={styles.dataSourceRowItem}>
<DataSourcePicker
onChange={this.onChangeDataSource}

View File

@ -175,6 +175,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props)
value={options.find((item) => item.value === query.scenarioId)}
onChange={onScenarioChange}
width={32}
inputId={`scenario-input-${query.refId}`}
/>
</InlineField>
{currentScenario?.stringInput && (

View File

@ -16,9 +16,10 @@
></gf-form-switch>
<div class="gf-form" ng-if="ctrl.panel.lines">
<label class="gf-form-label width-8">Line width</label>
<label class="gf-form-label width-8" for="linewidth-select-input">Line width</label>
<div class="gf-form-select-wrapper max-width-5">
<select
id="linewidth-select-input"
class="gf-form-input"
ng-model="ctrl.panel.linewidth"
ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"
@ -37,9 +38,10 @@
></gf-form-switch>
<div class="gf-form" ng-if="ctrl.panel.lines">
<label class="gf-form-label width-8">Area fill</label>
<label class="gf-form-label width-8" for="fill-select-input">Area fill</label>
<div class="gf-form-select-wrapper max-width-5">
<select
id="fill-select-input"
class="gf-form-input"
ng-model="ctrl.panel.fill"
ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"
@ -69,9 +71,10 @@
></gf-form-switch>
<div class="gf-form" ng-if="ctrl.panel.points">
<label class="gf-form-label width-8">Point Radius</label>
<label class="gf-form-label width-8" for="pointradius-select-input">Point Radius</label>
<div class="gf-form-select-wrapper max-width-5">
<select
id="pointradius-select-input"
class="gf-form-input"
ng-model="ctrl.panel.pointradius"
ng-options="f for f in [0.5,1,2,3,4,5,6,7,8,9,10]"
@ -87,7 +90,6 @@
checked="ctrl.panel.options.alertThreshold"
on-change="ctrl.render()"
></gf-form-switch>
</div>
<div class="gf-form-group">
@ -110,9 +112,10 @@
>
</gf-form-switch>
<div class="gf-form">
<label class="gf-form-label width-7">Null value</label>
<label class="gf-form-label width-7" for="null-value-select-input">Null value</label>
<div class="gf-form-select-wrapper">
<select
id="null-value-select-input"
class="gf-form-input max-width-9"
ng-model="ctrl.panel.nullPointMode"
ng-options="f for f in ['connected', 'null', 'null as zero']"
@ -125,9 +128,10 @@
<div class="gf-form-group">
<h5 class="section-heading">Hover tooltip</h5>
<div class="gf-form">
<label class="gf-form-label width-9">Mode</label>
<label class="gf-form-label width-9" for="tooltip-mode-select-input">Mode</label>
<div class="gf-form-select-wrapper max-width-8">
<select
id="tooltip-mode-select-input"
class="gf-form-input"
ng-model="ctrl.panel.tooltip.shared"
ng-options="f.value as f.text for f in [{text: 'All series', value: true}, {text: 'Single', value: false}]"
@ -136,9 +140,10 @@
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">Sort order</label>
<label class="gf-form-label width-9" for="tooltip-sort-select-input">Sort order</label>
<div class="gf-form-select-wrapper max-width-8">
<select
id="tooltip-sort-select-input"
class="gf-form-input"
ng-model="ctrl.panel.tooltip.sort"
ng-options="f.value as f.text for f in [{text: 'None', value: 0}, {text: 'Increasing', value: 1}, {text: 'Decreasing', value: 2}]"

View File

@ -5,7 +5,7 @@ import { Select } from '@grafana/ui';
export const ThresholdsStyleEditor: React.FC<
FieldOverrideEditorProps<SelectableValue<{ mode: GraphTresholdsStyleMode }>, any>
> = ({ item, value, onChange }) => {
> = ({ item, value, onChange, id }) => {
const onChangeCb = useCallback(
(v: SelectableValue<GraphTresholdsStyleMode>) => {
onChange({
@ -14,5 +14,7 @@ export const ThresholdsStyleEditor: React.FC<
},
[onChange]
);
return <Select menuShouldPortal value={value.mode} options={item.settings.options} onChange={onChangeCb} />;
return (
<Select inputId={id} menuShouldPortal value={value.mode} options={item.settings.options} onChange={onChangeCb} />
);
};

View File

@ -95,6 +95,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
min: 0,
max: 10,
step: 1,
ariaLabelForHandle: 'Line width',
},
showIf: (c) => c.drawStyle !== GraphDrawStyle.Points,
})
@ -107,6 +108,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
min: 0,
max: 100,
step: 1,
ariaLabelForHandle: 'Fill opacity',
},
showIf: (c) => c.drawStyle !== GraphDrawStyle.Points,
})
@ -173,6 +175,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
min: 1,
max: 40,
step: 1,
ariaLabelForHandle: 'Point size',
},
showIf: (c) => c.showPoints !== VisibilityMode.Never || c.drawStyle === GraphDrawStyle.Points,
});