WebberUI

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.jsonfiles[0].content)複製 year-pulse-grid.tsx 原始碼到你的 components/ui/ 目錄:

npm install motion lucide-react clsx tailwind-merge

使用

days 傳入一整年的日資料,每筆含 dateYYYY-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型別預設值說明
daysYearPulseDay[]一整年的日資料;未列出的日期補為 value 0 的空格
yearnumber首筆日期或當前年顯示年份
weekStartsOn0 | 10每週起始日:0 = 週日、1 = 週一
levelsnumber5色階等級數(含 0 級空格)
levelClassNamesstring[]翠綠色階由淺到深的色階 class,長度需與 levels 一致
getLevel(value: number, max: number) => number均分級距自訂強度值 → 色階等級的對應
cellSizenumber12單格邊長(px)
cellGapnumber3單格間距(px)
radiusnumber2單格圓角(px)
waveStaggernumber0.008載入波浪每一階的延遲係數(秒)
waveDurationnumber0.4單格點亮時長(秒)
rowWeightnumber2列方向對波次階數的加權,決定波浪的對角傾斜
animateOnLoadbooleantrue是否播放載入波浪進場
selectedDatestring | null受控:目前選取的日期
defaultSelectedDatestring | nullnull非受控初始選取日期
onSelectDate(date: string | null, day: YearPulseDay | null) => void選取變更時觸發
showSidebarbooleantrue是否顯示聯動旁欄清單
sidebarTitleReact.ReactNode"全年動態"未選取時的旁欄標題
emptyLabelstring"這一天沒有事件"該日無事件的提示文字
renderEvent(event: YearPulseEvent, day: YearPulseDay) => React.ReactNode自訂旁欄單則事件的渲染
localestring"zh-TW"Intl 語系,用於月份/星期/日期格式化
aria-labelstring"<year> 年度脈動格"最外層的無障礙標籤
classNamestring附加到最外層容器的 class
sidebarClassNamestring附加到旁欄容器的 class

YearPulseDay

欄位型別說明
datestringISO 日期(YYYY-MM-DD
valuenumber強度值,決定色階深淺;<= 0 視為空格
eventsYearPulseEvent[]當日事件清單(選填)

YearPulseEvent

欄位型別說明
idstring事件唯一 id
titlestring事件標題
detailstring次要說明(時間、分類等,選填)

動畫細節

  • 載入波浪:每格的進場延遲由 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 外框

On this page