Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions packages/@react-aria/i18n/src/useNumberFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a little too low to be doing the change. We'd end up with problems in NumberFields if we exposed this there

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]
);
}
2 changes: 1 addition & 1 deletion packages/@react-stately/slider/src/useSliderState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ const DEFAULT_MAX_VALUE = 100;
const DEFAULT_STEP_VALUE = 1;

export interface SliderStateOptions<T> extends SliderProps<T> {
numberFormatter: Intl.NumberFormat
numberFormatter: {format(value: number): string}
}

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/react-aria-components/src/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export interface SliderProps<T = number | number[]> extends Omit<AriaSliderProps
/**
* The display format of the value label.
*/
format?: Intl.NumberFormatOptions | ((value: number, locale: string) => string),
/**
* The display format of the value label.
* @deprecated Use `format` instead.
*/
formatOptions?: Intl.NumberFormatOptions
}

Expand Down Expand Up @@ -50,7 +55,7 @@ export interface SliderRenderProps {
function Slider<T extends number | number[]>(props: SliderProps<T>, ref: ForwardedRef<HTMLDivElement>) {
[props, ref] = useContextProps(props, ref, SliderContext);
let trackRef = useRef<HTMLDivElement>(null);
let numberFormatter = useNumberFormatter(props.formatOptions);
let numberFormatter = useNumberFormatter(props.format ?? props.formatOptions);
let state = useSliderState({...props, numberFormatter});
let [labelRef, label] = useSlot();
let {
Expand Down
49 changes: 49 additions & 0 deletions packages/react-aria-components/stories/Slider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,55 @@ SliderCSS.argTypes = {
}
};

interface SliderCustomFormatProps<T = number | number[]> extends SliderProps<T> {
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 (
<Slider {...props} defaultValue={32} className={styles.slider} format={format}>
<div className={styles.label}>
<Label>Temperature</Label>
<SliderOutput />
</div>
<SliderTrack className={styles.track}>
<SliderThumb className={styles.thumb} />
</SliderTrack>
</Slider>
);
};

SliderCustomFormat.args = {
fahrenheit: false,
showAlternative: false,
isDisabled: false,
minValue: 0,
maxValue: 100,
step: 1
};

const CustomThumb = ({index, children}: {index: number, children: React.ReactNode}) => {
return (
<SliderThumb
Expand Down