WebberUI

Column Transit Board

跨欄轉運看板:卡片因程式狀態改變(非拖曳)沿弧線飛越欄位,目標欄先撐開落點、原欄鄰卡合攏、飛行卡帶投影抬升。

載入預覽⋯

Playground

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

0.6
<ColumnTransitBoard />

安裝

npx shadcn@latest add https://webberui.com/r/column-transit-board.json

或在 components.json 設定 registries 後,改用 @webberui/column-transit-board 安裝。

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

npm install motion clsx tailwind-merge

飛行卡片的落點光暈使用 var(--wb-duration-fast,200ms) 作為調校 fallback,未定義該變數也能正常運作。

使用

卡片的欄位歸屬完全由你的狀態決定——把某張 TransitCard 在 JSX 中改放到另一個 TransitColumn,看板就會偵測到換欄並播放飛行動畫。這是純受控元件,不持有任何內部佈局狀態。

import * as React from "react";
import {
  ColumnTransitBoard,
  TransitColumn,
  TransitCard,
} from "@/components/ui/column-transit-board";

const COLUMNS = [
  { id: "todo", title: "待處理" },
  { id: "doing", title: "進行中" },
  { id: "done", title: "已完成" },
];

const [placement, setPlacement] = React.useState<Record<string, string>>({
  a: "todo",
  b: "doing",
});

<ColumnTransitBoard>
  {COLUMNS.map((col) => (
    <TransitColumn key={col.id} id={col.id} title={col.title}>
      {Object.entries(placement)
        .filter(([, colId]) => colId === col.id)
        .map(([cardId]) => (
          <TransitCard key={cardId} id={cardId} title={cardId} />
        ))}
    </TransitColumn>
  ))}
</ColumnTransitBoard>;

// 換欄:改變狀態即觸發飛行
setPlacement((prev) => ({ ...prev, a: "doing" }));

Props

ColumnTransitBoard

Prop型別預設值說明
childrenReact.ReactNode直接放入 TransitColumn 子元件,欄內再放 TransitCard
flightDurationnumber0.6卡片跨欄飛行的弧線時長(秒)
classNamestring附加到看板容器的 class

TransitColumn

Prop型別預設值說明
idstring欄位唯一識別 key,卡片換欄偵測與落點光暈定位用
titleReact.ReactNode欄位標題(字串時同時作為 aria-label
accentAccent"neutral"欄位強調色,套用到標記點與落點光暈
childrenReact.ReactNode直接放入 TransitCard 子元件
classNamestring附加到欄位容器的 class

TransitCard

Prop型別預設值說明
idstring卡片唯一識別 key,須全板唯一且穩定(跨欄 FLIP 靠它配對)
titleReact.ReactNode卡片標題(未提供 children 時的預設內容)
descriptionReact.ReactNode卡片描述(未提供 children 時的預設內容)
accentAccent"neutral"卡片強調色,套用到左側色條
childrenReact.ReactNode自訂卡片內容,提供時覆蓋 titledescription
classNamestring附加到卡片的 class

Accent"neutral" | "blue" | "violet" | "sky" | "emerald" | "amber" | "rose"

細節

  • id 追蹤,不靠 DOM 節點:卡片換欄時 React 會在來源欄卸載、目標欄重掛,看板以 id 為 key 記錄換欄前後的位置(FLIP),因此重掛也能無縫接上飛行。
  • 只有結構改變才重排:看板為每次渲染算出「欄位 → 卡片」的佈局指紋,指紋不變時完全不量測、不干擾進行中的動畫。
  • 三段編排:換欄卡片沿拋物弧線飛越並抬升投影(z-index 浮到最上層);同欄鄰卡以較快的 spring 直線合攏補位,讓目標欄落點「先撐開」;目標欄同時浮現一圈落點光暈。
  • 飛行途中若再次換欄:前一段飛行會即時收束到落點,再從新起點重新編排,狀態保持一致。

可及性

  • 使用者系統開啟「減少動態效果」時,卡片直接就位、不播放飛行,佈局結構不變。
  • 看板為 role="group",每欄為 role="list"(以標題作 aria-label),卡片為 role="listitem"
  • 落點光暈為純裝飾層(aria-hiddenpointer-events-none),不影響佈局量測與互動。

On this page