Year Pulse Grid
GitHub 風的整年日格牆:載入時波浪掃過依強度點亮色階,hover 單日浮出事件卡,點擊日格選取並聯動旁欄清單過濾當日事件。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
12
3
2
0.008
<YearPulseGrid />
安裝
npx shadcn@latest add https://webberui.com/r/year-pulse-grid.json或在 components.json 設定 registries 後,改用 @webberui/year-pulse-grid 安裝。
安裝依賴後,從 registry JSON(/r/year-pulse-grid.json 的 files[0].content)複製 year-pulse-grid.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion lucide-react clsx tailwind-merge使用
以 days 傳入一整年的日資料,每筆含 date(YYYY-MM-DD)、value(強度)與選填的 events。未列出的日期自動補為空格。選取的日期可由內建旁欄管理,也能透過 selectedDate / onSelectDate 受控主導。
import {
YearPulseGrid,
type YearPulseDay,
} from "@/components/ui/year-pulse-grid";
const days: YearPulseDay[] = [
{ date: "2025-01-06", value: 6, events: [{ id: "e1", title: "v1.0 發佈" }] },
{ date: "2025-01-07", value: 2 },
{ date: "2025-01-08", value: 0 },
// …一整年
];
<YearPulseGrid days={days} year={2025} weekStartsOn={1} />Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
days | YearPulseDay[] | — | 一整年的日資料;未列出的日期補為 value 0 的空格 |
year | number | 首筆日期或當前年 | 顯示年份 |
weekStartsOn | 0 | 1 | 0 | 每週起始日:0 = 週日、1 = 週一 |
levels | number | 5 | 色階等級數(含 0 級空格) |
levelClassNames | string[] | 翠綠色階 | 由淺到深的色階 class,長度需與 levels 一致 |
getLevel | (value: number, max: number) => number | 均分級距 | 自訂強度值 → 色階等級的對應 |
cellSize | number | 12 | 單格邊長(px) |
cellGap | number | 3 | 單格間距(px) |
radius | number | 2 | 單格圓角(px) |
waveStagger | number | 0.008 | 載入波浪每一階的延遲係數(秒) |
waveDuration | number | 0.4 | 單格點亮時長(秒) |
rowWeight | number | 2 | 列方向對波次階數的加權,決定波浪的對角傾斜 |
animateOnLoad | boolean | true | 是否播放載入波浪進場 |
selectedDate | string | null | — | 受控:目前選取的日期 |
defaultSelectedDate | string | null | null | 非受控初始選取日期 |
onSelectDate | (date: string | null, day: YearPulseDay | null) => void | — | 選取變更時觸發 |
showSidebar | boolean | true | 是否顯示聯動旁欄清單 |
sidebarTitle | React.ReactNode | "全年動態" | 未選取時的旁欄標題 |
emptyLabel | string | "這一天沒有事件" | 該日無事件的提示文字 |
renderEvent | (event: YearPulseEvent, day: YearPulseDay) => React.ReactNode | — | 自訂旁欄單則事件的渲染 |
locale | string | "zh-TW" | Intl 語系,用於月份/星期/日期格式化 |
aria-label | string | "<year> 年度脈動格" | 最外層的無障礙標籤 |
className | string | — | 附加到最外層容器的 class |
sidebarClassName | string | — | 附加到旁欄容器的 class |
YearPulseDay
| 欄位 | 型別 | 說明 |
|---|---|---|
date | string | ISO 日期(YYYY-MM-DD) |
value | number | 強度值,決定色階深淺;<= 0 視為空格 |
events | YearPulseEvent[] | 當日事件清單(選填) |
YearPulseEvent
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 事件唯一 id |
title | string | 事件標題 |
detail | string | 次要說明(時間、分類等,選填) |
動畫細節
- 載入波浪:每格的進場延遲由
col + row × rowWeight決定,波浪自左上向右下對角掃過,各格由縮小淡入放大到強度色。以waveStagger調速、waveDuration調單格時長;設animateOnLoad={false}可直接以最終狀態呈現。 - 浮出事件卡:hover 或鍵盤聚焦單日,事件卡自該格上方(近頂端時改為下方)淡入放大浮出,顯示日期、強度與前幾則事件;卡片標記
pointer-events-none且座標依即時getBoundingClientRect計算,水平捲動亦定位正確。 - 選取聯動:點擊日格切換選取(再次點擊清除),選取格浮起並套上焦點環,旁欄同步過濾為當日事件;未選取時旁欄列出全年事件,點清單項可回選對應日期。
可及性
- 使用者系統開啟「減少動態效果」時,波浪與浮出動畫即時定位(僅停用動畫參數,DOM 結構不變)
- 格牆為
role="group"具aria-label;每個日格為帶完整日期/強度/事件數aria-label的按鈕,並以aria-pressed標記選取狀態 - 採 roving tabindex:方向鍵於格間巡覽、
Home/End跳至年首/年尾、Enter/Space選取、Esc清除選取與事件卡 - 旁欄標題以
aria-live="polite"播報選取變更;月份、星期標籤與裝飾圓點皆標記aria-hidden,所有可聚焦元素具focus-visible外框