Skip to content

Commit ebde8c7

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

File tree

4 files changed

+71
-5
lines changed

4 files changed

+71
-5
lines changed

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,24 @@ import {NumberFormatOptions, NumberFormatter} from '@internationalized/number';
1414
import {useLocale} from './context';
1515
import {useMemo} from 'react';
1616

17+
export type NumberFormatFunction = (value: number, locale: string) => string;
18+
19+
export type CustomNumberFormat = {format(value: number): string};
20+
1721
/**
1822
* Provides localized number formatting for the current locale. Automatically updates when the locale changes,
1923
* and handles caching of the number formatter for performance.
20-
* @param options - Formatting options.
24+
* @param format - Formatting options or function.
2125
*/
22-
export function useNumberFormatter(options: NumberFormatOptions = {}): Intl.NumberFormat {
26+
export function useNumberFormatter(format?: NumberFormatOptions): Intl.NumberFormat
27+
export function useNumberFormatter(format?: NumberFormatOptions | NumberFormatFunction): CustomNumberFormat
28+
export function useNumberFormatter(format: NumberFormatOptions | NumberFormatFunction = {}): CustomNumberFormat {
2329
let {locale} = useLocale();
24-
return useMemo(() => new NumberFormatter(locale, options), [locale, options]);
30+
return useMemo(
31+
() =>
32+
typeof format === 'function'
33+
? {format: (value) => format(value, locale)}
34+
: new NumberFormatter(locale, format),
35+
[locale, format]
36+
);
2537
}

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: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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 = useNumberFormatter(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

0 commit comments

Comments
 (0)