mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
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:
1 parent
86fa57d8bf
commit
41a7f4daea
4 files changed
+90
-33
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user