انتخابگر بازه تاریخ (DateRangePicker)
کامپوننت انتخاب بازه تاریخ با پشتیبانی از تقویم شمسی و میلادی
معرفی
کامپوننت Date Range Picker برای انتخاب بازه تاریخ (از تاریخ شروع تا تاریخ پایان) استفاده میشود. این کامپوننت از تقویم شمسی (جلالی) و میلادی پشتیبانی میکند و به طور کامل RTL است.
DateRangePicker تنها کنترل بازهٔ زمانی سیستم طراحی است: بازهٔ کل صفحه (period در DashboardPage و DetailPage)، فیلتر تاریخ نوارابزار و «از … تا»ی فرم. مانند Google Analytics، وقتی باز میشود کنار تقویم فهرست بازههای آماده را دارد و کاربر همچنان میتواند بازهٔ دلخواهش را روی تقویم انتخاب کند.
چه زمانی استفاده کنیم:
- برای فیلتر کردن گزارشها و داشبوردها بر اساس بازه زمانی دلخواه کاربر
- در فرمهایی که تاریخ شروع و پایان به هم وابستهاند (مانند بازه اجرای کمپین تخفیف فصلی یا رزرو اقامت)
- زمانی که کاربر باید بازه دقیقی را روی تقویم شمسی یا میلادی انتخاب کند و دیدن روزهای بین شروع و پایان به تصمیم او کمک میکند
چه زمانی استفاده نکنیم:
- برای انتخاب فقط یک تاریخ (مانند تاریخ انتشار یک گزارش) — از
DatePickerاستفاده کنید - برای نمایش دائمی تقویم در صفحه بدون popover و دکمه trigger — از
Calendarباmode="range"به صورت مستقیم استفاده کنید - برای پنجرهٔ زمانی یک کارت نمودار در سرِ همان کارت (
actionsدرDashboardChartیاChartCard) — ازPeriodSelectorاستفاده کنید؛ این تنها جایPeriodSelectorاست و بازهٔ صفحه، نوارابزار و فیلترها همیشهDateRangePickerاست
زمین بازی
با تغییر تنظیمات زیر، پیشنمایش زنده را مشاهده کنید.
استفاده پایه
import { DateRangePicker } from '@partodata/ui'
import { useState } from 'react'
import { DateRange } from 'react-day-picker'
export default function MyComponent() {
const [dateRange, setDateRange] = useState<DateRange | undefined>()
return (
<DateRangePicker value={dateRange} onChange={setDateRange} label="بازه تاریخ" placeholder="انتخاب بازه تاریخ" />
)
}بازههای آماده (پیشفرض)
با باز شدن انتخابگر، فهرست بازههای آماده در سمت آغاز خط (در RTL سمت راست) کنار تقویم میآید و در موبایل بالای تقویم: «7 روز اخیر»، «14 روز اخیر»، «ماه گذشته» (30 روز)، «3 ماه گذشته»، «6 ماه گذشته»، «1 سال اخیر» و در آخر «بازهٔ دلخواه». بازهها امروز را هم در بر دارند و هر دو سرشان نیمهشب محلی است؛ «1 سال اخیر» یک سال تقویم شمسی است.
preset: '30d'
import { DateRangePicker, resolveDateRangePreset, type DateRangeValue } from '@partodata/ui'
// صفحهٔ تازه با «ماه گذشته» شروع میشود، مگر محصول بازهٔ دیگری لازم داشته باشد.
const [period, setPeriod] = React.useState<DateRangeValue | undefined>(() => resolveDateRangePreset('30d'))
<DashboardPage title="داشبورد" period={<DateRangePicker value={period} onChange={setPeriod} />} />- انتخاب یک بازهٔ آماده آن را همان لحظه اعمال میکند (
onChangeصدا زده میشود) و پنجره را میبندد. با باز کردن دوباره، روزهای آن بازه روی تقویم دیده میشوند. - روی تقویم، کلیک اول شروع بازهٔ تازه را انتخاب میکند و پنجره باز میماند؛ کلیک دوم پایان بازه را انتخاب میکند و پنجره بسته میشود. این رفتار بعد از یک بازهٔ قبلی هم همان است؛ تاریخ پایان قبلی وارد بازهٔ تازه نمیشود. برای بازهٔ یکروزه، همان روز را دو بار انتخاب کنید. انتخاب دستی فهرست را به «بازهٔ دلخواه» میبرد.
- تا وقتی یک بازهٔ آماده فعال است دکمه برچسب آن را نشان میدهد («3 ماه گذشته»)، وگرنه تاریخها را.
مدل مقدار: بازهٔ نسبی
مقدار همان DateRange قبلی است بهاضافهٔ کلید بازهٔ آماده: { from, to, preset: '30d' } (نوع DateRangeValue). بازهای
که دستی انتخاب شود preset ندارد. کلید، فیلتر ذخیرهشده یا پیوند اشتراکی را نسبی نگه میدارد: کلید را ذخیره کنید و
هر بار نسبت به امروز حلش کنید — انتخابگر هم مقداری با preset را همیشه به تاریخ امروز نشان میدهد. کلیدها:
'7d'، '14d'، '30d'، '90d'، '180d'، '1y'.
در انتخاب دستی، onChange در کلیک اول مقدار { from, to: undefined } و در کلیک دوم بازهٔ کامل را میدهد. اگر فقط بازهٔ کامل را برای دریافت داده لازم دارید، پیش از ارسال درخواست وجود to را بررسی کنید. بدون value، انتخابگر مقدار را خودش نگه میدارد؛ برای کنترل از بیرون، value و onChange را بدهید. برای پاک کردن مقدار کنترلشده، value={undefined} بدهید.
// در سرور (Server Component یا API): همان تابعی که انتخابگر به کار میبرد.
import { DEFAULT_DATE_RANGE_PRESETS, resolveDateRangePreset } from '@partodata/ui/server'
const range = resolveDateRangePreset(saved.preset ?? '30d') // { from, to, preset }فهرست خود صفحه، یا بدون فهرست
presets فهرست خود صفحه را میگیرد (DateRangePreset[]: key، برچسب هر زبان و تابع range(today)). presets={false}
فقط با دلیل: مثلاً بازهٔ آیندهٔ یک کمپین در فرم، که هیچ بازهٔ آمادهای به کارش نمیآید چون همه به امروز ختم میشوند.
دلیل را روی خودِ prop بنویسید؛ قاعدهٔ ESLint parto/date-range-control بدون آن هشدار میدهد:
<DateRangePicker
value={campaign}
onChange={setCampaign}
minDate={new Date()}
// بازهٔ آیندهٔ کمپین: همهٔ بازههای آماده به امروز ختم میشوند.
presets={false}
/>تقویم شمسی (پیشفرض)
از نسخهٔ 4.0 تقویم پیشفرض شمسی است؛ لازم نیست usePersianCalendar را بدهید (پیش از آن پیشفرض میلادی بود):
<DateRangePicker value={dateRange} onChange={setDateRange} label="بازه تاریخ شمسی" placeholder="انتخاب بازه تاریخ" />DateTimePicker و Calendar هنوز بهطور پیشفرض میلادیاند؛ برای آنها usePersianCalendar را صریحاً بدهید.
تقویم میلادی
برای محصولی که تقویم میلادی لازم دارد، usePersianCalendar={false} بدهید. برچسب دکمه هم میلادی میشود:
<DateRangePicker value={dateRange} onChange={setDateRange} usePersianCalendar={false} placeholder="Select range" />اگر fromYear/toYear را با سال میلادی میدهید، usePersianCalendar={false} را هم بدهید؛ این دو سال همیشه سال تقویم فعالاند (بخش «انتخابگر سال و ماه» در ادامه).
اندازه و عرض
دکمهٔ باز کننده روی همان نردبان اندازهٔ کنترلها (xs تا xl) قرار دارد؛ پیشفرض md (38 پیکسل) است، اندازهٔ فیلد فرم، و در نوار ابزار، FilterBar و سرِ کارتها و سربرگها ردیف آن را sm (30 پیکسل) میکند، تا با دکمهها، ورودیها و Select همردیف خود همقد باشد. بهطور پیشفرض دکمهٔ DateRangePicker تمام عرض ظرفش را با حداقل 256 پیکسل پر میکند، و دکمهٔ DateRangePickerInline به اندازهٔ متنش است با همان حداقل 256 پیکسل. در نوار ابزار و ردیف فیلتر با autoWidth عرض دکمه دقیقاً به اندازهٔ متنش میشود:
<div className="flex items-center gap-2">
<Input size="xs" placeholder="جستوجو" className="w-60" />
<DateRangePicker size="xs" autoWidth value={dateRange} onChange={setDateRange} />
<Button size="xs" variant="default">
اعمال
</Button>
</div>نام autoWidth است و نه block، چون جهتش برعکس است: Button بهطور پیشفرض به اندازهٔ متنش است و block آن را تمامعرض میکند، اما این انتخابگرها بهطور پیشفرض پهناند و autoWidth آنها را به اندازهٔ متن برمیگرداند.
ویژگیها
تعداد ماهها
میتوانید تعداد ماههای نمایش داده شده را تغییر دهید:
<DateRangePicker numberOfMonths={1} value={dateRange} onChange={setDateRange} />محدودیت تاریخ
میتوانید حداقل و حداکثر تاریخ قابل انتخاب را مشخص کنید:
<DateRangePicker
value={dateRange}
onChange={setDateRange}
minDate={new Date(2024, 0, 1)}
maxDate={new Date(2024, 11, 31)}
/>روزهای خارج از این بازه غیرفعال میشوند (قابل کلیک نیستند و onChange را صدا نمیزنند) و حرکت بین ماهها هم به همین بازه محدود میشود.
تا نسخه 3.0.0 این دو prop بیاثر بودند
minDate/maxDate به fromDate/toDate تقویم منتقل میشدند، اما react-day-picker نسخه 9 این دو prop را حذف کرده
است (نه تغییر نام). به همین دلیل محدودیت تاریخ در عمل اعمال نمیشد و کاربر میتوانست هر تاریخی را انتخاب کند. اگر روی
نسخههای قدیمیتر برای این محدودیت راهحل جانبی نوشتهاید، اکنون میتوانید آن را حذف کنید.
تقویم برچسب دکمه
متن روی دکمهی trigger همیشه با همان تقویمی نوشته میشود که در پنل باز میشود: بهطور پیشفرض تاریخ شمسی
(«1 فروردین - 1 اردیبهشت 1403») و با usePersianCalendar={false} تاریخ میلادی (Jan 01, 2024 - Jan 31, 2024). ارقام با حروف لاتین در DOM
نوشته میشوند و فونت آنها را با قابلیت ss01 به شکل فارسی نمایش میدهد؛ بنابراین کپی/جستوجو و خواندن با صفحهخوان
سالم میماند. DatePicker از نسخهٔ 4.0 همین برچسب را نشان میدهد.
انتخابگر سال و ماه
برای نمایش dropdown برای انتخاب سال و ماه، captionLayout="dropdown" بدهید. fromYear و toYear سالهای تقویم فعالاند: در تقویم شمسی (پیشفرض) سال شمسی، و با usePersianCalendar={false} سال میلادی. فهرست سالها از fromYear تا خود toYear است.
<DateRangePicker value={dateRange} onChange={setDateRange} captionLayout="dropdown" fromYear={1380} toYear={1410} />سال میلادی روی تقویم شمسی
تا 3٫x پیشفرض میلادی بود، پس کدی مثل fromYear={2020} toYear={2030} بدون usePersianCalendar سال میلادی میداد.
اکنون همین اعداد سال شمسی خوانده میشوند (سال 2020 شمسی) و تاریخ انتخابشده و امروز در فهرست نیستند. در این کد
usePersianCalendar={false} را اضافه کنید یا سالها را شمسی بنویسید.
بازههای آماده بیرون از minDate / maxDate
بازهٔ آمادهای که پیش از minDate شروع یا پس از maxDate تمام شود در فهرست غیرفعال است (تقویم هم روزهایش را نمیپذیرد).
مقایسه روزبهروز است، پس maxDate امروز (هر ساعتی) همهٔ بازههایی را که امروز تمام میشوند باز میگذارد.
راهنمای استفاده
بکنید
- از DateRangePicker برای هر بازهٔ زمانی استفاده کنید: بازهٔ صفحه (
period)، فیلتر نوارابزار و «از … تا»ی فرم - بازههای آماده را روشن بگذارید و صفحهٔ تازه را با «ماه گذشته» (resolveDateRangePreset('30d')) شروع کنید - در نوارابزار صفحه (filtersدرListPage) و درFormRowبدونlabel: نام فیلترplaceholderاست و نام فیلد فرم برچسب ردیف؛labelفقط برای انتخابگری تنها بیرون از این دو - برای محدود کردن بازه قابل انتخاب، ازminDateوmaxDateاستفاده کنید
نکنید
- برای انتخاب فقط یک تاریخ از DateRangePicker استفاده نکنید — از
DatePickerباmode="single"استفاده کنید - برای بازهٔ صفحه یا فیلتر ازPeriodSelectorیاDatePickerدر حالت بازه استفاده نکنید؛PeriodSelectorفقط در سرِ یک کارت نمودار است -presets={false}را بیدلیل ننویسید - فراموش نکنید که مقادیرDateهمیشه میلادی هستند، حتی وقتی تقویم شمسی فعال است
Props
مثالهای کاربردی
فرم رزرو هتل
export default function HotelBookingForm() {
const [checkInOut, setCheckInOut] = useState<DateRange | undefined>()
return (
<form>
<DateRangePicker
value={checkInOut}
onChange={setCheckInOut}
label="تاریخ ورود و خروج"
placeholder="انتخاب تاریخ"
minDate={new Date()}
numberOfMonths={2}
/>
</form>
)
}فیلتر گزارش با تقویم شمسی
export default function ReportFilter() {
const [reportRange, setReportRange] = useState<DateRange | undefined>()
return (
<div className="flex gap-4">
<DateRangePicker
value={reportRange}
onChange={setReportRange}
label="بازه گزارش"
placeholder="از تاریخ - تا تاریخ"
captionLayout="dropdown"
fromYear={1400}
toYear={1404}
/>
<Button onClick={() => console.log(reportRange)}>مشاهده گزارش</Button>
</div>
)
}نکات مهم
-
تقویم شمسی: با تقویم شمسی (پیشفرض)، تاریخها به صورت شمسی نمایش داده میشوند اما مقادیر
Dateهمچنان میلادی هستند. -
بسته شدن خودکار: کلیک اول روی تقویم شروع بازهٔ تازه را انتخاب میکند و پنجره باز میماند؛ کلیک دوم پایان را انتخاب میکند و پنجره بسته میشود. انتخاب یک بازهٔ آماده با همان یک کلیک اعمال میشود و پنجره را میبندد.
-
RTL: این کامپوننت به طور کامل از RTL پشتیبانی میکند و در محیط راست به چپ به درستی کار میکند.
-
دسترسی: کامپوننت از استانداردهای دسترسی پیروی میکند و با کیبورد قابل استفاده است.
-
بازه برای API: هر روزِ بازه نیمهشب محلی (00:00) آن روز است —
fromوtoهر دو؛ یک روز تنها{ from: d, to: d }است. APIای که باpublishedAt <= toفیلتر میکند روز آخر را جا میاندازد (و بازهٔ یکروزه چیزی پیدا نمیکند)، پسfromرا همانطور وtoرا پایانِ روز آخرش بفرستید:
// روزهای انتخابگر 00:00 هستند: بازه تا پایانِ روز آخرش میرود، وگرنه API آن روز را جا میاندازد.
const endOfDay = (day: Date) => new Date(day.getFullYear(), day.getMonth(), day.getDate(), 23, 59, 59, 999)
const from = range?.from?.toISOString()
const to = range?.to && endOfDay(range.to).toISOString()توابع کمکی
برای کار با تاریخهای شمسی، میتوانید از توابع utility استفاده کنید:
import { formatPersianDateRange, toPersianDigits, getPersianMonthName } from '@partodata/ui'
const formattedRange = formatPersianDateRange(new Date(2024, 0, 1), new Date(2024, 0, 15))
// خروجی: "11 - 25 دی 1402"دسترسی به توابع کمکی
formatPersianDateRange(from: Date, to: Date): string
تبدیل بازه تاریخ به رشته فارسی
toPersianDigits(num: number | string): string
تبدیل اعداد انگلیسی به فارسی
getPersianMonthName(date: Date): string
دریافت نام ماه شمسی
getPersianYear(date: Date): number
دریافت سال شمسی
jalaliToGregorian(year: number, month: number, day: number): Date
تبدیل تاریخ شمسی به میلادی
دسترسیپذیری
- دکمه trigger یک
<button>استاندارد است؛ با Enter/Space باز میشود و popover تقویم بهصورت Radix Popover فوکس را trap میکند. - در تقویم: کلیدهای جهت (← → ↑ ↓) برای جابهجایی روز،
Page Up/Page Downبرای ماه، وHome/Endبرای ابتدا/انتهای هفته کار میکنند. Enterتاریخ زیر فوکس را انتخاب میکند؛ پس از انتخاب هر دو تاریخ شروع و پایان، popover خودکار بسته میشود.- فهرست بازههای آماده یک
radiogroupبا نام «بازههای آماده» است: با Tab به گزینهٔ فعال میرسید، کلیدهای جهت بین بازهها جابهجا میشوند (در انتها به ابتدا برمیگردند) و Enter یا Space بازه را انتخاب میکند؛ گزینهٔ فعالaria-checked="true"دارد. Escapepopover را میبندد و فوکس به دکمه trigger برمیگردد (بازهٔ انتخابشده همان لحظه اعمال شده است).- روزهای انتخابشده با
aria-selected="true"، روزهای داخل بازه باaria-currentمتمایز میشوند، و بازه باdata-range-middlemarkup میشود. labelبه عنوان label قابل-دسترس به صورت بصری بالای trigger ظاهر میشود و screen reader هدف فیلد را اعلام میکند.- در حالت
usePersianCalendar={true}، نام ماه/روز و اعداد به فارسی اعلام میشوند ولی مقدارDateهمچنان میلادی است — بنابراین i18n value معنادار باقی میماند.
کامپوننتهای مرتبط
- DatePicker — اگر نیاز به انتخاب یک تاریخ (نه بازه) دارید، از DatePicker با
mode="single"استفاده کنید - PeriodSelector — فقط تغییر فشردهٔ پنجرهٔ زمانی در سرِ یک کارت نمودار؛ بازهٔ صفحه، نوارابزار و فیلترها
DateRangePickerاست - Calendar — اگر نیاز به نمایش تقویم بدون popover و input دارید، از Calendar مستقیماً استفاده کنید