diff --git a/packages/react-core/src/components/Slider/Slider.tsx b/packages/react-core/src/components/Slider/Slider.tsx index 946f61b80e5..bcb6f21790d 100644 --- a/packages/react-core/src/components/Slider/Slider.tsx +++ b/packages/react-core/src/components/Slider/Slider.tsx @@ -4,7 +4,7 @@ import { css } from '@patternfly/react-styles'; import { SliderStep } from './SliderStep'; import { InputGroup, InputGroupText, InputGroupItem } from '../InputGroup'; import { TextInput } from '../TextInput'; -import { Tooltip } from '../Tooltip'; +import { Tooltip, TooltipProps } from '../Tooltip'; import cssSliderValue from '@patternfly/react-tokens/dist/esm/c_slider_value'; import cssFormControlWidthChars from '@patternfly/react-tokens/dist/esm/c_slider__value_c_form_control_width_chars'; import { getLanguageDirection } from '../../helpers/util'; @@ -42,8 +42,12 @@ export interface SliderProps extends Omit, 'onCh className?: string; /** Array of custom slider step objects (value and label of each step) for the slider. */ customSteps?: SliderStepObject[]; - /* Adds a tooltip over the slider thumb containing the current value. */ + /** Enables a tooltip over the silder thumb. Defaults to the current value, or tooltipContent if provided. */ hasTooltipOverThumb?: boolean; + /** Content of the tooltip over the slider thumb. Defaults to the current value. */ + tooltipContent?: React.ReactNode; + /** Additional props passed to the tooltip. */ + tooltipProps?: Omit; /** Accessible label for the input field. */ inputAriaLabel?: string; /** Text label that is place after the input field. */ @@ -81,8 +85,10 @@ export interface SliderProps extends Omit, 'onCh showTicks?: boolean; /** The step interval. */ step?: number; - /* Accessible label for the slider thumb. */ + /** Accessible label for the slider thumb. */ thumbAriaLabel?: string; + /** Accessible text for the current value of the slider. Defaults to the current value. */ + thumbAriaValueText?: string; /** Current value of the slider. */ value?: number; } @@ -100,7 +106,10 @@ export const Slider: React.FunctionComponent = ({ inputLabel, inputAriaLabel = 'Slider value input', thumbAriaLabel = 'Value', + thumbAriaValueText, hasTooltipOverThumb = false, + tooltipContent, + tooltipProps, inputPosition = 'end', onChange, leftActions, @@ -425,7 +434,7 @@ export const Slider: React.FunctionComponent = ({ aria-valuemin={customSteps ? customSteps[0].value : min} aria-valuemax={customSteps ? customSteps[customSteps.length - 1].value : max} aria-valuenow={localValue} - aria-valuetext={findAriaTextValue()} + aria-valuetext={thumbAriaValueText ?? findAriaTextValue()} aria-label={thumbAriaLabel} aria-disabled={isDisabled} aria-describedby={ariaDescribedby} @@ -477,7 +486,8 @@ export const Slider: React.FunctionComponent = ({ className={css('pf-v6-m-tabular-nums')} triggerRef={thumbRef} entryDelay={0} - content={findAriaTextValue()} + content={tooltipContent ?? findAriaTextValue()} + {...tooltipProps} > {thumbComponent} diff --git a/packages/react-core/src/components/Slider/__tests__/Slider.test.tsx b/packages/react-core/src/components/Slider/__tests__/Slider.test.tsx index 7760c772faf..8bb83203c76 100644 --- a/packages/react-core/src/components/Slider/__tests__/Slider.test.tsx +++ b/packages/react-core/src/components/Slider/__tests__/Slider.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Slider } from '../Slider'; import { Button } from '../../Button'; @@ -77,6 +78,17 @@ describe('slider', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); + + test('renders slider with custom tooltip content on thumb', async () => { + const user = userEvent.setup(); + + render(); + + await user.hover(screen.getByRole('slider')); + + await screen.findByRole('tooltip'); + expect(screen.getByRole('tooltip')).toHaveTextContent('Custom tooltip content'); + }); }); test('renders slider with aria-labelledby', () => { @@ -104,3 +116,11 @@ test('renders slider with aria-describedby', () => { expect(slider).toBeVisible(); }); + +test('renders slider with thumbAriaValueText', () => { + render(); + + const slider = screen.getByRole('slider'); + + expect(slider).toHaveAttribute('aria-valuetext', 'Half capacity'); +}); diff --git a/packages/react-core/src/components/Slider/examples/Slider.md b/packages/react-core/src/components/Slider/examples/Slider.md index 209d8ecfcac..ab71f6643fb 100644 --- a/packages/react-core/src/components/Slider/examples/Slider.md +++ b/packages/react-core/src/components/Slider/examples/Slider.md @@ -50,6 +50,16 @@ import RhUiUnlockFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-unl ``` +### Custom step tooltip + +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. + +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. + +```ts file="./SliderCustomTooltip.tsx" + +``` + ## Types ### SliderOnChangeEvent diff --git a/packages/react-core/src/components/Slider/examples/SliderCustomTooltip.tsx b/packages/react-core/src/components/Slider/examples/SliderCustomTooltip.tsx new file mode 100644 index 00000000000..0cc075f612a --- /dev/null +++ b/packages/react-core/src/components/Slider/examples/SliderCustomTooltip.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react'; +import { Slider, SliderOnChangeEvent } from '@patternfly/react-core'; + +export const SliderCustomTooltip: React.FunctionComponent = () => { + const [value, setValue] = useState(50); + const steps = [ + { value: 0, label: '0' }, + { value: 12.5, label: '1', isLabelHidden: true }, + { value: 25, label: '2' }, + { value: 37.5, label: '3', isLabelHidden: true }, + { value: 50, label: '4' }, + { value: 62.5, label: '5', isLabelHidden: true }, + { value: 75, label: '6' }, + { value: 87.5, label: '7', isLabelHidden: true }, + { value: 100, label: '8' } + ]; + + const displayValue = () => { + const step = steps.find((step) => step.value === value); + return step ? step.label : 0; + }; + + const customTooltipContent = () =>
Custom tooltip content for step: {displayValue()}
; + + return ( + setValue(value)} + customSteps={steps} + /> + ); +};