便利貼牆
軟木板上的彩色便利貼:拖曳、新增、揉掉刪除。
一面 CSS 紋理軟木板,貼滿帶著微旋轉與膠帶/圖釘裝飾的彩色便利貼:可自由拖曳移動、雙擊編輯文字、按下 ✕ 便揉成一團紙球丟出,右下角一鍵新增自動配色的新便利貼,適合待辦牆、腦力激盪板等趣味場景。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add "https://webberui.com/r/sticky-note-board.json?t=<安裝 token>"Sticky Note Board 是用 React、Motion 與 Tailwind CSS 做的便利貼牆(便條紙看板)元件:便利貼可拖曳、雙擊編輯、新增與刪除,每次異動都會經 onChange 回傳完整陣列,方便你自行儲存。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<StickyNoteBoard/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- 團隊工具裡的腦力激盪板、回顧會議(retro)看板
- 個人儀表板上的待辦或備忘區
- 活動頁、課程平台讓使用者留言的互動牆
- 產品導覽或作品集中,用來展示「可以動手玩」的互動區塊
- 不適合:需要欄位分組與排序的工作流程,例如依狀態分欄的任務看板,請改用專門的看板或清單元件
安裝
npx webberui@latest add "https://webberui.com/r/sticky-note-board.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/sticky-note-board 安裝。
使用
import { StickyNoteBoard, type StickyNote } from "@/components/ui/sticky-note-board";
const notes: StickyNote[] = [
{ id: "milk", text: "下班買牛奶 🥛", x: 24, y: 22, color: 0 },
{ id: "gym", text: "週三 19:00 健身房", x: 200, y: 120, color: 2 },
];
<StickyNoteBoard notes={notes} height={400} onChange={(next) => console.log(next)} />範例
存到 localStorage
notes 只作為初始資料,所以先在 client 讀出儲存的內容,再渲染看板;之後每次 onChange 都寫回去。
"use client";
import * as React from "react";
import { StickyNoteBoard, type StickyNote } from "@/components/ui/sticky-note-board";
const KEY = "my-board-notes";
export function PersistentBoard() {
const [initial, setInitial] = React.useState<StickyNote[] | null>(null);
React.useEffect(() => {
const saved = localStorage.getItem(KEY);
setInitial(saved ? (JSON.parse(saved) as StickyNote[]) : []);
}, []);
if (!initial) return null;
return (
<StickyNoteBoard
notes={initial}
onChange={(next) => localStorage.setItem(KEY, JSON.stringify(next))}
/>
);
}同步到後端
拖曳結束、編輯提交、新增或刪除都會觸發 onChange,可以直接把最新陣列送到 API。
<StickyNoteBoard
notes={notesFromServer}
height={480}
addLabel="新增想法"
onChange={(next) => {
void fetch("/api/boards/retro-42", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ notes: next }),
});
}}
/>固定顏色、隱藏新增按鈕
用 color 指定色票索引(0–5 依序為黃、粉、綠、藍、橘、紫),並以 showAdd={false} 隱藏新增按鈕,只讓使用者整理既有的便利貼。
const notes: StickyNote[] = [
{ id: "bug", text: "登入頁在 Safari 跑版", x: 24, y: 24, color: 1 },
{ id: "idea", text: "結帳頁加上進度條", x: 200, y: 40, color: 2 },
{ id: "todo", text: "週五前更新文件", x: 110, y: 170, color: 3 },
];
<StickyNoteBoard notes={notes} showAdd={false} height={320} />Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
notes | StickyNote[] | [] | 初始便利貼資料;之後由元件內部管理,異動經 onChange 回報 |
onChange | (notes: StickyNote[]) => void | — | 拖曳、編輯、新增、揉掉任一異動後,回傳最新的便利貼陣列 |
height | number | 380 | 軟木板高度(px) |
addLabel | string | "新增便利貼" | 新增按鈕的文字 |
showAdd | boolean | true | 是否顯示右下角的新增按鈕 |
className | string | — | 透傳到最外層(木框)容器 |
StickyNote 型別
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 唯一識別碼,同時作為旋轉角度與圖釘/膠帶樣式的決定性雜湊種子 |
text | string | 便利貼上的文字內容 |
x | number | 相對板面左上角的 X 座標(px) |
y | number | 相對板面左上角的 Y 座標(px) |
color | number | 色票索引 0–5(黃/粉/綠/藍/橘/紫),未指定時依 id 雜湊自動配色 |
可及性
- 每張便利貼皆可鍵盤聚焦:
Enter進入編輯、方向鍵移動(Shift加速)、Delete/Backspace揉掉刪除 - 便利貼以
role="group"搭配aria-label朗讀內容,揉掉按鈕有明確的aria-label - 編輯中
Esc取消還原原文、Cmd/Ctrl + Enter或失焦即提交 - 使用者系統開啟「減少動態效果」時,進場、拖起放大與揉紙動畫皆改為單純淡入淡出
- 旋轉角度與裝飾樣式由
id決定性雜湊產生,SSR 與 client 渲染結果一致
常見問題
可以在 Next.js App Router 使用嗎?
可以。元件檔案開頭已有 "use client",在 Server Component 頁面中照常 import 即可。便利貼的旋轉角度與裝飾由 id 決定性雜湊產生,SSR 與 client 渲染結果一致。
為什麼改了 notes 之後畫面沒有更新?
notes 只在第一次渲染時讀入,之後由元件內部管理狀態。要整個換成新資料,請改變元件的 key 讓它重新掛載。
便利貼的位置怎麼存?
每張便利貼的 x、y 是相對板面左上角的像素座標,拖曳結束或用方向鍵移動後會四捨五入成整數,透過 onChange 回傳。把回傳的陣列原樣存起來,下次當成 notes 傳入即可還原。
可以用鍵盤操作嗎?
可以。每張便利貼都能聚焦:Enter 編輯、方向鍵移動(按住 Shift 每次移動較多)、Delete 或 Backspace 刪除;編輯時 Esc 取消、Cmd/Ctrl + Enter 提交。
需要哪些依賴?
motion、lucide-react、clsx 與 tailwind-merge。軟木板紋理是純 CSS 漸層,不需要圖片檔。