Repository: gpbl/react-day-picker
Pull request #2644 (2024-12-25 19:09:36)

fix(breaking): dropdown formatters to use dateLib format


Patch:
diff --git a/src/DayPicker.tsx b/src/DayPicker.tsx
index 6d8cdde7a..aaface07e 100644
--- a/src/DayPicker.tsx
+++ b/src/DayPicker.tsx
@@ -350,10 +350,7 @@ export function DayPicker(props: DayPickerProps) {
                         />
                       ) : (
                         <span role="status" aria-live="polite">
-                          {formatMonthDropdown(
-                            calendarMonth.date.getMonth(),
-                            locale
-                          )}
+                          {formatMonthDropdown(calendarMonth.date, dateLib)}
                         </span>
                       )}
                       {captionLayout === "dropdown" ||
@@ -371,7 +368,7 @@ export function DayPicker(props: DayPickerProps) {
                         />
                       ) : (
                         <span role="status" aria-live="polite">
-                          {formatYearDropdown(calendarMonth.date.getFullYear())}
+                          {formatYearDropdown(calendarMonth.date, dateLib)}
                         </span>
                       )}
                     </components.DropdownNav>
diff --git a/src/formatters/formatMonthDropdown.ts b/src/formatters/formatMonthDropdown.ts
index 05f72add6..cb3b2df40 100644
--- a/src/formatters/formatMonthDropdown.ts
+++ b/src/formatters/formatMonthDropdown.ts
@@ -1,4 +1,4 @@
-import type { DateFnsMonth, Locale } from "../classes/DateLib.js";
+import { defaultDateLib, type DateLib } from "../classes/DateLib.js";
 
 /**
  * Format the month number for the dropdown option label.
@@ -8,10 +8,8 @@ import type { DateFnsMonth, Locale } from "../classes/DateLib.js";
  * @see https://daypicker.dev/docs/translation#custom-formatters
  */
 export function formatMonthDropdown(
-  /** The month number to format. */
-  monthNumber: number,
-  /** The locale to use for formatting. */
-  locale: Locale
+  month: Date,
+  dateLib: DateLib = defaultDateLib
 ): string {
-  return locale.localize?.month(monthNumber as DateFnsMonth);
+  return dateLib.format(month, "LLLL");
 }
diff --git a/src/formatters/formatYearDropdown.ts b/src/formatters/formatYearDropdown.ts
index 07020c27e..879497cb3 100644
--- a/src/formatters/formatYearDropdown.ts
+++ b/src/formatters/formatYearDropdown.ts
@@ -1,12 +1,16 @@
+import { defaultDateLib, type DateLib } from "../classes/DateLib.js";
+
 /**
  * Format the years for the dropdown option label.
  *
- * @defaultValue `year.toString()`
  * @group Formatters
  * @see https://daypicker.dev/docs/translation#custom-formatters
  */
-export function formatYearDropdown(year: number): string {
-  return year.toString();
+export function formatYearDropdown(
+  year: Date,
+  dateLib: DateLib = defaultDateLib
+): string {
+  return dateLib.format(year, "yyyy");
 }
 
 /**
diff --git a/src/helpers/getMonthOptions.ts b/src/helpers/getMonthOptions.ts
index 372f151f6..80ee7ba9d 100644
--- a/src/helpers/getMonthOptions.ts
+++ b/src/helpers/getMonthOptions.ts
@@ -1,4 +1,4 @@
-import { defaultLocale, type DateLib } from "../classes/DateLib.js";
+import { type DateLib } from "../classes/DateLib.js";
 import { DropdownOption } from "../components/Dropdown.js";
 import type { Formatters } from "../types/index.js";
 
@@ -12,25 +12,23 @@ export function getMonthOptions(
 ): DropdownOption[] | undefined {
   if (!navStart) return undefined;
   if (!navEnd) return undefined;
+  // const year = dateLib.getYear(displayMonth);
+  const {
+    startOfMonth,
+    startOfYear,
+    endOfYear,
+    eachMonthOfInterval,
+    getMonth
+  } = dateLib;
 
-  const { addMonths, startOfMonth } = dateLib;
-  const year = displayMonth.getFullYear();
-
-  const months: number[] = [];
-  let month = navStart;
-  while (months.length < 12) {
-    months.push(month.getMonth());
-    month = addMonths(month, 1);
-  }
-  const sortedMonths = months.sort((a, b) => {
-    return a - b;
+  const months = eachMonthOfInterval({
+    start: startOfYear(displayMonth),
+    end: endOfYear(displayMonth)
   });
-  const options = sortedMonths.map((value) => {
-    const label = formatters.formatMonthDropdown(
-      value,
-      dateLib.options.locale ?? defaultLocale
-    );
-    const month = new dateLib.Date(year, value);
+
+  const options = months.map((month) => {
+    const label = formatters.formatMonthDropdown(month, dateLib);
+    const value = getMonth(month);
     const disabled =
       (navStart && month < startOfMonth(navStart)) ||
       (navEnd && month > startOfMonth(navEnd)) ||
diff --git a/src/helpers/getYearOptions.ts b/src/helpers/getYearOptions.ts
index 4c88f3161..7a0f35f26 100644
--- a/src/helpers/getYearOptions.ts
+++ b/src/helpers/getYearOptions.ts
@@ -11,21 +11,22 @@ export function getYearOptions(
 ): DropdownOption[] | undefined {
   if (!navStart) return undefined;
   if (!navEnd) return undefined;
-  const { startOfYear, endOfYear, addYears, isBefore, isSameYear } = dateLib;
+  const { startOfYear, endOfYear, addYears, getYear, isBefore, isSameYear } =
+    dateLib;
   const firstNavYear = startOfYear(navStart);
   const lastNavYear = endOfYear(navEnd);
-  const years: number[] = [];
+  const years: Date[] = [];
 
   let year = firstNavYear;
   while (isBefore(year, lastNavYear) || isSameYear(year, lastNavYear)) {
-    years.push(year.getFullYear());
+    years.push(year);
     year = addYears(year, 1);
   }
 
-  return years.map((value) => {
-    const label = formatters.formatYearDropdown(value);
+  return years.map((year) => {
+    const label = formatters.formatYearDropdown(year, dateLib);
     return {
-      value,
+      value: getYear(year),
       label,
       disabled: false
     };


Test patch:
diff --git a/src/formatters/formatMonthDropdown.test.ts b/src/formatters/formatMonthDropdown.test.ts
index b2613cde7..2c79e19e1 100644
--- a/src/formatters/formatMonthDropdown.test.ts
+++ b/src/formatters/formatMonthDropdown.test.ts
@@ -1,19 +1,19 @@
 import { es } from "date-fns/locale/es";
 
-import { defaultLocale } from "../classes/DateLib";
+import { DateLib } from "../classes/DateLib";
 
 import { formatMonthDropdown } from "./formatMonthDropdown";
 
 const date = new Date(2022, 10, 21);
 
 test("should return the formatted month dropdown label", () => {
-  expect(formatMonthDropdown(date.getMonth(), defaultLocale)).toEqual(
-    "November"
-  );
+  expect(formatMonthDropdown(date)).toEqual("November");
 });
 
 describe("when a locale is passed in", () => {
   test("should format using the locale", () => {
-    expect(formatMonthDropdown(date.getMonth(), es)).toEqual("noviembre");
+    expect(formatMonthDropdown(date, new DateLib({ locale: es }))).toEqual(
+      "noviembre"
+    );
   });
 });
diff --git a/src/formatters/formatYearDropdown.test.ts b/src/formatters/formatYearDropdown.test.ts
index a4cb1765a..8b47fabe2 100644
--- a/src/formatters/formatYearDropdown.test.ts
+++ b/src/formatters/formatYearDropdown.test.ts
@@ -1,5 +1,7 @@
+import { defaultDateLib } from "../classes";
+
 import { formatYearDropdown } from "./formatYearDropdown";
 
 test("should return the formatted weekday name", () => {
-  expect(formatYearDropdown(2022)).toEqual("2022");
+  expect(formatYearDropdown(new Date(2022, 0), defaultDateLib)).toEqual("2022");
 });
diff --git a/src/helpers/getFormatters.test.ts b/src/helpers/getFormatters.test.ts
index 373771aff..4672e2ee5 100644
--- a/src/helpers/getFormatters.test.ts
+++ b/src/helpers/getFormatters.test.ts
@@ -1,4 +1,3 @@
-import { defaultDateLib } from "../classes/DateLib";
 import * as defaultFormatters from "../formatters";
 
 import { getFormatters } from "./getFormatters";
@@ -20,12 +19,8 @@ test("merges custom formatters with default formatters", () => {
 
 test("assigns `formatMonthCaption` to `formatCaption` if `formatCaption` is not defined", () => {
   const result = getFormatters({ formatMonthCaption: () => "customMonth" });
-  expect(result.formatCaption(new Date(), {}, defaultDateLib)).toBe(
-    "customMonth"
-  );
-  expect(result.formatMonthCaption(new Date(), {}, defaultDateLib)).toBe(
-    "customMonth"
-  );
+  expect(result.formatCaption(new Date(), {})).toBe("customMonth");
+  expect(result.formatMonthCaption(new Date(), {})).toBe("customMonth");
 });
 
 test("does not overwrite `formatCaption` if already defined", () => {
@@ -33,22 +28,14 @@ test("does not overwrite `formatCaption` if already defined", () => {
     formatMonthCaption: () => "customMonth",
     formatCaption: () => "customCaption"
   });
-  expect(result.formatCaption(new Date(), {}, defaultDateLib)).toBe(
-    "customCaption"
-  );
-  expect(result.formatMonthCaption(new Date(), {}, defaultDateLib)).toBe(
-    "customMonth"
-  );
+  expect(result.formatCaption(new Date(), {})).toBe("customCaption");
+  expect(result.formatMonthCaption(new Date(), {})).toBe("customMonth");
 });
 
 test("assigns `formatYearCaption` to `formatYearDropdown` if `formatYearDropdown` is not defined", () => {
   const result = getFormatters({ formatYearCaption: () => "customYear" });
-  expect(result.formatYearDropdown(new Date(0).getFullYear())).toBe(
-    "customYear"
-  );
-  expect(result.formatYearCaption(new Date(0).getFullYear())).toBe(
-    "customYear"
-  );
+  expect(result.formatYearDropdown(new Date(2000, 0))).toBe("customYear");
+  expect(result.formatYearCaption(new Date(2000, 0))).toBe("customYear");
 });
 
 test("does not overwrite `formatYearDropdown` if already defined", () => {
@@ -56,8 +43,6 @@ test("does not overwrite `formatYearDropdown` if already defined", () => {
     formatYearCaption: () => "customYear",
     formatYearDropdown: () => "customDropdown"
   });
-  expect(result.formatYearDropdown(new Date().getFullYear())).toBe(
-    "customDropdown"
-  );
-  expect(result.formatYearCaption(new Date().getFullYear())).toBe("customYear");
+  expect(result.formatYearDropdown(new Date())).toBe("customDropdown");
+  expect(result.formatYearCaption(new Date())).toBe("customYear");
 });
diff --git a/src/helpers/getYearOptions.test.ts b/src/helpers/getYearOptions.test.ts
index c5473bea0..b54ff45e8 100644
--- a/src/helpers/getYearOptions.test.ts
+++ b/src/helpers/getYearOptions.test.ts
@@ -5,7 +5,7 @@ import { getYearOptions } from "./getYearOptions";
 
 test("return undefined if startMonth or endMonth is not provided", () => {
   const formatters = getFormatters({
-    formatYearDropdown: (year: number) => `${year}`
+    formatYearDropdown: (date: Date) => `${date.getFullYear()}`
   });
   const result1 = getYearOptions(
     undefined,
@@ -28,7 +28,7 @@ test("return correct dropdown options", () => {
   const startMonth = new Date(2022, 0, 1); // January 2022
   const endMonth = new Date(2024, 11, 31); // December 2024
   const formatters = getFormatters({
-    formatYearDropdown: (year: number) => `${year}`
+    formatYearDropdown: (date: Date) => `${date.getFullYear()}`
   });
 
   const result = getYearOptions(