تقويم (Calendar)
تقويم شهري لاختيار يوم واحد أو مدة من الأيام. يعرض شهرًا أو شهرين أو ثلاثة، ويمكن أن يحلّ محلّ عنوانه قائمتان للشهر والسنة، وتظهر أيامه بزوايا مربعة أو دائرية. وفي الصفحة العربية يعرض أسماء الأشهر بالعربية مع الأرقام الغربية، ويبدأ الأسبوع يوم السبت، وينعكس اتجاهه.
التثبيت
npx @smicolon/fasla-ui@latest add calendarقبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.
معاينة
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
الحالات
الأيام كما يرسمها التقويم: اليوم العادي، وأيام الشهرين المجاورين بنص خافت، واليوم الحالي بتعبئة هادئة، واليوم المختار بتعبئة كاملة. وإذا اخترت اليوم الحالي ظهرت نقطة تحت رقمه. أما الأيام التي توقفها بالخاصية disabled فتظهر بنصف شفافيتها ولا يمكن اختيارها. ضع المؤشر على يوم لترى حالة التمرير.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
الزوايا
تمنح القيمة radius="standard" الأيام وشريط المدة زوايا بنصف قطر 8px، وتجعلها القيمة radius="rounded" دوائر كاملة. وتبقى زوايا بطاقة التقويم 8px في الحالتين.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
المدة
تتيح القيمة mode="range" اختيار يوم للبداية ويوم للنهاية. يظهر الطرفان كأيام مختارة، وتقع الأيام بينهما على شريط ينقطع في نهاية كل أسبوع. واضبط الخاصية numberOfMonths على 2 لعرض شهرين متجاورين، ولا تظهر الأسهم إلا في الشهرين الخارجيين.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
ثلاثة أشهر
تتسع المساحة لثلاثة أشهر متجاورة على الأكثر، لمدة تمتد عبرها. وإذا ضاقت الصفحة عنها، انتقلت الأشهر إلى صفوف أخرى.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
قائمتا الشهر والسنة
تضع القيمة captionLayout="dropdown" مكان اسم الشهر وسهميه قائمتين من نوع حقل الاختيار، واحدة للشهر وأخرى للسنة. وتحدد الخاصية startMonth مع الخاصية endMonth السنوات التي تعرضها القائمة.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
مع أوقات المواعيد
تركيب تبنيه بنفسك، لا خاصية: عمود من أوقات المواعيد يعلوه اليوم المختار، في بداية التقويم ويفصله عنه خط. والأوقات التي مضت تظهر خافتة ولا يمكن اختيارها. تجد الشيفرة في قسم الاستخدام.
الخميس، 18
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
مع اختيارات جاهزة
وهذا تركيب أيضًا: أزرار من النوع Ghost فوق التقويم تختار يومًا بضغطة واحدة وتنقل التقويم إلى شهره. واليوم الحالي هنا هو السادس من الشهر، فإذا اخترت «اليوم» ظهرت النقطة. تجد الشيفرة في قسم الاستخدام.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
في نموذج
استخدم التقويم في النماذج عبر منتقي التاريخ: حقل له تسمية ونص مساعد، يفتح التقويم تحته ويرسل اليوم المختار مع النموذج.
إتاحة الوصول (accessibility)
- كل شهر شبكة بالدور
gridمن الأزرار، زر لكل يوم، واسمه تاريخه كاملًا مع بيان إن كان اليوم الحالي أو يومًا مختارًا. ويسمّي عنوان الشهر الشبكة. - تنقلك مفاتيح الأسهم يومًا أو أسبوعًا، ومفتاح Page Up أو مفتاح Page Down شهرًا، ومع مفتاح Shift سنة. وينقلك مفتاح Home إلى بداية الأسبوع، ومفتاح End إلى نهايته، ويختار مفتاح Enter أو مفتاح المسافة اليوم.
- يوم واحد فقط في ترتيب التنقل بمفتاح Tab: اليوم المختار، وإلا فاليوم الحالي. يصل مفتاح Tab إلى الأسهم أولًا ثم إلى ذلك اليوم.
- يأتي الاتجاه واللغة من الصفحة، ولا توجد لهما خاصية. وفي الصفحة العربية يتبادل السهمان طرفيهما ويشيران إلى الجهة الأخرى، وتتبع مفاتيح الأسهم اتجاه القراءة، ويقرأ قارئ الشاشة كل التسميات بالعربية.
الخصائص (props)
Calendar
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
mode | "single" | "multiple" | "range" | لا يوجد | طريقة اختيار الأيام: يوم واحد، أو عدة أيام، أو مدة. |
selected | Date | Date[] | DateRange | لا يوجد | اليوم أو الأيام أو المدة المختارة. وتُستخدم مع onSelect. |
onSelect | (selected) => void | لا يوجد | تُستدعى بالاختيار الجديد عند الضغط على يوم. |
required | boolean | false | يُبقي يومًا مختارًا: الضغط على اليوم المختار مرة أخرى لا يلغيه. |
defaultMonth | Date | لا يوجد | الشهر المعروض عند العرض الأول. |
month | Date | لا يوجد | الشهر المعروض حين تتحكم به. ويُستخدم مع onMonthChange. |
onMonthChange | (month: Date) => void | لا يوجد | تُستدعى بالشهر الجديد حين تغيّره الأسهم أو القائمتان. |
numberOfMonths | number | 1 | عدد الأشهر المتجاورة: 1 أو 2 أو 3. |
captionLayout | "label" | "dropdown" | "label" | اسم الشهر مع السهمين، أو قائمة للشهر وأخرى للسنة. |
startMonth | Date | لا يوجد | أقدم شهر تصل إليه الأسهم والقائمتان. |
endMonth | Date | لا يوجد | آخر شهر تصل إليه الأسهم والقائمتان. |
radius | "standard" | "rounded" | "standard" | زوايا الأيام والمدة: بنصف قطر 8px، أو دوائر كاملة. |
disabled | Matcher | Matcher[] | لا يوجد | أيام لا يمكن اختيارها: تاريخ، أو مدة، أو يوم من الأسبوع، أو دالة. |
today | Date | لا يوجد | اليوم الذي يُرسم بوصفه اليوم الحالي. والافتراضي التاريخ الحقيقي. |
showOutsideDays | boolean | true | يعرض أيام الشهرين المجاورين بنص خافت. |
fixedWeeks | boolean | true | يعرض ستة أسابيع دائمًا، فيحافظ التقويم على ارتفاعه بين الأشهر. |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | لا يوجد | أول أيام الأسبوع، من 0 للأحد إلى 6 للسبت. والافتراضي السبت في الصفحة التي تُقرأ من اليمين إلى اليسار، والأحد في غيرها. |
locale | DayPickerLocale | لا يوجد | أسماء الأشهر والأيام والتسميات. والافتراضي العربية في الصفحة العربية، والإنجليزية في غيرها. |
className | string | لا يوجد | أصناف CSS إضافية لبطاقة التقويم. |
الاستخدام
يوم واحد
"use client"
import * as React from "react"
import { Calendar } from "@/components/ui/calendar"
export function DeliveryDay() {
const [day, setDay] = React.useState<Date | undefined>(new Date())
return <Calendar mode="single" selected={day} onSelect={setDay} />
}مدة على شهرين
"use client"
import * as React from "react"
import type { DateRange } from "react-day-picker"
import { Calendar } from "@/components/ui/calendar"
export function StayDates() {
const [stay, setStay] = React.useState<DateRange | undefined>()
return (
<Calendar
mode="range"
numberOfMonths={2}
selected={stay}
onSelect={setStay}
disabled={{ before: new Date() }}
/>
)
}مع أوقات المواعيد
"use client"
import * as React from "react"
import { Calendar } from "@/components/ui/calendar"
import { cn } from "@/lib/utils"
const slots = ["09:00", "09:30", "10:00", "10:30", "11:00", "11:30"]
export function Appointment() {
const [day, setDay] = React.useState<Date>(new Date())
const [time, setTime] = React.useState<string>()
return (
<div className="flex w-fit rounded-md bg-background p-3 shadow">
<div className="relative w-[9.25rem] shrink-0 border-e">
<div className="absolute inset-0 flex flex-col gap-4 px-3">
<p className="pt-1 text-center text-sm font-medium">
{day.toLocaleDateString(undefined, { weekday: "long", day: "numeric" })}
</p>
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
{slots.map((slot) => (
<button
key={slot}
type="button"
aria-pressed={slot === time}
onClick={() => setTime(slot)}
className={cn(
"h-8 shrink-0 rounded-md border text-sm",
slot === time ? "border-primary bg-primary text-primary-foreground" : "border-input hover:bg-accent"
)}
>
{slot}
</button>
))}
</div>
</div>
</div>
<Calendar
mode="single"
required
selected={day}
onSelect={setDay}
className="ms-2 rounded-none p-0 shadow-none"
/>
</div>
)
}مع اختيارات جاهزة
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
const shift = (date: Date, days: number, months = 0) =>
new Date(date.getFullYear(), date.getMonth() + months, date.getDate() + days)
const startOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1)
export function DayWithPresets() {
const today = new Date()
const [day, setDay] = React.useState<Date>(today)
const [month, setMonth] = React.useState<Date>(startOfMonth(today))
const presets = [
{ label: "اليوم", date: today },
{ label: "أمس", date: shift(today, -1) },
{ label: "الأسبوع الماضي", date: shift(today, -7) },
{ label: "الشهر الماضي", date: shift(today, 0, -1) },
]
return (
<div className="flex w-fit flex-col items-center gap-6 rounded-md bg-background p-3 shadow">
<div className="flex gap-1">
{presets.map((preset) => (
<Button
key={preset.label}
variant="ghost"
size="xs"
onClick={() => {
setDay(preset.date)
setMonth(startOfMonth(preset.date))
}}
>
{preset.label}
</Button>
))}
</div>
<Calendar
mode="single"
required
month={month}
onMonthChange={setMonth}
selected={day}
onSelect={setDay}
className="rounded-none p-0 shadow-none"
/>
</div>
)
}قائمتا الشهر والسنة
"use client"
import * as React from "react"
import { Calendar } from "@/components/ui/calendar"
export function BirthDay() {
const [day, setDay] = React.useState<Date | undefined>()
return (
<Calendar
mode="single"
captionLayout="dropdown"
startMonth={new Date(1926, 0)}
endMonth={new Date()}
selected={day}
onSelect={setDay}
/>
)
}