Sieve Filter Grid
篩選沉降網格:被濾掉的項目縮小飛進觸發的篩選 chip,計數徽章同步脈衝,存留項目以 FLIP 重排收攏。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
3
<SieveFilterGrid />
安裝
npx shadcn@latest add https://webberui.com/r/sieve-filter-grid.json或在 components.json 設定 registries 後,改用 @webberui/sieve-filter-grid 安裝。
安裝依賴後,從 registry JSON(/r/sieve-filter-grid.json 的 files[0].content)複製 sieve-filter-grid.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge使用
import { SieveFilterGrid } from "@/components/ui/sieve-filter-grid";
<SieveFilterGrid
columns={3}
items={projects.map((project) => ({
id: project.id,
category: project.type,
content: <ProjectCard project={project} />,
}))}
onFilterChange={(category) => console.log(category)}
/>每個項目需帶唯一 id(動畫進出配對用)與所屬 category;篩選 chip 未指定 categories 時自動從項目推導,並各自帶計數徽章。
Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
items | SieveFilterGridItem[] | — | 網格項目,每項為 { id, category, content } |
categories | string[] | 自動推導 | 篩選分類清單,未指定時從 items 的 category 依出現順序去重推導 |
columns | number | 3 | 網格欄數 |
allLabel | string | "全部" | 「顯示全部」chip 的文字 |
onFilterChange | (category: string | null) => void | — | 篩選變更時觸發,顯示全部時帶入 null |
className | string | — | 附加到最外層容器的 class |
細節
- 因果動畫:一般篩選只是讓項目淡出淡入,看不出「誰造成的」。這裡點擊 chip 時,被濾掉的項目會縮小並飛向那顆 chip——動畫的終點就是動作的起因,視覺上直接回答「項目去哪了、為什麼」
- 飛行路徑:點擊當下以
getBoundingClientRect量測每個被濾掉項目與目標 chip 的中心偏移,exit 動畫再 animate 到該偏移並縮至 0.2 倍、淡出,如同被 chip 吸入 - 徽章脈衝:有項目飛入時,該 chip 的計數徽章在項目抵達時 scale 脈衝一下,強化「被接住」的感覺
- 記住去向:每個被濾掉的項目會記住是哪顆 chip 接住它;重新顯示時從同一顆 chip 的方向縮放飛回,來回路徑因果一致
- FLIP 收攏:離場項目以
AnimatePresence mode="popLayout"先脫離文件流,存留項目立即以 layout FLIP 平滑收攏補位,兩段動畫同時進行 - 選中 chip 的底塊以
layoutId在 chip 之間滑動
可及性
- 使用者系統開啟「減少動態效果」時,飛行、FLIP 與徽章脈衝全部停用,篩選直接切換顯示(DOM 結構與 SSR 一致)
- 篩選 chip 為原生
<button>群組(role="group"加aria-label),以aria-pressed標示選取狀態 - 鍵盤可 Tab 循序聚焦各 chip,
focus-visible時顯示外框