PF02▸💬 Feedback · 反饋▸進度
Progress
進度條
難度 ★★★★★標籤 progress · determinate · indeterminate3 min read
模式簡述
Progress:任務進度。determinate 顯示百分比;indeterminate 顯示「正在做」。circular vs linear 選對情境。
上傳中⋯45%
Claude Code Prompt
參考:PF02 progress 組件:Progress.tsx Progress:任務進度。determinate 顯示百分比;indeterminate 顯示「正在做」。circular vs linear 選對情境。 限制: - 進度回跳 - indeterminate 動畫太慢 - 到 100% 沒給結束反饋
完整程式碼
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
type State = "linear" | "indeterminate" | "circular" | "stepped";
export function Progress({ state = "linear" }: { state?: State }) {
const [v, setV] = useState(45);
useEffect(() => {
if (state === "indeterminate") return;
const target = state === "stepped" ? 60 : 65;
setV(target);
}, [state]);
if (state === "circular") {
const r = 32;
const c = 2 * Math.PI * r;
return (
<div className="grid w-full max-w-xs place-items-center">
<div className="relative grid h-24 w-24 place-items-center">
<svg className="absolute inset-0 -rotate-90" viewBox="0 0 80 80">
<circle cx="40" cy="40" r={r} stroke="currentColor" strokeWidth="6"
className="text-muted/40" fill="none" />
<circle cx="40" cy="40" r={r} stroke="currentColor" strokeWidth="6"
className="text-lab-accent transition-[stroke-dashoffset] duration-500"
fill="none" strokeDasharray={c} strokeDashoffset={c * (1 - 0.65)} strokeLinecap="round" />
</svg>
<span className="font-mono text-sm">65%</span>
</div>
</div>
);
}
if (state === "stepped") {
return (
<div className="w-full max-w-md">
<div className="mb-2 flex justify-between font-mono text-[11px] text-muted-foreground">
<span>步驟 3 / 5</span>
<span>60%</span>
</div>
<div className="flex gap-1">
{Array.from({ length: 5 }).map((_, i) => (
<div
key={i}
className={cn(
"h-1.5 flex-1 rounded-full transition-colors",
i < 3 ? "bg-lab-accent" : "bg-muted",
)}
/>
))}
</div>
</div>
);
}
return (
<div className="w-full max-w-md">
<div className="mb-2 flex justify-between font-mono text-[11px] text-muted-foreground">
<span>上傳中⋯</span>
<span>{state === "indeterminate" ? "—" : `${v}%`}</span>
</div>
<div className="relative h-1.5 overflow-hidden rounded-full bg-muted">
{state === "indeterminate" ? (
<div className="absolute inset-y-0 left-0 w-1/3 animate-[shimmer_1.4s_ease-in-out_infinite] rounded-full bg-lab-accent" />
) : (
<div className="h-full rounded-full bg-lab-accent transition-all duration-500" style={{ width: `${v}%` }} />
)}
</div>
<style jsx>{`
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(300%); }
}
`}</style>
</div>
);
}何時用
- ✅ 上傳/下載進度(determinate)
- ✅ 未知時長的 loading(indeterminate)
- ✅ 多步驟流程的整體進度
何時別用
- ❌ 瞬間完成(< 500ms 不用秀)
- ❌ 進度跳動劇烈(看起來假,反而焦慮)
- ❌ 錯誤情境(請切到 error state)
我踩過的坑
-
進度回跳:30% → 25% 顯得 buggy。只進不退。
-
indeterminate 動畫太慢:1s 一次 OK,3s 一次像當機。
-
到 100% 沒給結束反饋:使用者不知道好了沒。秀 checkmark + 訊息。
相關模式
- PE05
- PD06