Skip to content

Commit 2731e0f

Browse files
committed
feat: Add format prop to RAC Slider component to enable aria-valuetext customization.
1 parent d80999e commit 2731e0f

File tree

7 files changed

+97
-5
lines changed

7 files changed

+97
-5
lines changed

packages/@adobe/react-spectrum/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export {Well} from '@react-spectrum/well';
6565
export {Item, Section} from '@react-stately/collections';
6666
export {useAsyncList, useListData, useTreeData} from '@react-stately/data';
6767
export {VisuallyHidden} from '@react-aria/visually-hidden';
68-
export {useCollator, useDateFormatter, useFilter, useLocale, useLocalizedStringFormatter, useMessageFormatter, useNumberFormatter} from '@react-aria/i18n';
68+
export {useCollator, useCustomNumberFormatter, useDateFormatter, useFilter, useLocale, useLocalizedStringFormatter, useMessageFormatter, useNumberFormatter} from '@react-aria/i18n';
6969
export {SSRProvider} from '@react-aria/ssr';
7070
export {useDragAndDrop, DIRECTORY_DRAG_TYPE} from '@react-spectrum/dnd';
7171

packages/@react-aria/i18n/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export {useLocalizedStringFormatter, useLocalizedStringDictionary} from './useLo
1616
export {useListFormatter} from './useListFormatter';
1717
export {useDateFormatter} from './useDateFormatter';
1818
export {useNumberFormatter} from './useNumberFormatter';
19+
export {useCustomNumberFormatter} from './useCustomNumberFormatter';
1920
export {useCollator} from './useCollator';
2021
export {useFilter} from './useFilter';
2122

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {NumberFormatOptions, NumberFormatter} from '@internationalized/number';
14+
import {useLocale} from './context';
15+
import {useMemo} from 'react';
16+
17+
export type CustomNumberFormat = NumberFormatOptions | ((value: number, locale: string) => string);
18+
19+
export type CustomNumberFormatter = {
20+
format(value: number): string
21+
}
22+
23+
/**
24+
* Provides localized number formatting for the current locale. Automatically updates when the locale changes,
25+
* and handles caching of the number formatter for performance.
26+
* @param format - Formatting options or function.
27+
*/
28+
export function useCustomNumberFormatter(format: CustomNumberFormat = {}): CustomNumberFormatter {
29+
let {locale} = useLocale();
30+
return useMemo(
31+
() =>
32+
typeof format === 'function'
33+
? {format: (value) => format(value, locale)}
34+
: new NumberFormatter(locale, format),
35+
[locale, format]
36+
);
37+
}

packages/@react-stately/slider/src/useSliderState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ const DEFAULT_MAX_VALUE = 100;
150150
const DEFAULT_STEP_VALUE = 1;
151151

152152
export interface SliderStateOptions<T> extends SliderProps<T> {
153-
numberFormatter: Intl.NumberFormat
153+
numberFormatter: { format(value: number): string }
154154
}
155155

156156
/**

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {AriaSliderProps, AriaSliderThumbProps, HoverEvents, mergeProps, Orientation, useFocusRing, useHover, useNumberFormatter, useSlider, useSliderThumb, VisuallyHidden} from 'react-aria';
13+
import {AriaSliderProps, AriaSliderThumbProps, HoverEvents, mergeProps, Orientation, useCustomNumberFormatter, useFocusRing, useHover, useSlider, useSliderThumb, VisuallyHidden} from 'react-aria';
1414
import {ContextValue, forwardRefType, Provider, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils';
1515
import {filterDOMProps} from '@react-aria/utils';
1616
import {LabelContext} from './Label';
@@ -21,6 +21,11 @@ export interface SliderProps<T = number | number[]> extends Omit<AriaSliderProps
2121
/**
2222
* The display format of the value label.
2323
*/
24+
format?: Intl.NumberFormatOptions | ((value: number, locale: string) => string),
25+
/**
26+
* The display format of the value label.
27+
* @deprecated Use `format` instead.
28+
*/
2429
formatOptions?: Intl.NumberFormatOptions
2530
}
2631

@@ -49,7 +54,7 @@ export interface SliderRenderProps {
4954
function Slider<T extends number | number[]>(props: SliderProps<T>, ref: ForwardedRef<HTMLDivElement>) {
5055
[props, ref] = useContextProps(props, ref, SliderContext);
5156
let trackRef = useRef<HTMLDivElement>(null);
52-
let numberFormatter = useNumberFormatter(props.formatOptions);
57+
let numberFormatter = useCustomNumberFormatter(props.format ?? props.formatOptions);
5358
let state = useSliderState({...props, numberFormatter});
5459
let [labelRef, label] = useSlot();
5560
let {

packages/react-aria-components/stories/Slider.stories.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,55 @@ SliderCSS.argTypes = {
9595
}
9696
};
9797

98+
interface SliderCustomFormatProps<T = number | number[]> extends SliderProps<T> {
99+
fahrenheit: boolean,
100+
showAlternative: boolean
101+
}
102+
103+
function temperatureUnitFormat(fahrenheit: boolean): Intl.NumberFormatOptions {
104+
return {
105+
style: 'unit',
106+
unit: fahrenheit ? 'fahrenheit' : 'celsius',
107+
maximumFractionDigits: 1
108+
};
109+
}
110+
111+
export const SliderCustomFormat = ({fahrenheit, showAlternative, ...props}: SliderCustomFormatProps) => {
112+
const mainFormat = temperatureUnitFormat(fahrenheit);
113+
let format: Intl.NumberFormatOptions | ((value: number, locale: string) => string) = mainFormat;
114+
115+
if (showAlternative) {
116+
format = (value, locale) => {
117+
const formatter = new Intl.NumberFormat(locale, mainFormat);
118+
const altValue = fahrenheit ? value * 9 / 5 + 32 : (value - 32) * 5 / 9;
119+
const altFormat = temperatureUnitFormat(!fahrenheit);
120+
const altFormatter = new Intl.NumberFormat(locale, altFormat);
121+
return `${formatter.format(value)} (${altFormatter.format(altValue)})`;
122+
};
123+
}
124+
125+
return (
126+
<Slider {...props} defaultValue={32} className={styles.slider} format={format}>
127+
<div className={styles.label}>
128+
<Label>Temperature</Label>
129+
<SliderOutput />
130+
</div>
131+
<SliderTrack className={styles.track}>
132+
<SliderThumb className={styles.thumb} />
133+
</SliderTrack>
134+
</Slider>
135+
);
136+
};
137+
138+
SliderCustomFormat.args = {
139+
fahrenheit: false,
140+
showAlternative: false,
141+
isDisabled: false,
142+
minValue: 0,
143+
maxValue: 100,
144+
step: 1
145+
};
146+
98147
const CustomThumb = ({index, children}: {index: number, children: React.ReactNode}) => {
99148
return (
100149
<SliderThumb

packages/react-aria/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export {useDateField, useDatePicker, useDateRangePicker, useDateSegment, useTime
1919
export {useDialog} from '@react-aria/dialog';
2020
export {useDrag, useDrop, useDraggableCollection, useDroppableCollection, useDroppableItem, useDropIndicator, useDraggableItem, useClipboard, DragPreview, ListDropTargetDelegate, DIRECTORY_DRAG_TYPE, isDirectoryDropItem, isFileDropItem, isTextDropItem} from '@react-aria/dnd';
2121
export {FocusRing, FocusScope, useFocusManager, useFocusRing, useFocusable} from '@react-aria/focus';
22-
export {I18nProvider, useCollator, useDateFormatter, useFilter, useLocale, useLocalizedStringFormatter, useMessageFormatter, useNumberFormatter} from '@react-aria/i18n';
22+
export {I18nProvider, useCollator, useCustomNumberFormatter, useDateFormatter, useFilter, useLocale, useLocalizedStringFormatter, useMessageFormatter, useNumberFormatter} from '@react-aria/i18n';
2323
export {useFocus, useFocusVisible, useFocusWithin, useHover, useInteractOutside, useKeyboard, useMove, usePress, useLongPress} from '@react-aria/interactions';
2424
export {useField, useLabel} from '@react-aria/label';
2525
export {useGridList, useGridListItem, useGridListSelectionCheckbox} from '@react-aria/gridlist';

0 commit comments

Comments
 (0)