From 41a7f4daeaaa79b2a10a71a84ff3488cc3955464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 9 Oct 2026 18:22:10 +0200 Subject: [PATCH] perf(ui): load the date field's calendar when it first opens react-day-picker and the seven date-fns locales rode the first load of every page with a date field, the dashboard and the settings sections among them. The calendar now lives in its own module that the popover loads on first open; the locale map moves beside it. --- src/components/day/day-date-picker.tsx | 2 +- src/components/ui/calendar-locale.ts | 29 +++++++++++++ src/components/ui/date-field-calendar.tsx | 40 +++++++++++++++++ src/components/ui/date-field.tsx | 52 +++++++++-------------- 4 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 src/components/ui/calendar-locale.ts create mode 100644 src/components/ui/date-field-calendar.tsx diff --git a/src/components/day/day-date-picker.tsx b/src/components/day/day-date-picker.tsx index eda77a736..b6c61ba0e 100644 --- a/src/components/day/day-date-picker.tsx +++ b/src/components/day/day-date-picker.tsx @@ -5,7 +5,7 @@ import { useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Calendar, CalendarDayButton } from "@/components/ui/calendar"; import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; -import { resolveDateFnsLocale } from "@/components/ui/date-field"; +import { resolveDateFnsLocale } from "@/components/ui/calendar-locale"; import { Popover, PopoverContent, diff --git a/src/components/ui/calendar-locale.ts b/src/components/ui/calendar-locale.ts new file mode 100644 index 000000000..138686267 --- /dev/null +++ b/src/components/ui/calendar-locale.ts @@ -0,0 +1,29 @@ +import { de, enUS, es, fr, it, ko, pl } from "date-fns/locale"; +import type { Locale as DateFnsLocale } from "date-fns"; + +import type { Locale } from "@/lib/i18n/config"; + +/** + * Map the app locale → the date-fns locale so the calendar's month / weekday + * names match the UI language. Defaults to enUS for anything unmapped. + * Imported only by the calendars, which load on demand, so the seven + * locales never ride a page's first load. + */ +export function resolveDateFnsLocale(locale: Locale): DateFnsLocale { + switch (locale) { + case "de": + return de; + case "es": + return es; + case "fr": + return fr; + case "it": + return it; + case "pl": + return pl; + case "ko": + return ko; + default: + return enUS; + } +} diff --git a/src/components/ui/date-field-calendar.tsx b/src/components/ui/date-field-calendar.tsx new file mode 100644 index 000000000..9ee496761 --- /dev/null +++ b/src/components/ui/date-field-calendar.tsx @@ -0,0 +1,40 @@ +"use client"; + +import type { Matcher } from "react-day-picker"; + +import { Calendar } from "@/components/ui/calendar"; +import type { Locale } from "@/lib/i18n/config"; + +import { resolveDateFnsLocale } from "./calendar-locale"; + +/** + * `DateField`'s single-day calendar, in its own module so the field can + * load it on demand (`date-field.tsx`). Takes the app locale and resolves + * the date-fns one here, beside react-day-picker, so neither rides the + * field. + */ +export function DateFieldCalendar({ + appLocale, + selected, + onSelect, + disabled, + defaultMonth, +}: { + appLocale: Locale; + selected: Date | undefined; + onSelect: (date: Date | undefined) => void; + disabled: Matcher[] | undefined; + defaultMonth: Date; +}) { + return ( + + ); +} diff --git a/src/components/ui/date-field.tsx b/src/components/ui/date-field.tsx index ed011d731..95b427c6f 100644 --- a/src/components/ui/date-field.tsx +++ b/src/components/ui/date-field.tsx @@ -2,20 +2,35 @@ import * as React from "react"; import { CalendarIcon } from "lucide-react"; +import dynamic from "next/dynamic"; import type { Matcher } from "react-day-picker"; -import { de, enUS, es, fr, it, ko, pl } from "date-fns/locale"; -import type { Locale as DateFnsLocale } from "date-fns"; import { cn } from "@/lib/utils"; import { useTranslations, useDateFormatPreference } from "@/lib/i18n/context"; import { formatDate, parseIsoDate } from "@/lib/date-format"; -import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +/** + * The calendar (react-day-picker and the date-fns month names of every + * interface language) loads when the popover first opens, not with every + * form that holds a date field: it is the heaviest part of the field and + * the only one a person may never use. Until it arrives the popover keeps + * the calendar's footprint. + */ +const DateFieldCalendar = dynamic( + () => import("./date-field-calendar").then((m) => m.DateFieldCalendar), + { + ssr: false, + loading: () => ( +
+ ), + }, +); + /** * Dependency-free date input that DISPLAYS the value in the user's date-order * preference (AUTO follows the locale; DMY / MDY / YMD pin the field order) @@ -236,8 +251,7 @@ export const DateField = React.forwardRef(
- { commitIso(d ? dateToIso(d) : ""); @@ -245,8 +259,7 @@ export const DateField = React.forwardRef( }} disabled={disabledMatchers.length ? disabledMatchers : undefined} defaultMonth={selectedDate ?? new Date()} - locale={resolveDateFnsLocale(locale)} - autoFocus + appLocale={locale} /> @@ -311,31 +324,6 @@ function resolveOrder( return locale === "en" ? "MDY" : "DMY"; } -/** - * Map the app locale → the date-fns locale so the calendar's month / weekday - * names match the UI language. Defaults to enUS for anything unmapped. - */ -export function resolveDateFnsLocale( - locale: ReturnType["locale"], -): DateFnsLocale { - switch (locale) { - case "de": - return de; - case "es": - return es; - case "fr": - return fr; - case "it": - return it; - case "pl": - return pl; - case "ko": - return ko; - default: - return enUS; - } -} - /** * ISO `yyyy-MM-dd` → a LOCAL `Date` at midnight (no UTC shift — the value is a * plain calendar date, so it must read back as the same day in the user's