From 31a69e241c340792a9502054ec6a050d54570367 Mon Sep 17 00:00:00 2001 From: Lee Chase Date: Tue, 15 Nov 2022 17:09:07 +0000 Subject: [PATCH] feat: add readonly functionality to slider (#12410) * feat: add readonly functionality to slider * fix: cursor following review Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../react/src/components/Slider/Slider.js | 21 +++-- .../Slider/__tests__/Slider-test.js | 89 +++++++++++++++++++ .../scss/components/slider/_slider.scss | 14 +++ 3 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 packages/react/src/components/Slider/__tests__/Slider-test.js diff --git a/packages/react/src/components/Slider/Slider.js b/packages/react/src/components/Slider/Slider.js index 71192af28e7f..1eb177b78840 100644 --- a/packages/react/src/components/Slider/Slider.js +++ b/packages/react/src/components/Slider/Slider.js @@ -143,6 +143,11 @@ export default class Slider extends PureComponent { */ onRelease: PropTypes.func, + /** + * Whether the slider should be read-only + */ + readOnly: PropTypes.bool, + /** * `true` to specify if the control is required. */ @@ -176,6 +181,7 @@ export default class Slider extends PureComponent { ariaLabelInput: FeatureFlags.enabled('enable-v11-release') ? undefined : 'Slider number input', + readOnly: false, }; static contextType = FeatureFlagContext; @@ -267,7 +273,7 @@ export default class Slider extends PureComponent { */ onDragStart = (evt) => { // Do nothing if component is disabled - if (this.props.disabled) { + if (this.props.disabled || this.props.readOnly) { return; } @@ -292,7 +298,7 @@ export default class Slider extends PureComponent { */ onDragStop = () => { // Do nothing if component is disabled - if (this.props.disabled) { + if (this.props.disabled || this.props.readOnly) { return; } @@ -318,7 +324,7 @@ export default class Slider extends PureComponent { */ _onDrag = (evt) => { // Do nothing if component is disabled or we have no event - if (this.props.disabled || !evt) { + if (this.props.disabled || this.props.readOnly || !evt) { return; } @@ -357,7 +363,7 @@ export default class Slider extends PureComponent { */ onKeyDown = (evt) => { // Do nothing if component is disabled or we don't have a valid event - if (this.props.disabled || !('which' in evt)) { + if (this.props.disabled || this.props.readOnly || !('which' in evt)) { return; } @@ -401,7 +407,7 @@ export default class Slider extends PureComponent { onChange = (evt) => { // Do nothing if component is disabled - if (this.props.disabled) { + if (this.props.disabled || this.props.readOnly) { return; } @@ -551,6 +557,7 @@ export default class Slider extends PureComponent { disabled, name, light, + readOnly, ...other } = this.props; @@ -577,6 +584,7 @@ export default class Slider extends PureComponent { const sliderClasses = classNames( `${prefix}--slider`, { [`${prefix}--slider--disabled`]: disabled }, + { [`${prefix}--slider--readonly`]: readOnly }, [enabled ? null : className] ); @@ -629,7 +637,7 @@ export default class Slider extends PureComponent { className={`${prefix}--slider__thumb`} role="slider" id={id} - tabIndex={0} + tabIndex={!readOnly ? 0 : -1} aria-valuemax={max} aria-valuemin={min} aria-valuenow={value} @@ -669,6 +677,7 @@ export default class Slider extends PureComponent { onKeyUp={this.onInputKeyUp} data-invalid={isValid ? null : true} aria-invalid={isValid ? null : true} + readOnly={readOnly} /> diff --git a/packages/react/src/components/Slider/__tests__/Slider-test.js b/packages/react/src/components/Slider/__tests__/Slider-test.js new file mode 100644 index 000000000000..aad87d3e2256 --- /dev/null +++ b/packages/react/src/components/Slider/__tests__/Slider-test.js @@ -0,0 +1,89 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Slider from '../Slider'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; + +describe('Slider', () => { + describe('behaves as expected - Component API', () => { + it('should respect work normally when not readonly prop', () => { + const onChange = jest.fn(); + const onClick = jest.fn(); + + render( + + ); + + // Click events should fire + const theSlider = screen.getByRole('slider'); + userEvent.click(theSlider); + expect(onClick).toHaveBeenCalledTimes(1); + + userEvent.type(theSlider, '{arrowright}'); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith( + expect.objectContaining({ + value: 2, + }) + ); + + const theInput = screen.getByRole('spinbutton'); + userEvent.type(theInput, '{selectall}3'); + expect(onChange).toHaveBeenCalledTimes(2); + expect(onChange).toHaveBeenCalledWith( + expect.objectContaining({ + value: 3, + }) + ); + }); + + it('should respect readOnly prop', () => { + const onChange = jest.fn(); + const onClick = jest.fn(); + + render( + + ); + + // Click events should fire + const theSlider = screen.getByRole('slider'); + userEvent.click(theSlider); + expect(onClick).toHaveBeenCalledTimes(1); + + userEvent.type(theSlider, '{arrowright}'); + + const theInput = screen.getByRole('spinbutton'); + userEvent.type(theInput, '{selectall}3'); + + expect(onChange).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/packages/styles/scss/components/slider/_slider.scss b/packages/styles/scss/components/slider/_slider.scss index 24a0c0595944..b96ac6112347 100644 --- a/packages/styles/scss/components/slider/_slider.scss +++ b/packages/styles/scss/components/slider/_slider.scss @@ -200,6 +200,20 @@ } } + // readonly state + .#{$prefix}--slider--readonly { + cursor: default; + } + + .#{$prefix}--slider--readonly .#{$prefix}--slider__thumb { + width: 0; + height: 0; + } + + .#{$prefix}--slider--readonly ~ .#{$prefix}--slider-text-input { + background-color: transparent; + } + // Skeleton state .#{$prefix}--slider-container.#{$prefix}--skeleton .#{$prefix}--slider__range-label {