免運門檻進度條(React)
多階門檻進度條(免運→贈品→折扣)的 React 元件:「再買 NT$X 即可享免運」差額逐位滾動、越過門檻整條變色、節點彈跳並噴出一次性小彩帶,已達成的階以 chip 列出,可獨立貼在購物車頂欄。
獨立於購物車的滿額門檻條:把 subtotal 與 tiers(免運 → 贈品 → 折扣,依金額遞增)交給它,頂部文案就會顯示「再買 NT$X 即可享免運」,差額用逐位滾動的數字捲軸更新;軌道依階數等寬分段,每個門檻位置有一顆小圖示節點,未達灰、達成填色並彈跳一下;填色條以彈簧動畫推進,越過新門檻的瞬間整條換成該階顏色、節點噴出 10 顆小彩帶,下方 chip 同步打勾。全部達成時文案改為「已解鎖全部優惠」。compact 模式縮成一條薄軌,適合放在購物車或側欄的頂部。
載入預覽⋯
npx shadcn@latest add https://webberui.com/r/free-shipping-meter.jsonPlayground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
640
990
<FreeShippingMeter />
安裝
npx shadcn@latest add https://webberui.com/r/free-shipping-meter.json或在 components.json 設定 registries 後,改用 @webberui/free-shipping-meter 安裝。
使用
import { FreeShippingMeter } from "@/components/ui/free-shipping-meter";
import { Gift, Truck } from "lucide-react";
// 單階:只有免運門檻時,直接給 threshold
<FreeShippingMeter subtotal={640} threshold={990} />
// 多階:免運 → 贈品 → 折扣(依金額遞增)
<FreeShippingMeter
subtotal={cartSubtotal}
currency="NT$"
tiers={[
{ amount: 990, label: "免運", icon: Truck, reward: "全館免運" },
{ amount: 1500, label: "贈品", icon: Gift, reward: "送帆布購物袋" },
{ amount: 2500, label: "折扣", reward: "結帳再折 NT$100" },
]}
onTierReached={(tier, index) => console.log(`達成第 ${index + 1} 階:${tier.label}`)}
/>
// 購物車頂欄:精簡模式
<FreeShippingMeter subtotal={cartSubtotal} tiers={tiers} compact />Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
subtotal | number | 0 | 目前購物車小計;由外部提供,元件依此計算進度、文案與達標狀態 |
tiers | FreeShippingTier[] | — | 門檻階層(依 amount 遞增,元件內會再排序一次並忽略非正數);未提供時以 threshold 建立單一「免運」階 |
threshold | number | 990 | 單階簡寫:只有一個免運門檻時可直接給金額,等同 tiers=[{ amount, label: "免運" }];提供 tiers 時忽略 |
currency | string | "NT$" | 幣別前綴,顯示在所有金額前 |
onTierReached | (tier: FreeShippingTier, index: number) => void | — | 小計首次越過某一階時回呼;掛載當下已達成的階不會觸發,一次跨過多階會依序各觸發一次 |
compact | boolean | false | 精簡模式:更薄的軌道、更小的文字,不顯示小計讀數、獎勵說明與 chip 列,適合貼在購物車頂欄 |
ariaLabel | string | "免運門檻進度" | 進度條的無障礙標籤 |
className | string | — | 透傳到最外層容器 |
FreeShippingTier
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
amount | number | — | 門檻金額(小計達到含此金額即視為達成) |
label | string | — | 階層名稱,會出現在文案與 chip 上,例如「免運」「贈品」「折扣」 |
icon | LucideIcon | — | 節點圖示;未提供時依序使用卡車/禮物/折扣標籤/星芒 |
reward | string | — | 達標獎勵說明;顯示在下一階文案下方,並作為 chip 的 title |
另外具名匯出 tierProgress(subtotal, tiers)(回傳 0–100 的軌道百分比)與 FreeShippingTier 型別,方便在別處畫同一套進度。
細節
- 等寬分段:軌道百分比不是按金額比例,而是每一階佔相同寬度、階內線性內插。門檻金額往往差距懸殊(990 / 1500 / 3000),按比例畫會讓前段擠在一起、節點重疊;等寬讓版面穩定也更好讀
- 差額無條件進位:「再買 NT$X」以
Math.ceil計算,確保照著文案買一定跨得過門檻;金額一律以整數加千分位顯示 - 顏色跟著最高達成階走:未達任何階時填色條為中性深色,達成第一階起依序切換翡翠綠 → 紫 → 琥珀 → 天藍(超過四階循環),chip 與節點各自沿用自己那一階的顏色
- 彩帶只在「越過」時噴:以掛載當下的達成階數為基準,初始就已達成的階不噴、也不觸發
onTierReached;粒子軌跡是確定性表格,不用Math.random,SSR 與 CSR 輸出一致 tiers會在元件內濾掉非正數金額並依amount排序,因此傳入順序錯了也不會壞,但仍建議依遞增順序撰寫以免和onTierReached的index對不上
可及性
- 軌道使用
role="progressbar",aria-valuemin為 0、aria-valuemax為最高階金額、aria-valuenow為目前小計,aria-valuetext為完整文案(「小計 NT$640,再買 NT$350 即可享免運」) - 視覺上的滾動數字捲軸包含 0–9 全部字元,因此整段文案以
aria-hidden隱藏,真正的訊息放在role="status"、aria-live="polite"的隱藏段落,小計變動時螢幕閱讀器會朗讀最新差額 - chip 列為語意化的
ul,每個 chip 附有「(已達成)/(未達成)」的隱藏文字;節點title提供階層名稱與門檻金額 - 使用者系統開啟「減少動態效果」時:不噴彩帶、填色條與數字直接跳到定位、節點與 chip 不彈跳,僅保留顏色與文字變化