WebberUI

Mutation List Choreographer

完整 CRUD 列表編排:新增撐開間隙落入、刪除鄰項合攏、排序 FLIP 連續移動、批次級聯波次,並附 undo 幽靈項。

載入預覽⋯

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

4000
70
<MutationListChoreographer />

安裝

npx shadcn@latest add https://webberui.com/r/mutation-list-choreographer.json

或在 components.json 設定 registries 後,改用 @webberui/mutation-list-choreographer 安裝。

安裝依賴後,從 registry JSON(/r/mutation-list-choreographer.jsonfiles[0].content)複製 mutation-list-choreographer.tsx 原始碼到你的 components/ui/ 目錄:

npm install motion lucide-react clsx tailwind-merge

使用

元件為非受控:以 defaultItems 給定初始資料,透過 ref 取得命令式把手(MutationListHandle)從任意位置驅動增刪改排與復原。

import * as React from "react";
import {
  MutationListChoreographer,
  type MutationListHandle,
} from "@/components/ui/mutation-list-choreographer";

interface Task {
  id: string;
  title: string;
}

export function Example() {
  const listRef = React.useRef<MutationListHandle<Task>>(null);

  return (
    <>
      <button
        onClick={() =>
          listRef.current?.add({ id: crypto.randomUUID(), title: "新任務" })
        }
      >
        新增
      </button>
      <MutationListChoreographer
        ref={listRef}
        defaultItems={[{ id: "1", title: "校對發佈稿" }]}
        getKey={(task) => task.id}
        renderItem={(task) => <span>{task.title}</span>}
      />
    </>
  );
}

Props

Prop型別預設值說明
defaultItemsT[][]初始項目(非受控)
getKey(item: T) => string從項目取得穩定唯一鍵
renderItem(item: T) => ReactNode渲染單一項目內容
onItemsChange(items: T[]) => void有效(非幽靈)項目變動時的回呼
undoTimeoutnumber4000幽靈項可復原的時間窗(毫秒)
waveIntervalnumber70批次級聯每項的波次間隔(毫秒)
removedLabelstring"已移除"幽靈項文字
undoLabelstring"復原"復原按鈕文字
emptyStateReactNode列表為空時顯示的內容
refRef<MutationListHandle<T>>命令式操作把手
itemClassNamestring追加到每個項目容器的 className

MutationListHandle

透過 ref.current 取得的操作方法:

方法簽名說明
add(item: T, index?: number) => void新增項目,先撐開間隙再讓項目落入
remove(key: string) => void以 undo 幽靈項軟刪除單一項目
removeMany(keys: string[]) => void批次軟刪除,幽靈項與收合以級聯波次執行
update(key: string, updater: (item: T) => T) => void就地更新項目內容
move(from: number, to: number) => void依索引搬移,位移以 FLIP 呈現
sort(compare: (a: T, b: T) => number) => void重新排序,位移以 FLIP 呈現
shuffle() => void隨機洗牌
undo() => void復原最近一次刪除
getItems() => T[]讀取目前有效項目

細節

  • 新增:外層列以高度 0 → auto 撐開間隙,內層內容延遲 0.08s 才位移落入,形成「先開縫、後落定」的節奏。
  • 刪除:以 undoTimeout 為時間窗顯示幽靈項覆蓋原列並提供復原按鈕與倒數進度條;逾時才真正卸載,鄰項隨高度收合合攏補位。
  • 排序 / 搬移 / 洗牌:僅重排底層陣列,位置變化交由 Motion 的 layout(FLIP)連續移動。
  • 批次removeMany 依傳入順序給每項一個波次序號,幽靈項淡入、倒數進度與最終收合皆按 waveInterval 級聯錯開。

可及性

  • 列表以 role="list" 標記,每次增刪改與復原透過 aria-live="polite" 區域即時播報。
  • 幽靈項的復原鍵、示範中的完成切換與移除鍵皆為原生 button,可鍵盤聚焦操作並帶 focus-visible 外框。
  • 使用者系統開啟「減少動態效果」時,撐開、落入、FLIP、級聯與倒數皆停用,僅保留即時的狀態切換,DOM 結構不變。

On this page