Calendar 日曆
Skies.ui Calendar:純 React 日曆,單日/範圍/活動/行程表,無需日期函式庫;API 與範例。
Calendar 日曆
四種開箱即用的日曆變種,無需任何外部日期函式庫:
Calendar— 單日選擇CalendarRange— 日期範圍選擇CalendarEvents— 全頁活動日曆CalendarAgenda— 行程表小工具
單日選擇
import { Calendar } from "@skies-ui/ui";
export function Example() {
return <Calendar />;
}受控單日選擇
"use client";
import { useState } from "react";
import { Calendar } from "@skies-ui/ui";
export function ControlledCalendar() {
const [date, setDate] = useState<Date | undefined>();
return (
<div>
<Calendar value={date} onValueChange={setDate} />
<p>{date ? date.toLocaleDateString("zh-TW") : "未選擇"}</p>
</div>
);
}日期範圍選擇
第一次點擊設定起始日,第二次點擊設定結束日,hover 時有範圍預覽。
import { CalendarRange } from "@skies-ui/ui";
import type { DateRange } from "@skies-ui/ui";
export function RangeExample() {
const [range, setRange] = useState<DateRange>({ from: undefined, to: undefined });
return <CalendarRange value={range} onValueChange={setRange} />;
}全頁活動日曆
CalendarEvents 顯示整月格狀視圖,每天格子可顯示多個帶顏色的事件條,超過 3 個會顯示「+N 個」。
import { CalendarEvents } from "@skies-ui/ui";
import type { CalendarEventItem } from "@skies-ui/ui";
const events: CalendarEventItem[] = [
{ id: "1", title: "設計評審", date: new Date(2026, 3, 3), color: "blue" },
{ id: "2", title: "前端週會", date: new Date(2026, 3, 3), color: "green" },
{ id: "3", title: "產品發布", date: new Date(2026, 3, 10), color: "red" },
];
export function EventCalendarExample() {
return (
<CalendarEvents
events={events}
onDateClick={(date) => console.log("clicked:", date)}
onEventClick={(ev) => console.log("event:", ev)}
/>
);
}行程表小工具
CalendarAgenda 列出接下來 N 天(預設 14)有行程的日期,適合放在 Dashboard 側欄。
4月10日週五
4月12日週日
4月16日週四
import { CalendarAgenda } from "@skies-ui/ui";
import type { CalendarEventItem } from "@skies-ui/ui";
const events: CalendarEventItem[] = [
{ id: "1", title: "設計評審", date: new Date(2026, 3, 7), color: "blue" },
{ id: "2", title: "產品發布", date: new Date(2026, 3, 9), color: "red" },
];
export function AgendaExample() {
return <CalendarAgenda events={events} days={14} />;
}限制可選日期
<Calendar
minDate={new Date(2026, 0, 1)}
maxDate={new Date(2026, 11, 31)}
disabledDates={[new Date(2026, 3, 1), new Date(2026, 3, 5)]}
/>自訂語言與週起始日
locale 使用 BCP 47 字串(例如 zh-TW、en-US),會影響星期縮寫、月份標題與行程表側欄日期格式。
weekStartsOn(0 = 第一欄為週日,1 = 週一)可省略;省略時在支援的環境會依 Intl.Locale 的 weekInfo 推斷地區慣例,否則為 0。
導覽按鈕的 aria-label、行程表的「今天」、空狀態與「+N 則事件」等字串,會依 locale(zh* 為繁中風格預設,其餘為英文)顯示;需要微調時傳 messages:
<Calendar locale="en-US" />
<Calendar locale="de-DE" />
{/* weekStartsOn 未指定時,多為週一起排 */}
<Calendar locale="zh-TW" weekStartsOn={1} />
<CalendarAgenda
locale="zh-TW"
messages={{
today: "今日",
noEventsInDays: (d) => `未來 ${d} 天暫無行程`,
}}
/>星期列:單字「日一二…」或跟著 locale
有星期標題列的是 Calendar、CalendarRange、CalendarEvents(行程表 CalendarAgenda 沒有這一列)。
weekdayStyle | 行為 |
|---|---|
locale(預設) | 與 locale 相同,使用 Intl 的星期縮寫(如 zh-TW 可能是「週一…」這類格式,依引擎而定)。 |
zh-abbr | 固定顯示單字 日、一、二、三、四、五、六;欄順與 weekStartsOn 一致(週日開頭 → 日一二三四五六;週一開頭 → 一二三四五六日)。 |
即使 locale 是 en-US,仍可設 weekdayStyle="zh-abbr" 讓表頭用中文單字。
<Calendar weekdayStyle="zh-abbr" />
<CalendarRange weekdayStyle="zh-abbr" weekStartsOn={1} />Next.js 與 SSR
為什麼要注意: App Router 會先在伺服器產出 HTML,再在瀏覽器做 hydration。若伺服器算出的 locale 和瀏覽器不一致(例如伺服器用預設 zh-TW、客戶端才讀 navigator.language 變成 en-US),星期縮寫等文字可能和首屏 HTML 對不起來,React 會警告 hydration mismatch。
建議做法:
- 讓「語系」在伺服器與客戶端都能推出同一個值——常見來源:
/[locale]/...動態路由的params.locale、cookie(伺服器在cookies()、客戶端讀同一個名稱)、或 layout 從設定注入的常數。 navigator僅在瀏覽器存在,在 Server Component 裡沒有navigator.language;不要只在useEffect裡才第一次設定locale並改畫面,否則首屏與 hydration 仍可能不一致。若語系只存在客戶端,可整塊日曆放在 Client Component,但locale仍應來自與首屏一致的來源(例如已由 middleware 寫入 cookie、再在同一份 props 傳入)。
App Router 範例(語系來自路由): 假設頁面路徑為 app/[locale]/page.tsx,把 locale 往下傳給標示了 "use client" 的元件即可;伺服器與客戶端第一次 render 都會拿到同一個 params.locale。
// app/[locale]/calendar-section.tsx
"use client";
import { Calendar } from "@skies-ui/ui";
export function CalendarSection({ locale }: { locale: string }) {
return <Calendar locale={locale} />;
}// app/[locale]/page.tsx(Server Component 範例)
import { CalendarSection } from "./calendar-section";
export default async function Page({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return <CalendarSection locale={locale} />;
}關於 Intl: 星期與月份字樣由執行環境的 ICU 決定,本機 Node 與使用者瀏覽器版本不同時,同一 locale 也可能有極小差異,一般可接受。
API 摘要
Calendar / CalendarRange
| Prop | 型別 | 說明 |
|---|---|---|
value | Date | DateRange | 受控選取值 |
onValueChange | fn | 變更回呼 |
defaultValue | Date | DateRange | 非受控預設值 |
minDate | Date | 最早可選日期 |
maxDate | Date | 最晚可選日期 |
disabledDates | Date[] | 指定停用的日期 |
locale | string | 語言(預設 "zh-TW") |
weekStartsOn | 0 | 1 | 選填;週起始。省略時依 locale 自動推斷(見上文) |
weekdayStyle | CalendarWeekdayStyle | 選填;星期列用 Intl(locale)或單字日一二…(zh-abbr) |
messages | Partial<CalendarMessages> | 選填;覆寫導覽 aria-label 等文案 |
CalendarEvents
| Prop | 型別 | 說明 |
|---|---|---|
events | CalendarEventItem[] | 事件清單 |
onEventClick | (ev: CalendarEventItem) => void | 點擊事件回呼 |
onDateClick | (date: Date) => void | 點擊日期格回呼 |
locale | string | 語言 |
weekStartsOn | 0 | 1 | 選填;週起始(省略時自動推斷) |
weekdayStyle | CalendarWeekdayStyle | 選填;同上 |
messages | Partial<CalendarMessages> | 選填 |
CalendarAgenda
| Prop | 型別 | 說明 |
|---|---|---|
events | CalendarEventItem[] | 事件清單 |
days | number | 顯示未來幾天(預設 14) |
onEventClick | (ev: CalendarEventItem) => void | 點擊事件回呼 |
locale | string | 語言 |
messages | Partial<CalendarMessages> | 選填 |
CalendarMessages(摘要)
| 欄位 | 說明 |
|---|---|
previousMonth / nextMonth | 上/下月按鈕 aria-label |
today | 行程表「今天」 |
noEventsInDays(days) | 無行程時的空狀態 |
moreEvents(count) | 活動格內「+N …」 |
亦匯出 defaultCalendarMessages(locale)、getDefaultWeekStartsOn(locale) 供進階用法或測試。
CalendarWeekdayStyle
| 值 | 說明 |
|---|---|
locale | 預設;星期列依 locale 走 Intl。 |
zh-abbr | 星期列為 日一二三四五六(順序依 weekStartsOn)。 |
CalendarEventItem
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 唯一識別碼 |
title | string | 事件標題 |
date | Date | 事件日期(或起始日) |
endDate | Date | 選用,多日事件的結束日 |
color | "default" | "blue" | "green" | "red" | "orange" | "purple" | 事件顏色 |