تقويم (Calendar)

تقويم شهري لاختيار يوم واحد أو مدة من الأيام. يعرض شهرًا أو شهرين أو ثلاثة، ويمكن أن يحلّ محلّ عنوانه قائمتان للشهر والسنة، وتظهر أيامه بزوايا مربعة أو دائرية. وفي الصفحة العربية يعرض أسماء الأشهر بالعربية مع الأرقام الغربية، ويبدأ الأسبوع يوم السبت، وينعكس اتجاهه.

التثبيت

bash
npx @smicolon/fasla-ui@latest add calendar

قبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.

معاينة

June 2026

الحالات

الأيام كما يرسمها التقويم: اليوم العادي، وأيام الشهرين المجاورين بنص خافت، واليوم الحالي بتعبئة هادئة، واليوم المختار بتعبئة كاملة. وإذا اخترت اليوم الحالي ظهرت نقطة تحت رقمه. أما الأيام التي توقفها بالخاصية disabled فتظهر بنصف شفافيتها ولا يمكن اختيارها. ضع المؤشر على يوم لترى حالة التمرير.

June 2026
June 2026

الزوايا

تمنح القيمة radius="standard" الأيام وشريط المدة زوايا بنصف قطر 8px، وتجعلها القيمة radius="rounded" دوائر كاملة. وتبقى زوايا بطاقة التقويم 8px في الحالتين.

June 2026
June 2026

المدة

تتيح القيمة mode="range" اختيار يوم للبداية ويوم للنهاية. يظهر الطرفان كأيام مختارة، وتقع الأيام بينهما على شريط ينقطع في نهاية كل أسبوع. واضبط الخاصية numberOfMonths على 2 لعرض شهرين متجاورين، ولا تظهر الأسهم إلا في الشهرين الخارجيين.

June 2026
July 2026

ثلاثة أشهر

تتسع المساحة لثلاثة أشهر متجاورة على الأكثر، لمدة تمتد عبرها. وإذا ضاقت الصفحة عنها، انتقلت الأشهر إلى صفوف أخرى.

June 2026
July 2026
August 2026

قائمتا الشهر والسنة

تضع القيمة captionLayout="dropdown" مكان اسم الشهر وسهميه قائمتين من نوع حقل الاختيار، واحدة للشهر وأخرى للسنة. وتحدد الخاصية startMonth مع الخاصية endMonth السنوات التي تعرضها القائمة.

June 2026

مع أوقات المواعيد

تركيب تبنيه بنفسك، لا خاصية: عمود من أوقات المواعيد يعلوه اليوم المختار، في بداية التقويم ويفصله عنه خط. والأوقات التي مضت تظهر خافتة ولا يمكن اختيارها. تجد الشيفرة في قسم الاستخدام.

الخميس، 18

June 2026

مع اختيارات جاهزة

وهذا تركيب أيضًا: أزرار من النوع Ghost فوق التقويم تختار يومًا بضغطة واحدة وتنقل التقويم إلى شهره. واليوم الحالي هنا هو السادس من الشهر، فإذا اخترت «اليوم» ظهرت النقطة. تجد الشيفرة في قسم الاستخدام.

June 2026

في نموذج

استخدم التقويم في النماذج عبر منتقي التاريخ: حقل له تسمية ونص مساعد، يفتح التقويم تحته ويرسل اليوم المختار مع النموذج.

تاريخ الميلاد
نستخدم تاريخ ميلادك لحساب عمرك.

إتاحة الوصول (accessibility)

  • كل شهر شبكة بالدور grid من الأزرار، زر لكل يوم، واسمه تاريخه كاملًا مع بيان إن كان اليوم الحالي أو يومًا مختارًا. ويسمّي عنوان الشهر الشبكة.
  • تنقلك مفاتيح الأسهم يومًا أو أسبوعًا، ومفتاح Page Up أو مفتاح Page Down شهرًا، ومع مفتاح Shift سنة. وينقلك مفتاح Home إلى بداية الأسبوع، ومفتاح End إلى نهايته، ويختار مفتاح Enter أو مفتاح المسافة اليوم.
  • يوم واحد فقط في ترتيب التنقل بمفتاح Tab: اليوم المختار، وإلا فاليوم الحالي. يصل مفتاح Tab إلى الأسهم أولًا ثم إلى ذلك اليوم.
  • يأتي الاتجاه واللغة من الصفحة، ولا توجد لهما خاصية. وفي الصفحة العربية يتبادل السهمان طرفيهما ويشيران إلى الجهة الأخرى، وتتبع مفاتيح الأسهم اتجاه القراءة، ويقرأ قارئ الشاشة كل التسميات بالعربية.

الخصائص (props)

Calendar

الخاصيةالنوعالافتراضيالوصف
mode"single" | "multiple" | "range"لا يوجدطريقة اختيار الأيام: يوم واحد، أو عدة أيام، أو مدة.
selectedDate | Date[] | DateRangeلا يوجداليوم أو الأيام أو المدة المختارة. وتُستخدم مع onSelect.
onSelect(selected) => voidلا يوجدتُستدعى بالاختيار الجديد عند الضغط على يوم.
requiredbooleanfalseيُبقي يومًا مختارًا: الضغط على اليوم المختار مرة أخرى لا يلغيه.
defaultMonthDateلا يوجدالشهر المعروض عند العرض الأول.
monthDateلا يوجدالشهر المعروض حين تتحكم به. ويُستخدم مع onMonthChange.
onMonthChange(month: Date) => voidلا يوجدتُستدعى بالشهر الجديد حين تغيّره الأسهم أو القائمتان.
numberOfMonthsnumber1عدد الأشهر المتجاورة: 1 أو 2 أو 3.
captionLayout"label" | "dropdown""label"اسم الشهر مع السهمين، أو قائمة للشهر وأخرى للسنة.
startMonthDateلا يوجدأقدم شهر تصل إليه الأسهم والقائمتان.
endMonthDateلا يوجدآخر شهر تصل إليه الأسهم والقائمتان.
radius"standard" | "rounded""standard"زوايا الأيام والمدة: بنصف قطر 8px، أو دوائر كاملة.
disabledMatcher | Matcher[]لا يوجدأيام لا يمكن اختيارها: تاريخ، أو مدة، أو يوم من الأسبوع، أو دالة.
todayDateلا يوجداليوم الذي يُرسم بوصفه اليوم الحالي. والافتراضي التاريخ الحقيقي.
showOutsideDaysbooleantrueيعرض أيام الشهرين المجاورين بنص خافت.
fixedWeeksbooleantrueيعرض ستة أسابيع دائمًا، فيحافظ التقويم على ارتفاعه بين الأشهر.
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6لا يوجدأول أيام الأسبوع، من 0 للأحد إلى 6 للسبت. والافتراضي السبت في الصفحة التي تُقرأ من اليمين إلى اليسار، والأحد في غيرها.
localeDayPickerLocaleلا يوجدأسماء الأشهر والأيام والتسميات. والافتراضي العربية في الصفحة العربية، والإنجليزية في غيرها.
classNamestringلا يوجدأصناف CSS إضافية لبطاقة التقويم.

الاستخدام

يوم واحد

tsx
"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} />
}

مدة على شهرين

tsx
"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() }}
    />
  )
}

مع أوقات المواعيد

tsx
"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>
  )
}

مع اختيارات جاهزة

tsx
"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>
  )
}

قائمتا الشهر والسنة

tsx
"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}
    />
  )
}