Skip to content

Commit ad3b35b

Browse files
authored
feat(Slider): add support for custom tooltip content (#12531)
1 parent bcdd2a8 commit ad3b35b

4 files changed

Lines changed: 79 additions & 5 deletions

File tree

packages/react-core/src/components/Slider/Slider.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { css } from '@patternfly/react-styles';
44
import { SliderStep } from './SliderStep';
55
import { InputGroup, InputGroupText, InputGroupItem } from '../InputGroup';
66
import { TextInput } from '../TextInput';
7-
import { Tooltip } from '../Tooltip';
7+
import { Tooltip, TooltipProps } from '../Tooltip';
88
import cssSliderValue from '@patternfly/react-tokens/dist/esm/c_slider_value';
99
import cssFormControlWidthChars from '@patternfly/react-tokens/dist/esm/c_slider__value_c_form_control_width_chars';
1010
import { getLanguageDirection } from '../../helpers/util';
@@ -42,8 +42,12 @@ export interface SliderProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onCh
4242
className?: string;
4343
/** Array of custom slider step objects (value and label of each step) for the slider. */
4444
customSteps?: SliderStepObject[];
45-
/* Adds a tooltip over the slider thumb containing the current value. */
45+
/** Enables a tooltip over the silder thumb. Defaults to the current value, or tooltipContent if provided. */
4646
hasTooltipOverThumb?: boolean;
47+
/** Content of the tooltip over the slider thumb. Defaults to the current value. */
48+
tooltipContent?: React.ReactNode;
49+
/** Additional props passed to the tooltip. */
50+
tooltipProps?: Omit<TooltipProps, 'content'>;
4751
/** Accessible label for the input field. */
4852
inputAriaLabel?: string;
4953
/** Text label that is place after the input field. */
@@ -83,8 +87,10 @@ export interface SliderProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onCh
8387
showTicks?: boolean;
8488
/** The step interval. */
8589
step?: number;
86-
/* Accessible label for the slider thumb. */
90+
/** Accessible label for the slider thumb. */
8791
thumbAriaLabel?: string;
92+
/** Accessible text for the current value of the slider. Defaults to the current value. */
93+
thumbAriaValueText?: string;
8894
/** Current value of the slider. */
8995
value?: number;
9096
}
@@ -103,7 +109,10 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
103109
inputLabel,
104110
inputAriaLabel = 'Slider value input',
105111
thumbAriaLabel = 'Value',
112+
thumbAriaValueText,
106113
hasTooltipOverThumb = false,
114+
tooltipContent,
115+
tooltipProps,
107116
inputPosition = 'end',
108117
onChange,
109118
leftActions,
@@ -431,7 +440,7 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
431440
aria-valuemin={customSteps ? customSteps[0].value : min}
432441
aria-valuemax={customSteps ? customSteps[customSteps.length - 1].value : max}
433442
aria-valuenow={localValue}
434-
aria-valuetext={findAriaTextValue()}
443+
aria-valuetext={thumbAriaValueText ?? findAriaTextValue()}
435444
aria-label={thumbAriaLabel}
436445
aria-disabled={isDisabled}
437446
aria-describedby={ariaDescribedby}
@@ -483,7 +492,8 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
483492
className={css('pf-v6-m-tabular-nums')}
484493
triggerRef={thumbRef}
485494
entryDelay={0}
486-
content={findAriaTextValue()}
495+
content={tooltipContent ?? findAriaTextValue()}
496+
{...tooltipProps}
487497
>
488498
{thumbComponent}
489499
</Tooltip>

packages/react-core/src/components/Slider/__tests__/Slider.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
23
import { Slider } from '../Slider';
34
import { Button } from '../../Button';
45

@@ -77,6 +78,17 @@ describe('slider', () => {
7778
const { asFragment } = render(<Slider value={50} hasTooltipOverThumb />);
7879
expect(asFragment()).toMatchSnapshot();
7980
});
81+
82+
test('renders slider with custom tooltip content on thumb', async () => {
83+
const user = userEvent.setup();
84+
85+
render(<Slider value={50} hasTooltipOverThumb tooltipContent="Custom tooltip content" />);
86+
87+
await user.hover(screen.getByRole('slider'));
88+
89+
await screen.findByRole('tooltip');
90+
expect(screen.getByRole('tooltip')).toHaveTextContent('Custom tooltip content');
91+
});
8092
});
8193

8294
test('renders slider with aria-labelledby', () => {
@@ -104,3 +116,11 @@ test('renders slider with aria-describedby', () => {
104116

105117
expect(slider).toBeVisible();
106118
});
119+
120+
test('renders slider with thumbAriaValueText', () => {
121+
render(<Slider value={50} thumbAriaValueText="Half capacity" />);
122+
123+
const slider = screen.getByRole('slider');
124+
125+
expect(slider).toHaveAttribute('aria-valuetext', 'Half capacity');
126+
});

packages/react-core/src/components/Slider/examples/Slider.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ import RhUiUnlockFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-unl
5050

5151
```
5252

53+
### Custom step tooltip
54+
55+
You can customize the content of the tooltip by passing the tooltipContent property. By default this tooltip will act as a description to the slider thumb, and thus shouldn't include critical information about the current slider step unless that information is part of the step's aria-valuetext.
56+
57+
If instead you want the tooltip to act as the human-readable value of the slider step - such as when all slider step labels are hidden - you must also pass the thumbAriaValueText property with the same string value as the tooltipContent. Additionally, you should pass the tooltip props object `{aria: 'none', 'aria-live': 'off'}` to tooltipProps in order to help prevent duplicate announcement from assistive technologies.
58+
59+
```ts file="./SliderCustomTooltip.tsx"
60+
61+
```
62+
5363
## Types
5464

5565
### SliderOnChangeEvent
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useState } from 'react';
2+
import { Slider, SliderOnChangeEvent } from '@patternfly/react-core';
3+
4+
export const SliderCustomTooltip: React.FunctionComponent = () => {
5+
const [value, setValue] = useState(50);
6+
const steps = [
7+
{ value: 0, label: '0' },
8+
{ value: 12.5, label: '1', isLabelHidden: true },
9+
{ value: 25, label: '2' },
10+
{ value: 37.5, label: '3', isLabelHidden: true },
11+
{ value: 50, label: '4' },
12+
{ value: 62.5, label: '5', isLabelHidden: true },
13+
{ value: 75, label: '6' },
14+
{ value: 87.5, label: '7', isLabelHidden: true },
15+
{ value: 100, label: '8' }
16+
];
17+
18+
const displayValue = () => {
19+
const step = steps.find((step) => step.value === value);
20+
return step ? step.label : 0;
21+
};
22+
23+
const customTooltipContent = () => <div>Custom tooltip content for step: {displayValue()}</div>;
24+
25+
return (
26+
<Slider
27+
hasTooltipOverThumb
28+
tooltipContent={customTooltipContent()}
29+
value={value}
30+
onChange={(_event: SliderOnChangeEvent, value: number) => setValue(value)}
31+
customSteps={steps}
32+
/>
33+
);
34+
};

0 commit comments

Comments
 (0)