A right-to-left Persian (Solar Hijri) date picker with Persian month names and digits, a Saturday week start, Nowruz marked and an ISO value. Use this when your app serves Persian-speaking users who expect their own calendar, direction and week.
registry/patterns/persian-calendar-picker/persian-calendar-picker.tsx
"use client"
import * as React from "react"
import { CalendarGrid } from "@/components/ui/calendar-grid"
import {
addDays,
calendarFullLabel,
calendarParts,
eachDay,
endOfMonth,
startOfMonth,
today as todayISO,
} from "@/components/ui/date-time-core"
export interface PersianCalendarPickerProps {
value?: string | null
defaultValue?: string | null
onValueChange?: (value: string | null) => void
locale?: string
today?: string
}
/** The first day of the Persian year. */
export function isNowruz(iso: string) {
const p = calendarParts(iso, "persian", "en")
return p.month === 1 && p.day === 1
}
export function PersianCalendarPicker({
value,
defaultValue = null,
onValueChange,
locale = "fa-IR",
today,
}: PersianCalendarPickerProps) {
const now = today ?? todayISO()
const [inner, setInner] = React.useState<string | null>(defaultValue)
const [month, setMonth] = React.useState(defaultValue ?? now)
const date = value === undefined ? inner : value
// Nowruz can sit in the neighbouring Gregorian month's grid, so look a week either side.
const visible = eachDay(
addDays(startOfMonth(month), -7),
addDays(endOfMonth(month), 7)
)
const markers = Object.fromEntries(
visible.filter(isNowruz).map((d) => [d, "Nowruz"])
)
return (
<div dir="rtl" lang={locale} className="flex w-fit flex-col gap-2">
<CalendarGrid
label="تقویم"
calendar="persian"
locale={locale}
today={now}
firstDay={6}
month={month}
onMonthChange={setMonth}
markers={markers}
value={date}
onValueChange={(d) => {
if (value === undefined) setInner(d as string | null)
onValueChange?.(d as string | null)
}}
className="border"
/>
<p role="status" className="text-sm">
{date
? calendarFullLabel(date, "persian", locale)
: "یک روز را انتخاب کنید"}
</p>
</div>
)
}