Skip to content

Commit 8415552

Browse files
devongovettdannify
andauthored
Fix wrapping and support uncontrolled value label in slider (#1399)
Co-authored-by: Danni <drobinson@livefyre.com>
1 parent e91d2ed commit 8415552

File tree

9 files changed

+48
-23
lines changed

9 files changed

+48
-23
lines changed

packages/@adobe/spectrum-css-temp/components/slider/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ governing permissions and limitations under the License.
362362

363363
.spectrum-Slider-value {
364364
margin-inline-start: var(--spectrum-slider-label-gap-x);
365+
white-space: nowrap;
365366
}
366367

367368
.spectrum-Slider-ticks {

packages/@react-spectrum/slider/src/RangeSlider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import styles from '@adobe/spectrum-css-temp/components/slider/vars.css';
2323
import {useLocale, useMessageFormatter} from '@react-aria/i18n';
2424

2525
function RangeSlider(props: SpectrumRangeSliderProps, ref: FocusableRef<HTMLDivElement>) {
26-
let {onChange, value, defaultValue, ...otherProps} = props;
26+
let {onChange, value, defaultValue, getValueLabel, ...otherProps} = props;
2727

2828
let baseProps: Omit<SliderBaseProps, 'children'> = {
2929
...otherProps,
@@ -34,7 +34,8 @@ function RangeSlider(props: SpectrumRangeSliderProps, ref: FocusableRef<HTMLDivE
3434
: [props.minValue ?? DEFAULT_MIN_VALUE, props.maxValue ?? DEFAULT_MAX_VALUE],
3535
onChange(v) {
3636
onChange?.({start: v[0], end: v[1]});
37-
}
37+
},
38+
getValueLabel: getValueLabel ? ([start, end]) => getValueLabel({start, end}) : undefined
3839
};
3940

4041
let formatter = useMessageFormatter(intlMessages);

packages/@react-spectrum/slider/src/Slider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import styles from '@adobe/spectrum-css-temp/components/slider/vars.css';
2121
import {useLocale} from '@react-aria/i18n';
2222

2323
function Slider(props: SpectrumSliderProps, ref: FocusableRef<HTMLDivElement>) {
24-
let {onChange, value, defaultValue, isFilled, fillOffset, trackGradient, ...otherProps} = props;
24+
let {onChange, value, defaultValue, isFilled, fillOffset, trackGradient, getValueLabel, ...otherProps} = props;
2525

2626
let baseProps: Omit<SliderBaseProps, 'children'> = {
2727
...otherProps,
@@ -30,7 +30,8 @@ function Slider(props: SpectrumSliderProps, ref: FocusableRef<HTMLDivElement>) {
3030
defaultValue: defaultValue != null ? [defaultValue] : undefined,
3131
onChange(v) {
3232
onChange?.(v[0]);
33-
}
33+
},
34+
getValueLabel: getValueLabel ? ([v]) => getValueLabel(v) : undefined
3435
};
3536

3637
let {direction} = useLocale();

packages/@react-spectrum/slider/src/SliderBase.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function SliderBase(props: SliderBaseProps, ref: FocusableRef<HTMLDivElement>) {
4040
classes,
4141
style,
4242
labelPosition = 'top',
43-
valueLabel,
43+
getValueLabel,
4444
showValueLabel = !!props.label,
4545
formatOptions,
4646
...otherProps
@@ -80,10 +80,31 @@ function SliderBase(props: SliderBaseProps, ref: FocusableRef<HTMLDivElement>) {
8080
let inputRef = useRef();
8181
let domRef = useFocusableRef(ref, inputRef);
8282

83-
let displayValue = valueLabel;
83+
let displayValue = '';
8484
let maxLabelLength = undefined;
8585

86-
if (!displayValue) {
86+
if (typeof getValueLabel === 'function') {
87+
displayValue = getValueLabel(state.values);
88+
switch (state.values.length) {
89+
case 1:
90+
maxLabelLength = Math.max(
91+
getValueLabel([state.getThumbMinValue(0)]).length,
92+
getValueLabel([state.getThumbMaxValue(0)]).length
93+
);
94+
break;
95+
case 2:
96+
// Try all possible combinations of min and max values.
97+
maxLabelLength = Math.max(
98+
getValueLabel([state.getThumbMinValue(0), state.getThumbMinValue(1)]).length,
99+
getValueLabel([state.getThumbMinValue(0), state.getThumbMaxValue(1)]).length,
100+
getValueLabel([state.getThumbMaxValue(0), state.getThumbMinValue(1)]).length,
101+
getValueLabel([state.getThumbMaxValue(0), state.getThumbMaxValue(1)]).length
102+
);
103+
break;
104+
default:
105+
throw new Error('Only sliders with 1 or 2 handles are supported!');
106+
}
107+
} else {
87108
maxLabelLength = Math.max([...formatter.format(state.getThumbMinValue(0))].length, [...formatter.format(state.getThumbMaxValue(0))].length);
88109
switch (state.values.length) {
89110
case 1:

packages/@react-spectrum/slider/stories/RangeSlider.stories.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {action} from '@storybook/addon-actions';
1414
import {ErrorBoundary} from '@react-spectrum/story-utils';
1515
import {RangeSlider} from '../';
16-
import React, {useState} from 'react';
16+
import React from 'react';
1717
import {SpectrumRangeSliderProps} from '@react-types/slider';
1818
import {storiesOf} from '@storybook/react';
1919

@@ -58,10 +58,11 @@ storiesOf('Slider/RangeSlider', module)
5858
)
5959
.add(
6060
'custom valueLabel',
61-
() => {
62-
let [state, setState] = useState({start: 20, end: 50});
63-
return render({label: 'Label', value: state, onChange: setState, valueLabel: `${state.start} <-> ${state.end}`});
64-
}
61+
() => render({label: 'Label', getValueLabel: (value) => `${value.start} <-> ${value.end}`})
62+
)
63+
.add(
64+
'custom valueLabel with label overflow',
65+
() => render({label: 'This is a rather long label for this narrow slider element.', getValueLabel: (value) => `${value.start} <-> ${value.end}`})
6566
)
6667
.add(
6768
'labelPosition: side',

packages/@react-spectrum/slider/stories/Slider.stories.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {action} from '@storybook/addon-actions';
1414
import {ErrorBoundary} from '@react-spectrum/story-utils';
1515
import {Flex} from '@adobe/react-spectrum';
16-
import React, {useState} from 'react';
16+
import React from 'react';
1717
import {Slider} from '../';
1818
import {SpectrumSliderProps} from '@react-types/slider';
1919
import {storiesOf} from '@storybook/react';
@@ -70,10 +70,11 @@ storiesOf('Slider', module)
7070
)
7171
.add(
7272
'custom valueLabel',
73-
() => {
74-
let [state, setState] = useState(0);
75-
return render({label: 'Label', value: state, onChange: setState, valueLabel: `A ${state} B`});
76-
}
73+
() => render({label: 'Label', getValueLabel: state => `A ${state} B`})
74+
)
75+
.add(
76+
'custom valueLabel with label overflow',
77+
() => render({label: 'This is a rather long label for this narrow slider element.', getValueLabel: state => `A ${state} B`})
7778
)
7879
.add(
7980
'labelPosition: side',

packages/@react-spectrum/slider/test/RangeSlider.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('RangeSlider', function () {
162162
it('supports a custom valueLabel', function () {
163163
function Test() {
164164
let [value, setValue] = useState({start: 10, end: 40});
165-
return (<RangeSlider label="The Label" value={value} onChange={setValue} valueLabel={`A${value.start}B${value.end}C`} />);
165+
return (<RangeSlider label="The Label" value={value} onChange={setValue} getValueLabel={value => `A${value.start}B${value.end}C`} />);
166166
}
167167

168168
let {getAllByRole, getByRole} = render(<Test />);

packages/@react-spectrum/slider/test/Slider.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ describe('Slider', function () {
145145
expect(renders).toStrictEqual([50, 55]);
146146
});
147147

148-
it('supports a custom valueLabel', function () {
148+
it('supports a custom getValueLabel', function () {
149149
function Test() {
150150
let [value, setValue] = useState(50);
151-
return (<Slider label="The Label" value={value} onChange={setValue} valueLabel={`A${value}B`} />);
151+
return (<Slider label="The Label" value={value} onChange={setValue} getValueLabel={value => `A${value}B`} />);
152152
}
153153

154154
let {getByRole} = render(<Test />);

packages/@react-types/slider/src/index.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {AriaLabelingProps, AriaValidationProps, FocusableDOMProps, FocusableProps, LabelableProps, LabelPosition, Orientation, RangeInputBase, RangeValue, StyleProps, Validation, ValueBase} from '@react-types/shared';
2-
import {ReactNode} from 'react';
32

43
export interface BaseSliderProps extends RangeInputBase<number>, LabelableProps, AriaLabelingProps {
54
orientation?: Orientation,
@@ -24,8 +23,8 @@ export interface SpectrumBarSliderBase<T> extends BaseSliderProps, ValueBase<T>,
2423
labelPosition?: LabelPosition,
2524
/** Whether the value's label is displayed. True by default if there's a `label`, false by default if not. */
2625
showValueLabel?: boolean,
27-
/** The content to display as the value's label. Overrides default formatted number. */
28-
valueLabel?: ReactNode
26+
/** A function that returns the content to display as the value's label. Overrides default formatted number. */
27+
getValueLabel?: (value: T) => string
2928
}
3029

3130
export interface SpectrumSliderProps extends SpectrumBarSliderBase<number> {

0 commit comments

Comments
 (0)