WebberUI

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.jsonfiles[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型別預設值說明
editingboolean受控的編輯模式狀態;不傳時由元件內部管理
onEditingChange(editing: boolean) => void編輯模式變更時觸發(點網格空白處或按 Esc 結束編輯時也會呼叫)
childrenReact.ReactNode直接放入 Widget / WidgetStack 子元件
classNamestring附加到板面容器的 class(容器同時是容器查詢基準,避免在此加 padding)

Widget

Prop型別預設值說明
idstring—(必填)唯一識別 key:移除/加回配對與 wiggle 相位計算用
size'sm' | 'md' | 'lg''sm'尺寸階級:sm 1×1、md 2×1、lg 2×2
labelstringid顯示名稱:用於移除鈕 aria-label 與「已移除」托盤 chip
childrenReact.ReactNode卡片內容:預設無內距,可自行做滿版圖片或加 padding
classNamestring附加到單一 widget 外框的 class

WidgetStack

Prop型別預設值說明
idstring—(必填)唯一識別 key:移除/加回配對與 wiggle 相位計算用
size'sm' | 'md' | 'lg''sm'尺寸階級:sm 1×1、md 2×1、lg 2×2
intervalnumber4自動翻頁間隔(秒)
labelstringid顯示名稱:用於移除鈕 aria-label 與「已移除」托盤 chip
childrenReact.ReactNode多個內容節點,每個直接子節點是一頁
classNamestring附加到堆疊外框的 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 互不干擾
  • 移除與補位:編輯模式左上角浮出「−」鈕(原生 buttonaria-label 為「移除+label」);點擊後由 AnimatePresence popLayout 讓該 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 深淺色自適應,建議放在有色「桌布」背景上呈現效果最佳

On this page