Widget Board
iOS 桌面 widget 網格:sm/md/lg 尺寸階級毛玻璃卡,編輯模式全體 wiggle 搖晃可移除,Smart Stack 定時輪播翻頁。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
4
<WidgetBoard />
安裝
npx shadcn@latest add https://webberui.com/r/widget-board.json安裝時會一併寫入 --wb-duration-fast 與 --wb-ease-out CSS 變數到你的全域樣式。
安裝依賴後,從 registry JSON(/r/widget-board.json 的 files[0].content)複製 widget-board.tsx 原始碼到你的 components/ui/ 目錄,並在全域 CSS 加入:
npm install motion clsx tailwind-merge:root {
--wb-duration-fast: 200ms;
--wb-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}wiggle 的 keyframes 由元件內嵌 <style> 注入,不需額外設定;格位尺寸以容器查詢(cqw)計算,需 Tailwind v4。
使用
import { WidgetBoard, Widget, WidgetStack } from "@/components/ui/widget-board";
const [editing, setEditing] = React.useState(false);
<WidgetBoard editing={editing} onEditingChange={setEditing}>
<Widget id="clock" size="sm" label="時鐘">
<Clock />
</Widget>
<Widget id="weather" size="md" label="天氣">
<Weather />
</Widget>
<Widget id="music" size="lg" label="音樂">
<MusicPlayer />
</Widget>
<WidgetStack id="glance" size="sm" label="智慧堆疊" interval={4}>
<CalendarGlance />
<Reminders />
</WidgetStack>
</WidgetBoard>Props
WidgetBoard
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
editing | boolean | — | 受控的編輯模式狀態;不傳時由元件內部管理 |
onEditingChange | (editing: boolean) => void | — | 編輯模式變更時觸發(點網格空白處或按 Esc 結束編輯時也會呼叫) |
children | React.ReactNode | — | 直接放入 Widget / WidgetStack 子元件 |
className | string | — | 附加到板面容器的 class(容器同時是容器查詢基準,避免在此加 padding) |
Widget
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | —(必填) | 唯一識別 key:移除/加回配對與 wiggle 相位計算用 |
size | 'sm' | 'md' | 'lg' | 'sm' | 尺寸階級:sm 1×1、md 2×1、lg 2×2 |
label | string | id | 顯示名稱:用於移除鈕 aria-label 與「已移除」托盤 chip |
children | React.ReactNode | — | 卡片內容:預設無內距,可自行做滿版圖片或加 padding |
className | string | — | 附加到單一 widget 外框的 class |
WidgetStack
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | —(必填) | 唯一識別 key:移除/加回配對與 wiggle 相位計算用 |
size | 'sm' | 'md' | 'lg' | 'sm' | 尺寸階級:sm 1×1、md 2×1、lg 2×2 |
interval | number | 4 | 自動翻頁間隔(秒) |
label | string | id | 顯示名稱:用於移除鈕 aria-label 與「已移除」托盤 chip |
children | React.ReactNode | — | 多個內容節點,每個直接子節點是一頁 |
className | string | — | 附加到堆疊外框的 class |
細節
- 正方形格位:容器寬 ≥ 24rem 時 4 欄、否則 2 欄;row 高以容器查詢(
100cqw)換算成欄寬,格位恆為 1:1 正方形,md 與 lg 自然對應 iOS 的 2×1、2×2 比例。grid-flow-row-dense讓 md/lg 造成的空洞由後面的小 widget 自動補上 - wiggle 搖晃:編輯模式時以 CSS keyframes
rotate ±1.2deg無限循環;每個 widget 以id雜湊出負的animation-delay錯開相位,不會整片同步搖。相位由雜湊而非Math.random()產生,SSR 與客戶端輸出一致 - 圖層分離:外層
motion.div負責 FLIP 補位與縮小退場、中層負責加回的飛行動畫、內層跑 CSS wiggle,三者的 transform 互不干擾 - 移除與補位:編輯模式左上角浮出「−」鈕(原生
button,aria-label為「移除+label」);點擊後由AnimatePresencepopLayout 讓該 widget 原地縮小淡出,其餘 widget 以 spring FLIP 補位 - 已移除托盤:板面底部列出被移除的 widget,點擊 chip 加回;點擊當下量測 chip 與新格位的中心距離,widget 從托盤位置放大飛回網格
- Smart Stack:每
interval秒垂直滑動翻頁,右緣小圓點指示當前頁;計時器在 unmount 時清理,編輯模式下自動暫停輪播 - 結束編輯:點網格空白處或按 Esc 即結束編輯模式,受控模式下透過
onEditingChange通知外部 - 減少動態效果:使用者系統開啟「減少動態效果」時,wiggle 停用(JS 與 CSS
prefers-reduced-motion雙保險)、FLIP 與飛行停用、Smart Stack 輪播改為瞬間切換,功能完全不受影響 - 毛玻璃卡片:
backdrop-blur深淺色自適應,建議放在有色「桌布」背景上呈現效果最佳