Archive Index
工作室檔案館式的全頁索引表格,多欄可排序並以 FLIP 重排,點列展開成滿版詳情,其餘列彈性讓位下沉。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<ArchiveIndex />
安裝
npx shadcn@latest add https://webberui.com/r/archive-index.json或在 components.json 設定 registries 後,改用 @webberui/archive-index 安裝。
安裝依賴後,從 registry JSON(/r/archive-index.json 的 files[0].content)複製 archive-index.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion lucide-react clsx tailwind-merge調校點用到的 --wb-duration-fast、--wb-ease-out 有內建 fallback,未設定也可運作;如需自訂可在全域 CSS 加入:
:root {
--wb-duration-fast: 200ms;
--wb-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}使用
import {
ArchiveIndex,
type ArchiveColumn,
type ArchiveEntry,
} from "@/components/ui/archive-index";
const columns: ArchiveColumn[] = [
{ key: "year", header: "年份", width: "4rem", align: "right" },
{ key: "project", header: "專案", width: "minmax(0,1.6fr)" },
{ key: "category", header: "類別" },
];
const entries: ArchiveEntry[] = [
{
id: "aperture",
label: "Aperture 識別系統",
fields: { year: "2024", project: "Aperture 識別系統", category: "品牌" },
detail: <p>為光學儀器製造商打造的完整視覺識別。</p>,
},
// ...
];
<ArchiveIndex columns={columns} entries={entries} />排序與展開皆支援受控/非受控雙模式。傳入 sort / expandedId 即進入受控模式,並透過 onSortChange / onExpandedChange 回寫狀態:
const [sort, setSort] = React.useState<ArchiveSort | null>({
key: "year",
direction: "desc",
});
<ArchiveIndex
columns={columns}
entries={entries}
sort={sort}
onSortChange={setSort}
/>Props
ArchiveIndex
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
columns | ArchiveColumn[] | — | 欄位定義(年份/專案/類別等多欄) |
entries | ArchiveEntry[] | — | 資料列 |
expandedId | string | null | — | 受控的展開列 id;傳入即進入受控模式 |
defaultExpandedId | string | null | null | 非受控模式的初始展開列 id |
onExpandedChange | (id: string | null) => void | — | 展開列變更時觸發(收合時為 null) |
sort | ArchiveSort | null | — | 受控的排序狀態;傳入即進入受控模式 |
defaultSort | ArchiveSort | null | null | 非受控模式的初始排序狀態 |
onSortChange | (sort: ArchiveSort | null) => void | — | 排序變更時觸發(清除排序時為 null) |
renderDetail | (entry: ArchiveEntry) => React.ReactNode | — | 以 render prop 產生詳情內容(優先於 entry.detail) |
stickyHeader | boolean | true | 表頭是否黏著於捲動容器頂端 |
className | string | — | 附加到最外層容器的 class |
ArchiveColumn
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
key | string | — | 欄位唯一 key,對應 ArchiveEntry.fields 的鍵 |
header | React.ReactNode | — | 表頭顯示內容 |
sortable | boolean | true | 此欄是否可排序 |
align | "left" | "right" | "left" | 儲存格對齊方向 |
width | string | "minmax(0,1fr)" | CSS grid 軌道寬度,如 "6rem"、"minmax(0,2fr)" |
className | string | — | 附加在此欄儲存格上的 class |
ArchiveEntry
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | — | 資料列唯一識別,用於受控展開、FLIP 配對與 React key |
fields | Record<string, React.ReactNode> | — | 各欄位顯示值,key 對應 ArchiveColumn.key |
sortValues | Record<string, string | number> | — | 各欄位排序值;未提供時退回以 fields 的字串/數字值排序 |
detail | React.ReactNode | — | 展開後的滿版詳情內容;未提供則此列不可展開 |
label | string | — | 無障礙用的列名稱,套用於展開鈕 aria-label(未提供時退回 id) |
細節
- 排序循環:點同一欄表頭依序切換
asc → desc → 清除排序;清除後回到entries原始順序。排序採穩定排序,不會更動傳入的陣列。 - FLIP 重排:排序改變時,各列以
layout="position"走 FLIP 動畫平滑換位,帶輕微回彈的 spring。 - 展開讓位:點某列時該列詳情以高度 spring 撐開成滿版區塊,下方列順著流式版面被彈性推移下沉;同時其餘列淡淡降暗以聚焦目前列。
- 排序值:數字對數字採數值比較,其餘以 numeric 感知的
localeCompare,故"2019"與"2021"或年份數字都能正確排序。若顯示值非純文字(如帶樣式的節點),請用sortValues提供可比較的值。
可及性
- 表頭排序控制為原生
<button>,可鍵盤聚焦與操作,並帶aria-label標示排序欄位。 - 可展開列為原生
<button>,帶aria-expanded與aria-controls指向詳情區塊;詳情區塊為role="region"並帶aria-label。 - 使用者系統開啟「減少動態效果」時,停用 FLIP、高度與降暗動畫,直接切換展開/收合狀態,DOM 結構維持一致。