[Lens] add an accessible label to range slider (#205308)

## Summary

Fixes https://github.com/elastic/kibana/issues/182765 (with the solution
suggested by @1Copenut)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marta Bondyra 2025-01-25 23:11:46 +01:00 committed by GitHub
parent 235d5ebe53
commit ed5728302a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 105 additions and 15 deletions

View file

@ -10,13 +10,42 @@ import React from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import { ControlSlider } from '.';
const values = [
{
label: '.001%',
value: 0.00001,
accessibleLabel: 'Point zero zero one percent, most performant',
},
{
label: '.01%',
value: 0.0001,
},
{
label: '.1%',
value: 0.001,
},
{
label: '1%',
value: 0.01,
},
{
label: '10%',
value: 0.1,
},
{
label: '100%',
value: 1,
accessibleLabel: 'One hundred percent, most accurate',
},
];
describe('Slider Control', () => {
it('should basically work', () => {
render(
<I18nProvider>
<ControlSlider
values={[0.1, 1]}
currentValue={0.1}
values={values}
currentValue={0.00001}
onChange={jest.fn()}
data-test-subj="test-id"
/>
@ -26,18 +55,35 @@ describe('Slider Control', () => {
expect(input.value).toBe('0'); // index 0 of the values array
});
it('should fallback to 1 when the provided value is not present within the values', () => {
it('should display accessible label when provided', () => {
render(
<I18nProvider>
<ControlSlider
values={[0.1, 0.5, 1]}
currentValue={2}
values={values}
currentValue={0.00001}
onChange={jest.fn()}
data-test-subj="test-id"
/>
</I18nProvider>
);
const input = screen.getByTestId('test-id') as HTMLInputElement;
expect(input.value).toBe('2'); // index 2 of the values array
expect(input.getAttribute('aria-valuetext')).toBe(
'0, (Point zero zero one percent, most performant)'
);
});
it('should fallback to 1 when the provided value is not present within the values', () => {
render(
<I18nProvider>
<ControlSlider
values={values}
currentValue={4}
onChange={jest.fn()}
data-test-subj="test-id"
/>
</I18nProvider>
);
const input = screen.getByTestId('test-id') as HTMLInputElement;
expect(input.value).toBe('5'); // index 5 of the values array
});
});

View file

@ -9,10 +9,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiRange, EuiText, useEuiTheme } from '@elas
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { TooltipWrapper } from '@kbn/visualization-utils';
import { i18n } from '@kbn/i18n';
export interface ControlSliderProps {
/** Allowed values to show on the Control Slider */
values: number[];
values: Array<{ label: string; value: number; accessibleLabel?: string }>;
/** Current value set */
currentValue: number | undefined;
/** When set will show the control in a disabled state */
@ -34,8 +35,10 @@ export function ControlSlider({
'data-test-subj': dataTestSubj,
}: ControlSliderProps) {
const { euiTheme } = useEuiTheme();
const samplingIndex = values.findIndex((v) => v === currentValue);
const samplingIndex = values.findIndex((v) => v.value === currentValue);
const currentSamplingIndex = samplingIndex > -1 ? samplingIndex : values.length - 1;
return (
<TooltipWrapper
tooltipContent={disabledReason}
@ -58,11 +61,18 @@ export function ControlSlider({
<EuiFlexItem>
<EuiRange
data-test-subj={dataTestSubj}
aria-label={i18n.translate('randomSampling.ui.sliderControl.ariaLabel', {
defaultMessage: 'Sampling percentages',
})}
aria-describedby={i18n.translate('randomSampling.ui.sliderControl.ariaDescribedby', {
defaultMessage:
'Lower sampling percentages increases the performance, but lowers the accuracy. Lower sampling percentages are best for large datasets.',
})}
value={currentSamplingIndex}
disabled={disabled}
fullWidth
onChange={(e) => {
onChange(values[Number(e.currentTarget.value)]);
onChange(values[Number(e.currentTarget.value)].value);
}}
showInput={false}
showRange={false}
@ -70,10 +80,9 @@ export function ControlSlider({
step={1}
min={0}
max={values.length - 1}
ticks={values.map((v, i) => ({
// Remove the initial 0 from values with decimal digits: 0.001 => .001
label: `${v * 100}%`.slice(Number.isInteger(v * 100) ? 0 : 1),
value: i,
ticks={values.map((tick, index) => ({
...tick,
value: index,
}))}
/>
</EuiFlexItem>

View file

@ -14,6 +14,7 @@
"kbn_references": [
"@kbn/i18n-react",
"@kbn/visualization-utils",
"@kbn/i18n",
],
"exclude": [
"target/**/*",

View file

@ -17,7 +17,41 @@ import { IgnoreGlobalFilterRowControl } from '../../shared_components/ignore_glo
import { trackUiCounterEvents } from '../../lens_ui_telemetry';
import { ExperimentalBadge } from '../../shared_components';
const samplingValues = [0.00001, 0.0001, 0.001, 0.01, 0.1, 1];
const samplingValues = [
{
label: '.001%',
value: 0.00001,
accessibleLabel: i18n.translate(
'xpack.lens.randomSampling.ui.sliderControl.tickLabels.0.00001',
{
defaultMessage: 'Point zero zero one percent, most performant',
}
),
},
{
label: '.01%',
value: 0.0001,
},
{
label: '.1%',
value: 0.001,
},
{
label: '1%',
value: 0.01,
},
{
label: '10%',
value: 0.1,
},
{
label: '100%',
value: 1,
accessibleLabel: i18n.translate('xpack.lens.randomSampling.ui.sliderControl.tickLabels.1', {
defaultMessage: 'One hundred percent, most accurate',
}),
},
];
export function LayerSettingsPanel({
state,
@ -26,7 +60,7 @@ export function LayerSettingsPanel({
}: DatasourceLayerSettingsProps<FormBasedPrivateState>) {
const isSamplingValueDisabled = !isSamplingValueEnabled(state.layers[layerId]);
const currentValue = isSamplingValueDisabled
? samplingValues[samplingValues.length - 1]
? samplingValues[samplingValues.length - 1].value
: state.layers[layerId].sampling;
return (