Skip to content

Commit

Permalink
feat(slider): add tooltips for single and two handle sliders (carbon-…
Browse files Browse the repository at this point in the history
…design-system#15129)

* fix(slider): refine what is focusable to be more broadly inclusive

* feat(slider): add tooltips to slider

* fix(slider): adjust for nice default tooltips on single handle

* fix(slider): correct single handle RTL bug

* fix(slider): put back the focus handler for the lower thumb

* fix(slider): rtl positioning bug

* chore: udpate tests

* test(slider): adjust unit tests for slider w/ tooltips

* fix(slider): remove space when text input is hidden

* fix(slider): restore blue focus filled track

* feat(slider): adjustments to tooltip positioning

* feat(slider): use a ThumbWrapper component to conditionaly do tooltips

* feat(slider): add stories for hidden inputs

* fix(tooltip): hold the tooltip open if the user is mid-drag interaction

* refactor(slider): move slider handle componets into own file

* fix(slider): make skeletons good again

* fix(slider): manage focus when activating using the track

* chore(slider): remove redundant preventDefault

* fix(slider): more robust activeHandle detection

* test(slider): fix slider tests

* fix(slider): fix positioning of thumbs for rtl

* fix(slider): correct tooltips rtl

---------

Co-authored-by: Andrea N. Cardona <cardona.n.andrea@gmail.com>
  • Loading branch information
2 people authored and danoro96 committed Jan 18, 2024
1 parent 5143da0 commit 12adfbc
Show file tree
Hide file tree
Showing 9 changed files with 522 additions and 181 deletions.
59 changes: 37 additions & 22 deletions packages/react/src/components/Slider/Slider-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,12 @@ describe('Slider', () => {
});

it('should accurately position slider on mount', () => {
renderSlider({ value: 50, max: 100, min: 0 });
expect(screen.getByRole('slider')).toHaveStyle({
const { container } = renderSlider({ value: 50, max: 100, min: 0 });
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const sliderWrapper = container.querySelector(
`.${prefix}--slider__thumb-wrapper`
);
expect(sliderWrapper).toHaveStyle({
insetInlineStart: '50%',
});
});
Expand Down Expand Up @@ -681,40 +685,51 @@ describe('Slider', () => {

// Keyboard interactions on the upper thumb, lets mix it up and do the up
// and down arrow keys this time.
// Note: Somewhat unintuitively, a click on the upperThumb in the moves it
// as close to the lowerThumb as possible. This is because the elements
// in Jest don't exist in a specific position, layer and size.
// @see https://testing-library.com/docs/user-event/pointer
await click(upperThumb);
expect(upperThumb).toHaveFocus();
await keyboard('{ArrowUp}');
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 91 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '91');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '91');
expect(upperInput).toHaveValue(91);
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 11 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '11');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '11');
expect(upperInput).toHaveValue(11);
await keyboard('{ArrowDown}');
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 90 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '90');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '90');
expect(upperInput).toHaveValue(90);
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 10 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '10');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '10');
expect(upperInput).toHaveValue(10);
await keyboard('{Shift>}{ArrowUp}{/Shift}');
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 94 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '94');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '94');
expect(upperInput).toHaveValue(94);
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 14 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '14');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '14');
expect(upperInput).toHaveValue(14);
await keyboard('{Shift>}{ArrowDown}{/Shift}');
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 90 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '90');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '90');
expect(upperInput).toHaveValue(90);
expect(onChange).toHaveBeenLastCalledWith({ value: 10, valueUpper: 10 });
expect(upperThumb).toHaveAttribute('aria-valuenow', '10');
expect(lowerThumb).toHaveAttribute('aria-valuemax', '10');
expect(upperInput).toHaveValue(10);
});

it('should accurately position handles on mount', () => {
renderTwoHandleSlider({
const { container } = renderTwoHandleSlider({
value: 50,
unstable_valueUpper: 50,
min: 0,
max: 100,
});
const [lowerThumb, upperThumb] = screen.getAllByRole('slider');
expect(lowerThumb).toHaveStyle({ insetInlineStart: '50%' });
expect(upperThumb).toHaveStyle({ insetInlineStart: '50%' });
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const sliderWrapperLower = container.querySelector(
`.${prefix}--slider__thumb-wrapper--lower`
);
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const sliderWrapperUpper = container.querySelector(
`.${prefix}--slider__thumb-wrapper--upper`
);
expect(sliderWrapperLower).toHaveStyle({ insetInlineStart: '50%' });
expect(sliderWrapperUpper).toHaveStyle({ insetInlineStart: '50%' });
});

it('marks input field as hidden if hidden via props', () => {
Expand Down
37 changes: 35 additions & 2 deletions packages/react/src/components/Slider/Slider.Skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, { HTMLAttributes } from 'react';
import cx from 'classnames';
import { usePrefix } from '../../internal/usePrefix';
import classNames from 'classnames';
import { LowerHandle, UpperHandle } from './SliderHandles';

export interface SliderSkeletonProps extends HTMLAttributes<HTMLDivElement> {
/**
Expand All @@ -35,11 +36,13 @@ const SliderSkeleton = ({
...rest
}: SliderSkeletonProps) => {
const prefix = usePrefix();
const isRtl = document?.dir === 'rtl';
const containerClasses = classNames(
`${prefix}--slider-container`,
`${prefix}--skeleton`,
{
[`${prefix}--slider-container--two-handles`]: twoHandles,
[`${prefix}--slider-container--rtl`]: isRtl,
}
);
const lowerThumbClasses = classNames(`${prefix}--slider__thumb`, {
Expand All @@ -48,6 +51,18 @@ const SliderSkeleton = ({
const upperThumbClasses = classNames(`${prefix}--slider__thumb`, {
[`${prefix}--slider__thumb--upper`]: twoHandles,
});
const lowerThumbWrapperClasses = classNames(
`${prefix}--slider__thumb-wrapper`,
{
[`${prefix}--slider__thumb-wrapper--lower`]: twoHandles,
}
);
const upperThumbWrapperClasses = classNames(
`${prefix}--slider__thumb-wrapper`,
{
[`${prefix}--slider__thumb-wrapper--upper`]: twoHandles,
}
);
return (
<div className={cx(`${prefix}--form-item`, className)} {...rest}>
{!hideLabel && (
Expand All @@ -58,8 +73,26 @@ const SliderSkeleton = ({
<div className={`${prefix}--slider`}>
<div className={`${prefix}--slider__track`} />
<div className={`${prefix}--slider__filled-track`} />
<div className={lowerThumbClasses} />
{twoHandles ? <div className={upperThumbClasses} /> : undefined}
<div className={lowerThumbWrapperClasses}>
<div className={lowerThumbClasses}>
{twoHandles && !isRtl ? (
<LowerHandle />
) : twoHandles && isRtl ? (
<UpperHandle />
) : undefined}
</div>
</div>
{twoHandles ? (
<div className={upperThumbWrapperClasses}>
<div className={upperThumbClasses}>
{twoHandles && !isRtl ? (
<UpperHandle />
) : twoHandles && isRtl ? (
<LowerHandle />
) : undefined}
</div>
</div>
) : undefined}
</div>
<span className={`${prefix}--slider__range-label`} />
</div>
Expand Down
30 changes: 30 additions & 0 deletions packages/react/src/components/Slider/Slider.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ export const Default = () => (
/>
);

export const SliderWithHiddenInputs = () => (
<Slider
labelText="Slider label"
value={50}
min={0}
max={100}
step={1}
stepMultiplier={10}
noValidate
invalidText="Invalid message goes here"
hideTextInput={true}
/>
);

export const ControlledSlider = () => {
const [val, setVal] = useState(87);
return (
Expand Down Expand Up @@ -110,6 +124,22 @@ export const TwoHandleSlider = () => (
/>
);

export const TwoHandleSliderWithHiddenInputs = () => (
<Slider
ariaLabelInput="Lower bound"
unstable_ariaLabelInputUpper="Upper bound"
labelText="Slider label"
value={10}
unstable_valueUpper={90}
min={0}
max={100}
step={1}
stepMultiplier={10}
invalidText="Invalid message goes here"
hideTextInput={true}
/>
);

export const Skeleton = () => <SliderSkeleton />;

export const TwoHandleSkeleton = () => <SliderSkeleton twoHandles={true} />;
Expand Down
Loading

0 comments on commit 12adfbc

Please sign in to comment.