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.
This commit is contained in:
Marc-André Bombeck committed 2026-10-09 18:22:10 +02:00
1 parent 86fa57d8bf
commit 41a7f4daea
4 files changed
+90 -33

No files matched your search

+1 -1
View File
@@ -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,
+29
View File
@@ -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;
}
}
+40
View File
@@ -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 (
<Calendar
mode="single"
selected={selected}
onSelect={onSelect}
disabled={disabled}
defaultMonth={defaultMonth}
locale={resolveDateFnsLocale(appLocale)}
autoFocus
/>
);
}
+20 -32
View File
@@ -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: () => (
<div data-slot="date-field-calendar-loading" className="h-72 w-64" />
),
},
);
/**
* 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<HTMLInputElement, DateFieldProps>(
</PopoverTrigger>
</div>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
<DateFieldCalendar
selected={selectedDate}
onSelect={(d) => {
commitIso(d ? dateToIso(d) : "");
@@ -245,8 +259,7 @@ export const DateField = React.forwardRef<HTMLInputElement, DateFieldProps>(
}}
disabled={disabledMatchers.length ? disabledMatchers : undefined}
defaultMonth={selectedDate ?? new Date()}
locale={resolveDateFnsLocale(locale)}
autoFocus
appLocale={locale}
/>
</PopoverContent>
</Popover>
@@ -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<typeof useTranslations>["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