Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/upcoming/7675.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Accessibility**

- Added `aria-valuetext` attributes to `EuiRange`s with tick labels for improved screen reader UX
7 changes: 7 additions & 0 deletions src-docs/src/views/range/range_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,13 @@ export const RangeControlExample = {
<EuiCode>label</EuiCode>. The value must be included in the range of
values (min-max), though the label may be anything you choose.
</p>
<p>
Tick labels can improve the accessibility of your range. If your
label is a simple string, it will be read out to screen readers
alongside the value. You can also use the{' '}
<EuiCode>accessibleLabel</EuiCode> property to provide more explicit
screen reader text.
</p>
<EuiCallOut
color="warning"
title="Minimum of 5px width per tick allowed"
Expand Down
16 changes: 8 additions & 8 deletions src-docs/src/views/range/ticks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ export default () => {
min={0}
max={84}
ticks={[
{ label: '1 GB', value: 0 },
{ label: '2GB', value: 14 },
{ label: '4GB', value: 28 },
{ label: '8GB', value: 42 },
{ label: '16GB', value: 56 },
{ label: '32GB', value: 70 },
{ label: '64GB', value: 84 },
{ label: '1 GB', value: 0, accessibleLabel: 'one gigabyte' },
{ label: '2GB', value: 14, accessibleLabel: 'two gigabytes' },
{ label: '4GB', value: 28, accessibleLabel: 'four gigabytes' },
{ label: '8GB', value: 42, accessibleLabel: 'eight gigabytes' },
{ label: '16GB', value: 56, accessibleLabel: 'sixteen gigabytes' },
{ label: '32GB', value: 70, accessibleLabel: 'thirty-two gigabytes' },
{ label: '64GB', value: 84, accessibleLabel: 'sixty-four gigabytes' },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍 Now this is an example that makes sense to me! Huzzah!

]}
aria-label="An example of EuiDualRange with no linear intervals"
aria-label="An example of EuiRange with no linear intervals"
/>
</>
);
Expand Down
62 changes: 62 additions & 0 deletions src/components/form/range/range.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,66 @@ describe('EuiRange', () => {
expect(container.firstChild).toMatchSnapshot();
});
});

describe('input aria-valuetext', () => {
it('should exist when the current value has an accessible label', () => {
const { getByRole } = render(
<EuiRange
{...props}
showTicks
ticks={[
{
label: '20kb',
value: 20,
accessibleLabel: 'twenty kilobytes',
},
{
label: '100kb',
value: 100,
accessibleLabel: 'one-hundred kilobytes',
},
]}
value={20}
/>
);
expect(getByRole('slider')).toHaveAttribute(
'aria-valuetext',
'20, (twenty kilobytes)'
);
});

it('falls back to string `label`s if `accessibleLabel` does not exist', () => {
const { getByRole } = render(
<EuiRange
{...props}
showTicks
ticks={[
{ label: '20kb', value: 20 },
{ label: '100kb', value: 100 },
]}
value={20}
/>
);

expect(getByRole('slider')).toHaveAttribute(
'aria-valuetext',
'20, (20kb)'
);
});

it('should not exist when the current value does not have a matching label', () => {
const { getByRole } = render(
<EuiRange
{...props}
showTicks
ticks={[
{ value: 20, label: '20kb', accessibleLabel: 'twenty kilobytes' },
]}
value={10}
/>
);

expect(getByRole('slider')).not.toHaveAttribute('aria-valuetext');
});
});
});
22 changes: 21 additions & 1 deletion src/components/form/range/range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { EuiRangeTooltip } from './range_tooltip';
import { EuiRangeTrack } from './range_track';
import { EuiRangeWrapper } from './range_wrapper';

import type { EuiRangeProps } from './types';
import type { EuiRangeProps, EuiRangeTick } from './types';

import { euiRangeStyles } from './range.styles';
import { EuiI18n } from '../../i18n';
Expand Down Expand Up @@ -116,6 +116,23 @@ export class EuiRangeClass extends Component<
});
};

handleAriaValueText = (
ticks: EuiRangeTick[],
currentVal: string | number
): string | undefined => {
const target = ticks.find(
(tick) => tick.value.toString() === currentVal.toString()
);

if (target) {
return target.accessibleLabel
? `${target.value}, (${target.accessibleLabel})`
: typeof target.label === 'string' // Fall back to the label if it's a usable string
? `${target.value}, (${target.label})`
: undefined;
}
};

render() {
const { defaultFullWidth } = this.context as FormContextValue;
const {
Expand Down Expand Up @@ -220,6 +237,9 @@ export class EuiRangeClass extends Component<
showRange={showRange}
>
<EuiRangeSlider
ariaValueText={
ticks ? this.handleAriaValueText(ticks, value) : undefined
}
id={showInput ? undefined : id} // Attach id only to the input if there is one
name={name}
min={min}
Expand Down
3 changes: 3 additions & 0 deletions src/components/form/range/range_slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface EuiRangeSliderProps
onChange?: ChangeEventHandler<HTMLInputElement>;
thumbColor?: EuiRangeLevel['color'];
onResize: EuiResizeObserverProps['onResize'];
ariaValueText?: string;
}

export const EuiRangeSlider: FunctionComponent<EuiRangeSliderProps> = ({
Expand All @@ -69,6 +70,7 @@ export const EuiRangeSlider: FunctionComponent<EuiRangeSliderProps> = ({
showRange,
thumbColor,
onResize,
ariaValueText,
...rest
}) => {
const classes = classNames('euiRangeSlider', className);
Expand All @@ -94,6 +96,7 @@ export const EuiRangeSlider: FunctionComponent<EuiRangeSliderProps> = ({
<EuiResizeObserver onResize={onResize}>
{(resizeRef) => (
<input
aria-valuetext={ariaValueText}
ref={resizeRef}
type="range"
id={id}
Expand Down
1 change: 1 addition & 0 deletions src/components/form/range/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export interface EuiDualRangeProps
export interface EuiRangeTick {
value: number;
label: ReactNode;
accessibleLabel?: string;
}

export interface EuiRangeLevel
Expand Down