Data View Morph
同一資料集在清單、表格、看板、卡片四種版面間切換時,每筆項目以共享元素動畫連續變形飛至新位置,維持使用者的空間記憶。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<DataViewMorph />
安裝
npx shadcn@latest add https://webberui.com/r/data-view-morph.json或在 components.json 設定 registries 後,改用 @webberui/data-view-morph 安裝。
安裝依賴後,從 registry JSON(/r/data-view-morph.json 的 files[0].content)複製 data-view-morph.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge lucide-react使用
import { DataViewMorph, type DataViewItem } from "@/components/ui/data-view-morph";
const items: DataViewItem[] = [
{ id: "auth", title: "登入流程改版", subtitle: "OAuth", group: "doing", value: 8 },
{ id: "billing", title: "計費頁重構", subtitle: "年繳方案", group: "todo", value: 5 },
{ id: "export", title: "資料匯出", subtitle: "CSV / PDF", group: "done", value: 2 },
];
<DataViewMorph
items={items}
groups={[
{ id: "todo", label: "待辦" },
{ id: "doing", label: "進行中" },
{ id: "done", label: "已完成" },
]}
/>;四種版面共用同一份 items——切換時每筆項目以其穩定 id 為 layoutId 配對前後位置,連續飛移變形,而非淡出重繪。id 必須穩定(不要用陣列索引),共享元素動畫才能正確配對。
受控模式
不傳 layout 時元件自帶工具列並管理版面狀態。傳入 layout 即進入受控,可自訂觸發按鈕或與網址狀態同步;此時可用 showToolbar={false} 隱藏內建工具列。
const [view, setView] = React.useState<DataViewLayout>("board");
<DataViewMorph
items={items}
layout={view}
onLayoutChange={setView}
showToolbar={false}
/>;Props
DataViewMorph
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
items | DataViewItem[] | — | 資料集;每筆在四種版面間以共享元素動畫連續變形 |
layout | DataViewLayout | — | 受控版面;提供時由外部主導,停用內建狀態 |
defaultLayout | DataViewLayout | "list" | 非受控初始版面 |
onLayoutChange | (layout: DataViewLayout) => void | — | 版面切換時觸發(受控與非受控皆會呼叫) |
layouts | DataViewLayout[] | 四種全上 | 工具列顯示哪些版面及排列順序 |
groups | DataViewGroup[] | 由 items 推導 | 看板欄位順序與標題 |
showToolbar | boolean | true | 是否顯示內建版面切換工具列 |
headings | Partial<Record<"title" | "group" | "value", string>> | — | 表格三欄表頭文字覆寫 |
className | string | — | 附加到最外層容器的 class |
DataViewItem
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 穩定唯一識別碼,共享元素動畫據此配對同一筆項目 |
title | string | 主標題 |
subtitle | string | 次要說明(可選) |
group | string | 分組鍵:看板依此分欄,其餘版面顯示為分組標籤 |
value | string | number | 量化欄位(表格數值欄、卡片/看板角標,可選) |
media | React.ReactNode | 左側媒體,未提供時以標題首字佔位(可選) |
DataViewLayout
"list" | "table" | "board" | "grid"——分別對應清單、表格、看板、卡片網格。
細節
- 共享元素變形:每筆項目以穩定
id作 MotionlayoutId,切換版面時由LayoutGroup統籌,項目從舊版面的邊界框以 spring 連續飛移、縮放到新版面位置,維持使用者的空間記憶 - 抗拉伸:外框動畫尺寸,內層內容以
layout="position"只重新定位不被縮放,文字與媒體在大幅變形時不會扭曲;媒體框四版面固定同尺寸 - 看板分組:依
group分欄,欄位順序與標題由groups指定或自資料推導,欄頭顯示該欄項目數;分組色調(點/標籤)依欄位順序循環套用 - 實例隔離:
layoutId以元件useId()命名空間(LayoutGroup id),同頁多個DataViewMorph不會互相吸附 - 表格對齊:三欄採固定寬度格線,
1fr才能跨列對齊;分組與數值欄寬固定
可及性
- 內建工具列為
radiogroup,各版面按鈕為radio,支援方向鍵(←→↑↓)、Home/End 切換並移動焦點(roving tabindex) - 版面切換以
aria-live="polite"朗讀目前檢視名稱 - 資料區為語意化清單(
role="list"/listitem),看板每欄為role="group"並帶aria-label;分組色點為裝飾性元素,標記aria-hidden - 使用者系統開啟「減少動態效果」時,共享元素動畫改為瞬時完成(項目直接跳到新位置),DOM 結構與一般狀態一致