Skip to content

Commit d09e2ac

Browse files
authored
feat: add new numerals prop (#2647)
* feat: add new `numerals` prop * Update docs * Update playground
1 parent 8a67562 commit d09e2ac

11 files changed

+192
-100
lines changed

examples/NumberingSystem.test.tsx

-21
This file was deleted.

examples/NumberingSystem.tsx

-33
This file was deleted.

examples/Numerals.test.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from "react";
2+
3+
import { render } from "@testing-library/react";
4+
5+
import { grid } from "@/test/elements";
6+
7+
import { Numerals } from "./Numerals";
8+
9+
const today = new Date(2025, 10, 25);
10+
11+
beforeAll(() => jest.setSystemTime(today));
12+
afterAll(() => jest.useRealTimers());
13+
14+
test("should use Devanagari numerals", () => {
15+
render(<Numerals />);
16+
17+
expect(grid()).toHaveTextContent("२५");
18+
});

examples/Numerals.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from "react";
2+
3+
import { hi } from "date-fns/locale/hi";
4+
import { DayPicker } from "react-day-picker";
5+
6+
export function Numerals() {
7+
return <DayPicker numerals="deva" locale={hi} />;
8+
}

examples/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export * from "./MultipleMinMax";
4242
export * from "./MultipleRequired";
4343
export * from "./MultipleMonths";
4444
export * from "./MultipleMonthsPaged";
45-
export * from "./NumberingSystem";
45+
export * from "./Numerals";
4646
export * from "./OutsideDays";
4747
export * from "./PastDatesDisabled";
4848
export * from "./Persian";

src/DayPicker.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export function DayPicker(props: DayPickerProps) {
4848
firstWeekContainsDate: props.firstWeekContainsDate,
4949
useAdditionalWeekYearTokens: props.useAdditionalWeekYearTokens,
5050
useAdditionalDayOfYearTokens: props.useAdditionalDayOfYearTokens,
51-
timeZone: props.timeZone
51+
timeZone: props.timeZone,
52+
numerals: props.numerals
5253
},
5354
props.dateLib
5455
);
@@ -69,6 +70,7 @@ export function DayPicker(props: DayPickerProps) {
6970
props.useAdditionalWeekYearTokens,
7071
props.useAdditionalDayOfYearTokens,
7172
props.timeZone,
73+
props.numerals,
7274
props.dateLib,
7375
props.components,
7476
props.formatters,

src/classes/DateLib.ts

+60-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { enUS } from "date-fns/locale";
4444
import { endOfBroadcastWeek } from "../helpers/endOfBroadcastWeek.js";
4545
import { startOfBroadcastWeek } from "../helpers/startOfBroadcastWeek.js";
4646
import type { PropsBase } from "../types/props.js";
47+
import { Numerals } from "../types/shared.js";
4748

4849
export type { Locale } from "date-fns/locale";
4950
export type { Month as DateFnsMonth } from "date-fns";
@@ -76,8 +77,18 @@ export interface DateLibOptions
7677
Date?: typeof Date;
7778
/** A locale to use for formatting dates. */
7879
locale?: Locale;
79-
/** A time zone to use for dates. */
80+
/**
81+
* A time zone to use for dates.
82+
*
83+
* @since 9.5.0
84+
*/
8085
timeZone?: string;
86+
/**
87+
* The numbering system to use for formatting numbers.
88+
*
89+
* @since 9.5.0
90+
*/
91+
numerals?: Numerals;
8192
}
8293

8394
/**
@@ -111,6 +122,49 @@ export class DateLib {
111122
this.overrides = overrides;
112123
}
113124

125+
/**
126+
* Generate digit map dynamically using Intl.NumberFormat.
127+
*
128+
* @since 9.5.0
129+
*/
130+
private getDigitMap(): Record<string, string> {
131+
const { numerals = "latn" } = this.options;
132+
133+
// Use Intl.NumberFormat to create a formatter with the specified numbering system
134+
const formatter = new Intl.NumberFormat("en-US", {
135+
numberingSystem: numerals
136+
});
137+
138+
// Map Arabic digits (0-9) to the target numerals
139+
const digitMap: Record<string, string> = {};
140+
for (let i = 0; i < 10; i++) {
141+
digitMap[i.toString()] = formatter.format(i);
142+
}
143+
144+
return digitMap;
145+
}
146+
147+
/**
148+
* Replace Arabic digits with the target numbering system digits.
149+
*
150+
* @since 9.5.0
151+
*/
152+
private replaceDigits(input: string): string {
153+
const digitMap = this.getDigitMap();
154+
return input.replace(/\d/g, (digit) => digitMap[digit] || digit);
155+
}
156+
157+
/**
158+
* Format number using the custom numbering system.
159+
*
160+
* @since 9.5.0
161+
* @param value The number to format.
162+
* @returns The formatted number.
163+
*/
164+
formatNumber(value: number): string {
165+
return this.replaceDigits(value.toString());
166+
}
167+
114168
/**
115169
* Reference to the built-in Date constructor.
116170
*
@@ -324,9 +378,13 @@ export class DateLib {
324378
* @returns The formatted date string.
325379
*/
326380
format: typeof format = (date, formatStr) => {
327-
return this.overrides?.format
381+
const formatted = this.overrides?.format
328382
? this.overrides.format(date, formatStr, this.options)
329383
: format(date, formatStr, this.options);
384+
if (this.options.numerals && this.options.numerals !== "latn") {
385+
return this.replaceDigits(formatted);
386+
}
387+
return formatted;
330388
};
331389

332390
/**

src/types/props.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import type {
1616
DayEventHandler,
1717
Modifiers,
1818
DateRange,
19-
Mode
19+
Mode,
20+
Numerals
2021
} from "./shared.js";
2122

2223
/**
@@ -390,6 +391,12 @@ export interface PropsBase {
390391
* @see https://github.com/date-fns/date-fns/tree/main/src/locale for a list of the supported locales
391392
*/
392393
locale?: Partial<Locale> | undefined;
394+
/**
395+
* The numeral system to use when formatting dates.
396+
*
397+
* @see https://daypicker.dev/docs/translation#numeral-systems
398+
*/
399+
numerals?: Numerals;
393400
/**
394401
* The index of the first day of the week (0 - Sunday). Overrides the locale's
395402
* one.

src/types/shared.ts

+32
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,35 @@ export type MoveFocusBy =
408408
| "endOfWeek"
409409
| "month"
410410
| "year";
411+
412+
/**
413+
* The numbering system supported by DayPicker.
414+
*
415+
* - `latn`: Latin (Western Arabic)
416+
* - `arab`: Arabic-Indic
417+
* - `arabext`: Eastern Arabic-Indic (Persian)
418+
* - `deva`: Devanagari
419+
* - `beng`: Bengali
420+
* - `guru`: Gurmukhi
421+
* - `gujr`: Gujarati
422+
* - `orya`: Oriya
423+
* - `tamldec`: Tamil
424+
* - `telu`: Telugu
425+
* - `knda`: Kannada
426+
* - `mlym`: Malayalam
427+
*
428+
* @see https://daypicker.dev/docs/translation#numeral-systems
429+
*/
430+
export type Numerals =
431+
| "latn"
432+
| "arab"
433+
| "arabext"
434+
| "deva"
435+
| "beng"
436+
| "guru"
437+
| "gujr"
438+
| "orya"
439+
| "tamldec"
440+
| "telu"
441+
| "knda"
442+
| "mlym";

website/docs/docs/translation.mdx

+18-40
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,24 @@ import { arSA } from "react-day-picker/locale";
9797
<Examples.Rtl />
9898
</BrowserWindow>
9999

100+
## Numeral Systems
101+
102+
| Prop Name | Type | Description |
103+
| ---------- | --------------------------------------------- | ----------------------- |
104+
| `numerals` | [`Numerals`](../api/type-aliases/Numerals.md) | Set the numeral system. |
105+
106+
Use the `numerals` prop to set the numeral system. The default is `latn`.
107+
108+
```tsx
109+
import { hi } from "date-fns/locale/hi";
110+
111+
<DayPicker numerals="deva" locale={hi} />;
112+
```
113+
114+
<BrowserWindow>
115+
<Examples.Numerals />
116+
</BrowserWindow>
117+
100118
## Custom Formatters
101119

102120
Use the `formatters` prop to customize the formatting of dates, week numbers, day names, and more.
@@ -120,43 +138,3 @@ import { format } from "date-fns";
120138
| [`formatWeekNumberHeader`](../api/functions/formatWeekNumberHeader.md) | Format the week number header. |
121139
| [`formatWeekdayName`](../api/functions/formatWeekdayName.md) | Format the weekday name to be displayed in the weekdays header. |
122140
| [`formatYearDropdown`](../api/functions/formatYearDropdown.md) | Format the years for the dropdown option label. |
123-
124-
### Numbering System
125-
126-
Use the `formatters` prop to change the [numbering system](https://en.wikipedia.org/wiki/Numeral_system) used in the calendar.
127-
128-
For example, to switch to Hindu-Arabic numerals, use the native [`Date.toLocaleString`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString) method:
129-
130-
```tsx
131-
import { format } from "date-fns/format";
132-
import { DayPicker, Formatters } from "react-day-picker";
133-
import { arSA } from "react-day-picker/locale";
134-
135-
const NU_LOCALE = "ar-u-nu-arab";
136-
137-
const formatDay = (day) => day.getDate().toLocaleString(NU_LOCALE);
138-
const formatWeekNumber = (weekNumber) => weekNumber.toLocaleString(NU_LOCALE);
139-
const formatMonthCaption = (date, options) => {
140-
const y = date.getFullYear().toLocaleString(NU_LOCALE);
141-
const m = format(date, "LLLL", options);
142-
return `${m} ${y}`;
143-
};
144-
145-
export function NumberingSystemExample() {
146-
return (
147-
<DayPicker
148-
locale={arSA}
149-
dir="rtl"
150-
formatters={{
151-
formatDay,
152-
formatMonthCaption,
153-
formatWeekNumber
154-
}}
155-
/>
156-
);
157-
}
158-
```
159-
160-
<BrowserWindow>
161-
<Examples.NumberingSystem />
162-
</BrowserWindow>

website/src/pages/playground.tsx

+44-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
type DayPickerProps,
88
DateLib,
99
DayPicker,
10-
isDateRange
10+
isDateRange,
11+
Numerals
1112
} from "react-day-picker";
1213
import * as locales from "react-day-picker/locale";
1314
import {
@@ -32,6 +33,21 @@ const timeZones = [
3233

3334
const calendars = ["Gregorian", "Persian"];
3435
const persianLocales = { faIR: faIRPersian, enUS: enUSPersian };
36+
37+
const numerals: { value: Numerals; label: string }[] = [
38+
{ value: "latn", label: "Latin (Western Arabic)" },
39+
{ value: "arab", label: "Arabic-Indic" },
40+
{ value: "arabext", label: "Eastern Arabic-Indic (Persian)" },
41+
{ value: "deva", label: "Devanagari" },
42+
{ value: "beng", label: "Bengali" },
43+
{ value: "guru", label: "Gurmukhi" },
44+
{ value: "gujr", label: "Gujarati" },
45+
{ value: "orya", label: "Oriya" },
46+
{ value: "tamldec", label: "Tamil" },
47+
{ value: "telu", label: "Telugu" },
48+
{ value: "knda", label: "Kannada" },
49+
{ value: "mlym", label: "Malayalam" }
50+
];
3551
/**
3652
* Function to format a json object of props to a jsx source displaying the
3753
* props as example
@@ -421,6 +437,33 @@ export default function Playground() {
421437
))}
422438
</select>
423439
</label>
440+
<label>
441+
Numerals:
442+
<select
443+
style={{ maxWidth: 100 }}
444+
name="numerals"
445+
value={
446+
numerals.find((numeral) => numeral.value === props.numerals)
447+
?.value
448+
}
449+
onChange={(e) =>
450+
setProps({
451+
...props,
452+
numerals:
453+
e.target.value === ""
454+
? undefined
455+
: (e.target.value as Numerals)
456+
})
457+
}
458+
>
459+
<option value=""></option>
460+
{numerals.map((numeral) => (
461+
<option key={numeral.value} value={numeral.value}>
462+
{numeral.label}
463+
</option>
464+
))}
465+
</select>
466+
</label>
424467
<label>
425468
Locale:
426469
{calendar === "Persian" ? (

0 commit comments

Comments
 (0)