PA03▸🧩 Compose · 互動組合▸社群
Comments Thread
留言串
難度 ★★★★★標籤 留言 · 狀態 · empty · loading3 min read
模式簡述
留言區是「狀態 UI」最完整的案例:空 / 載入 / 正常 / 大量 / 錯誤。每個狀態都需要設計, 而且空狀態跟錯誤狀態經常被偷懶忽略,造成「畫面空白以為網頁壞了」的爛經驗。
尚無留言
Claude Code Prompt
參考:PA03 comments-thread 技術:純 React + skeleton 載入 具體行為: - 空:顯示 icon + CTA「搶頭香」 - 載入:3 筆 skeleton(avatar + 2 行) - 正常:avatar、name、時間、內容;自己的留言高亮 - 大量:先顯示 6 筆 + 「載入更多」按鈕 - 錯誤:red icon + 訊息 + retry 限制: - skeleton 一定要跟真實內容尺寸接近,不然會跳版 - 自己的留言用 ring + bg 微微強調就好,不要太顯眼 - 時間用相對時間(2 分鐘前),不要 ISO
完整程式碼
'use client'
type Comment = { name: string; avatar: string; text: string; own?: boolean; time: string }
export function CommentsThread({ comments, state }: { comments: Comment[]; state: 'empty'|'loading'|'normal'|'error' }) {
if (state === 'empty') {
return (
<div>
<p>尚無留言</p>
<button>搶頭香</button>
</div>
)
}
if (state === 'loading') {
return (
<ul>
{[0,1,2].map(i => (
<li key={i} className="flex gap-3">
<div className="h-8 w-8 animate-pulse rounded-full bg-muted" />
<div className="flex-1 space-y-2">
<div className="h-3 w-24 animate-pulse rounded bg-muted" />
<div className="h-3 w-full animate-pulse rounded bg-muted" />
</div>
</li>
))}
</ul>
)
}
if (state === 'error') {
return <div>讀取失敗 <button>重試</button></div>
}
return (
<ul>
{comments.map((c, i) => (
<li key={i} className={c.own ? 'bg-lab-accent/5 ring-1 ring-lab-accent/20' : ''}>
<div>{c.avatar}</div>
<div>
<span>{c.name}</span> <span>{c.time}</span>
<p>{c.text}</p>
</div>
</li>
))}
</ul>
)
}何時用
- ✅ 部落格、文章下方留言
- ✅ Issue / PR 討論串
- ✅ 任何需要「使用者參與」的內容區
何時別用
- ❌ 沒人會看的舊內容(懶得做就別做空狀態)
- ❌ 即時聊天(要用 chat UI 模式,bottom-up)
- ❌ 一次性 feedback(用 form 更直接)
我踩過的坑
-
空狀態只寫「沒有留言」:使用者看不出來能不能留言。要加 CTA。
-
skeleton 尺寸跟真實差很多:載入完畫面整個跳。要量好 avatar 寬度跟 文字 line-height,skeleton 才不會跳版。
-
自己的留言高亮太重:早期用 bg-yellow,整篇刺眼。 改成
bg-lab-accent/5 + ring-1 ring-lab-accent/20,剛好。
相關模式
- PA01 Search Match — 搜尋留言時的命中高亮