Agenda Tracks Grid
時間 × 軌道的會議議程網格:時間軸欄凍結於左側,切換軌道或講者篩選時場次卡以 FLIP 重排補位,點擊場次原位展開講者與摘要。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
240
68
152
<AgendaTracksGrid />
安裝
npx shadcn@latest add https://webberui.com/r/agenda-tracks-grid.json或在 components.json 設定 registries 後,改用 @webberui/agenda-tracks-grid 安裝。
安裝依賴後,從 registry JSON(/r/agenda-tracks-grid.json 的 files[0].content)複製 agenda-tracks-grid.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion lucide-react clsx tailwind-merge使用
軌道以 tracks 傳入(決定欄),場次以 sessions 傳入。每個 trackId 與 start 的交會至多對應一場。工具列的軌道開關與講者篩選由元件內建管理,也可透過受控 props 由外部主導。
import {
AgendaTracksGrid,
type AgendaTrack,
type AgendaSession,
} from "@/components/ui/agenda-tracks-grid";
const tracks: AgendaTrack[] = [
{ id: "keynote", label: "主舞台" },
{ id: "workshop", label: "工作坊" },
];
const sessions: AgendaSession[] = [
{
id: "s1",
trackId: "keynote",
start: "09:00",
title: "開場:動畫優先的介面",
speaker: "林安琪",
summary: "從第一性原理談動效如何承載資訊層級。",
},
{
id: "s2",
trackId: "workshop",
start: "09:00",
title: "手把手:FLIP 補位實作",
speaker: "周子軒",
summary: "以 layout 動畫做出切換即補位的網格重排。",
},
];
<AgendaTracksGrid tracks={tracks} sessions={sessions} defaultExpandedId="s1" />Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
tracks | AgendaTrack[] | — | 軌道清單,決定欄的內容與由左至右順序 |
sessions | AgendaSession[] | — | 場次清單,每個 track + time 交會至多一場 |
times | string[] | — | 時間列順序;省略時自動由場次彙整並字典序排序 |
activeTracks | string[] | — | 受控:目前顯示的軌道 id 陣列 |
defaultActiveTracks | string[] | 全部 | 非受控初始顯示的軌道 |
onActiveTracksChange | (ids: string[]) => void | — | 顯示軌道變更時觸發 |
speaker | string | null | — | 受控:講者篩選(null 代表全部) |
defaultSpeaker | string | null | null | 非受控初始講者篩選 |
onSpeakerChange | (speaker: string | null) => void | — | 講者篩選變更時觸發 |
expandedId | string | null | — | 受控:目前展開的場次 id |
defaultExpandedId | string | null | null | 非受控初始展開的場次 |
onExpandedChange | (id: string | null) => void | — | 展開場次變更時觸發 |
showControls | boolean | true | 是否顯示內建的軌道/講者篩選工具列 |
maxHeight | number | 360 | 捲動容器最大高度(px) |
timeColumnWidth | number | 68 | 時間軸欄寬度(px) |
trackMinWidth | number | 152 | 每條軌道的最小欄寬(px),欄過多時觸發水平捲動 |
className | string | — | 附加到最外層容器的 class |
AgendaTrack
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 軌道唯一 id,對應 AgendaSession.trackId |
label | string | 軌道顯示名稱(欄首) |
AgendaSession
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 場次唯一 id |
trackId | string | 所屬軌道 id |
start | string | 起始時間標籤,需對應某個時間列 |
title | string | 場次標題 |
speaker | string | 講者姓名(供篩選與展開顯示) |
summary | React.ReactNode | 展開後顯示的摘要內容 |
動畫細節
- 凍結表頭:時間軸欄以
sticky left固定於左側,軌道欄首以sticky top固定於頂部,左上角落交會處疊在最上層;欄數超出容器寬度時水平捲動,時間軸欄始終可見。 - FLIP 重排補位:切換軌道會移除整欄,或以講者篩選時自動收合沒有對應場次的軌道;離場的場次卡以
popLayout凍結原位淡出,其餘卡片以layout="position"平滑補位,避免文字縮放變形。 - 原位展開:點擊場次卡就地展開講者與摘要,該時間列高度隨之增長,下方列以相同 spring 推移補位;再次點擊或點其他卡片收合(單一展開)。
- 講者篩選:非符合的場次卡淡出為停用狀態,仍保留時間對齊的脈絡。
可及性
- 使用者系統開啟「減少動態效果」時,停用 FLIP、進退場與展開動畫,切換即定位;DOM 結構不變,掛載後才切換避免 hydration 落差
- 網格採
role="grid"搭配aria-rowcount/aria-colcount,列與格分別標記columnheader、rowheader、gridcell與對應索引 - 軌道開關為
aria-pressed切換鈕;講者篩選為role="radiogroup"單選;場次卡為帶aria-expanded/aria-controls的按鈕,展開內容以role="region"標記 - 所有裝飾性圖示與佔位符皆標記
aria-hidden,可鍵盤聚焦並具focus-visible外框