Fix Arabic RTL calendar in public booking date pickers (Task #674)
Problem: In the public room-booking form (protocol-request.tsx), the date
picker calendar showed English weekday headers and a scrambled day grid under
RTL. Root cause: the shared Calendar (react-day-picker v9) was rendered without
`locale` or `dir`, so weekdays stayed English and its flex week rows reversed
visually under CSS RTL.
Changes (artifacts/tx-os/src/components/ui/calendar.tsx):
- Pass date-fns Arabic locale (`ar`) + `dir="rtl"` when Arabic is active, else
`enUS` + `dir="ltr"`; placed before `{...props}` so callers can still override.
- Added `formatCaption` and `formatDay` formatters (alongside existing
`formatMonthDropdown`) using Intl `numberingSystem: "latn"` to keep Western
digits, matching the app-wide AR_LATN convention (Arabic locale would
otherwise emit Arabic-Indic numerals).
- `isArabic()` now resolves from `document.documentElement.lang`/`dir` first,
falling back to `i18n.language`.
Deviation from plan: after code review, changed language detection from
i18n.language-only to DOM-first. Reason: the booking page forces Arabic by
setting documentElement.lang/dir without changing i18n.language, so a guest with
saved English would otherwise get an English/LTR calendar on that page.
Out of scope (per user): no time-picker or من/إلى changes.
Verified: `tsc --noEmit` passes for @workspace/tx-os. English calendar
unaffected (default enUS/LTR).
This commit is contained in:
@@ -7,16 +7,33 @@ import {
|
|||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
||||||
|
import { ar as dfAr, enUS as dfEn } from "date-fns/locale"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
import i18n from "@/i18n"
|
import i18n from "@/i18n"
|
||||||
|
|
||||||
|
function isArabic(): boolean {
|
||||||
|
// The public booking page forces Arabic by setting documentElement.lang/dir
|
||||||
|
// without changing i18n.language, so prefer the DOM signal (i18n keeps it in
|
||||||
|
// sync elsewhere) and fall back to the i18n language.
|
||||||
|
if (typeof document !== "undefined") {
|
||||||
|
const el = document.documentElement
|
||||||
|
if (el.lang) return el.lang.toLowerCase().startsWith("ar")
|
||||||
|
if (el.dir) return el.dir === "rtl"
|
||||||
|
}
|
||||||
|
return i18n.language === "ar"
|
||||||
|
}
|
||||||
|
|
||||||
function activeLocaleLatn(): string {
|
function activeLocaleLatn(): string {
|
||||||
const lang = i18n.language === "ar" ? "ar" : "en-US"
|
const lang = isArabic() ? "ar" : "en-US"
|
||||||
return `${lang}-u-nu-latn`
|
return `${lang}-u-nu-latn`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activeDfLocale() {
|
||||||
|
return isArabic() ? dfAr : dfEn
|
||||||
|
}
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
className,
|
className,
|
||||||
classNames,
|
classNames,
|
||||||
@@ -41,9 +58,15 @@ function Calendar({
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
captionLayout={captionLayout}
|
captionLayout={captionLayout}
|
||||||
|
locale={activeDfLocale()}
|
||||||
|
dir={isArabic() ? "rtl" : "ltr"}
|
||||||
formatters={{
|
formatters={{
|
||||||
formatMonthDropdown: (date) =>
|
formatMonthDropdown: (date) =>
|
||||||
new Intl.DateTimeFormat(activeLocaleLatn(), { month: "short", numberingSystem: "latn" }).format(date),
|
new Intl.DateTimeFormat(activeLocaleLatn(), { month: "short", numberingSystem: "latn" }).format(date),
|
||||||
|
formatCaption: (date) =>
|
||||||
|
new Intl.DateTimeFormat(activeLocaleLatn(), { month: "long", year: "numeric", numberingSystem: "latn" }).format(date),
|
||||||
|
formatDay: (date) =>
|
||||||
|
new Intl.DateTimeFormat(activeLocaleLatn(), { day: "numeric", numberingSystem: "latn" }).format(date),
|
||||||
...formatters,
|
...formatters,
|
||||||
}}
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
|
|||||||
Reference in New Issue
Block a user