diff --git a/packages/@react-aria/i18n/src/useNumberFormatter.ts b/packages/@react-aria/i18n/src/useNumberFormatter.ts index 9a1749ab9f2..43a7064cd61 100644 --- a/packages/@react-aria/i18n/src/useNumberFormatter.ts +++ b/packages/@react-aria/i18n/src/useNumberFormatter.ts @@ -14,12 +14,24 @@ import {NumberFormatOptions, NumberFormatter} from '@internationalized/number'; import {useLocale} from './context'; import {useMemo} from 'react'; +export type NumberFormatFunction = (value: number, locale: string) => string; + +export type CustomNumberFormat = {format(value: number): string}; + /** * Provides localized number formatting for the current locale. Automatically updates when the locale changes, * and handles caching of the number formatter for performance. - * @param options - Formatting options. + * @param format - Formatting options or function. */ -export function useNumberFormatter(options: NumberFormatOptions = {}): Intl.NumberFormat { +export function useNumberFormatter(format?: NumberFormatOptions): Intl.NumberFormat +export function useNumberFormatter(format?: NumberFormatOptions | NumberFormatFunction): CustomNumberFormat +export function useNumberFormatter(format: NumberFormatOptions | NumberFormatFunction = {}): CustomNumberFormat { let {locale} = useLocale(); - return useMemo(() => new NumberFormatter(locale, options), [locale, options]); + return useMemo( + () => + typeof format === 'function' + ? {format: (value) => format(value, locale)} + : new NumberFormatter(locale, format), + [locale, format] + ); } diff --git a/packages/@react-stately/slider/src/useSliderState.ts b/packages/@react-stately/slider/src/useSliderState.ts index dc6b489c2de..6de0af7fc8a 100644 --- a/packages/@react-stately/slider/src/useSliderState.ts +++ b/packages/@react-stately/slider/src/useSliderState.ts @@ -150,7 +150,7 @@ const DEFAULT_MAX_VALUE = 100; const DEFAULT_STEP_VALUE = 1; export interface SliderStateOptions extends SliderProps { - numberFormatter: Intl.NumberFormat + numberFormatter: {format(value: number): string} } /** diff --git a/packages/react-aria-components/src/Slider.tsx b/packages/react-aria-components/src/Slider.tsx index 84099efcc11..0802149aaad 100644 --- a/packages/react-aria-components/src/Slider.tsx +++ b/packages/react-aria-components/src/Slider.tsx @@ -22,6 +22,11 @@ export interface SliderProps extends Omit string), + /** + * The display format of the value label. + * @deprecated Use `format` instead. + */ formatOptions?: Intl.NumberFormatOptions } @@ -50,7 +55,7 @@ export interface SliderRenderProps { function Slider(props: SliderProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, SliderContext); let trackRef = useRef(null); - let numberFormatter = useNumberFormatter(props.formatOptions); + let numberFormatter = useNumberFormatter(props.format ?? props.formatOptions); let state = useSliderState({...props, numberFormatter}); let [labelRef, label] = useSlot(); let { diff --git a/packages/react-aria-components/stories/Slider.stories.tsx b/packages/react-aria-components/stories/Slider.stories.tsx index c7f50d0dc50..412336b3843 100644 --- a/packages/react-aria-components/stories/Slider.stories.tsx +++ b/packages/react-aria-components/stories/Slider.stories.tsx @@ -95,6 +95,55 @@ SliderCSS.argTypes = { } }; +interface SliderCustomFormatProps extends SliderProps { + fahrenheit: boolean, + showAlternative: boolean +} + +function temperatureUnitFormat(fahrenheit: boolean): Intl.NumberFormatOptions { + return { + style: 'unit', + unit: fahrenheit ? 'fahrenheit' : 'celsius', + maximumFractionDigits: 1 + }; +} + +export const SliderCustomFormat = ({fahrenheit, showAlternative, ...props}: SliderCustomFormatProps) => { + const mainFormat = temperatureUnitFormat(fahrenheit); + let format: Intl.NumberFormatOptions | ((value: number, locale: string) => string) = mainFormat; + + if (showAlternative) { + format = (value, locale) => { + const formatter = new Intl.NumberFormat(locale, mainFormat); + const altValue = fahrenheit ? value * 9 / 5 + 32 : (value - 32) * 5 / 9; + const altFormat = temperatureUnitFormat(!fahrenheit); + const altFormatter = new Intl.NumberFormat(locale, altFormat); + return `${formatter.format(value)} (${altFormatter.format(altValue)})`; + }; + } + + return ( + +
+ + +
+ + + +
+ ); +}; + +SliderCustomFormat.args = { + fahrenheit: false, + showAlternative: false, + isDisabled: false, + minValue: 0, + maxValue: 100, + step: 1 +}; + const CustomThumb = ({index, children}: {index: number, children: React.ReactNode}) => { return (