mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
A11y/Dashboard: Fix misc. fastpass issues (#40296)
* A11y/Dashboard: Fix misc. fastpass issues See #39429
This commit is contained in:
parent
0ac81e9e47
commit
c443f244a0
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -39,6 +39,7 @@ export interface SliderFieldConfigSettings {
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
ariaLabelForHandle?: string;
|
||||
}
|
||||
|
||||
export interface DataLinksFieldConfigSettings {}
|
||||
|
@ -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>>;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -17,6 +17,7 @@ export const SliderValueEditor: React.FC<FieldConfigEditorProps<number, SliderFi
|
||||
max={settings?.max || 100}
|
||||
step={settings?.step}
|
||||
onChange={onChange}
|
||||
ariaLabelForHandle={settings?.ariaLabelForHandle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -12,6 +12,7 @@ export interface SliderProps {
|
||||
formatTooltipResult?: (value: number) => number;
|
||||
onChange?: (value: number) => void;
|
||||
onAfterChange?: (value?: number) => void;
|
||||
ariaLabelForHandle?: string;
|
||||
}
|
||||
|
||||
export interface RangeSliderProps {
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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={
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
@ -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} />;
|
||||
};
|
||||
|
@ -114,6 +114,7 @@ export class ShareLink extends PureComponent<Props, State> {
|
||||
|
||||
<Field label="Link URL">
|
||||
<Input
|
||||
id="link-url-input"
|
||||
value={shareUrl}
|
||||
readOnly
|
||||
addonAfter={
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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 && (
|
||||
|
@ -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}]"
|
||||
|
@ -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} />
|
||||
);
|
||||
};
|
||||
|
@ -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,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user