WebberUI

團購成團進度(React)

以人數計的團購卡 React 元件:已跟團頭像堆疊、階梯團購價、成團倒數,達門檻瞬間 CTA 變「已成團」並整卡脈動噴彩帶,截止未達門檻則轉為未成團並顯示全額退款提示。

台灣社群團、辦公室團最常見的「湊人數」介面:商品列顯示目前團購價——跟團人數每跨過一個階梯門檻,舊價劃線、新價由下往上滾入;進度條以人數為刻度,每個階梯門檻都有節點與價籤;已跟團者以名字首字+確定性色相的頭像堆疊呈現(最多 6 位+「+N」,新成員從右側滑入);成團倒數每秒更新。按「+1 跟團」呼叫 onJoin,達成團門檻的瞬間 CTA 變成「已成團 🎉」、整卡輕微 scale 脈動並噴出彩帶粒子;截止仍未達門檻則自動轉為未成團,顯示「未成團將全額退款」。狀態支援受控/非受控。

載入預覽⋯
npx shadcn@latest add https://webberui.com/r/group-buy-progress.json

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

5
<GroupBuyProgress />

安裝

npx shadcn@latest add https://webberui.com/r/group-buy-progress.json

或在 components.json 設定 registries 後,改用 @webberui/group-buy-progress 安裝。

使用

import * as React from "react";
import {
  GroupBuyProgress,
  type GroupBuyParticipant,
} from "@/components/ui/group-buy-progress";

function OfficeCoffeeGroupBuy() {
  // 名單由你的資料層維護;元件只讀取,按「+1 跟團」時透過 onJoin 通知你新增
  const [participants, setParticipants] = React.useState<GroupBuyParticipant[]>([
    { id: "u1", name: "林曉彤" },
    { id: "u2", name: "陳建宏" },
    { id: "u3", name: "黃雅琪" },
  ]);

  return (
    <GroupBuyProgress
      product={{ name: "手沖濾掛咖啡 12 入組", subtitle: "深焙", price: 520 }}
      tiers={[
        { count: 5, price: 450 },
        { count: 10, price: 399 },
      ]}
      participants={participants}
      deadline="2026-09-30T23:59:59+08:00"
      maxSpots={20}
      onJoin={() =>
        setParticipants((list) => [...list, { id: "me", name: "我" }])
      }
      onFormed={() => console.log("成團了,通知主購結單")}
      onChange={(status) => console.log("狀態:", status)}
    />
  );
}

Props

Prop型別預設值說明
productGroupBuyProduct商品名稱、原價與副標
tiersGroupBuyTier[]階梯團購價(依 count 遞增;元件內會再排序並忽略非正數)
participantsGroupBuyParticipant[]目前已跟團的名單;人數、進度、價格與頭像都由此推導
deadlinestring | number | Date成團截止時間(ISO 字串、毫秒時間戳或 Date);到期仍未達門檻即轉為未成團
maxSpotsnumber名額上限;達到後跟團按鈕變為「已額滿」,同時作為進度條的滿刻度
minCountnumbertiers 最小的 count成團門檻人數;成團門檻與最低價格階不同時可單獨指定
valueGroupBuyStatus受控狀態;提供時由外部決定目前是募集中/已成團/未成團
defaultValueGroupBuyStatus依人數推導非受控模式的初始狀態;省略時已達門檻即為 "formed",否則 "open"
onChange(status: GroupBuyStatus) => void狀態需要變更時回呼(達門檻→formed、截止未達→failed);受控模式請在此更新 value
onJoin() => void按下跟團按鈕時回呼;元件不會自行新增名單,請在此把新成員加進 participants
onFormed() => void狀態實際轉為已成團時回呼一次
currencystring"NT$"幣別前綴,顯示在所有金額前
maxAvatarsnumber6頭像堆疊最多顯示幾位(顯示最新加入者),其餘彙整成「+N」
showCountdownbooleantrue是否顯示成團倒數
compactbooleanfalse精簡模式:更小的間距與頭像,隱藏副標、階梯價籤與提示行,適合嵌進商品列表
joinLabelstring跟團按鈕文字;等同 labels.join 的捷徑,兩者同時提供時以此為準
labelsPartial<GroupBuyProgressLabels>內建繁中覆寫介面文案(部分覆寫即可,其餘沿用內建)
classNamestring透傳到最外層容器

GroupBuyStatus

"open" | "formed" | "failed"——募集中/已成團/未成團。

GroupBuyProduct

欄位型別預設值說明
namestring商品名稱
pricenumber原價:未達任何階梯門檻時顯示的單價
subtitlestring副標/規格說明(精簡模式不顯示)

GroupBuyTier

欄位型別預設值說明
countnumber門檻人數(正整數)
pricenumber達到門檻後的單價

GroupBuyParticipant

欄位型別預設值說明
idstring唯一識別,同時作為頭像色相的種子(同一 id 每次顏色相同)
namestring顯示名稱;頭像取首字
colorstring自訂頭像底色(任意 CSS 色值);省略時由 id 推導確定性色相

GroupBuyProgressLabels

{count}{price} 的字串會在顯示時代入實際值。

欄位型別預設值說明
joinstring"+1 跟團"跟團按鈕文字
formedstring"已成團 🎉"成團後的按鈕文字
failedstring"未成團"未成團的按鈕文字,也用於進度列右側
fullstring"已額滿"額滿的按鈕文字
refundNotestring"未成團將全額退款"未成團時顯示於按鈕下方的退款提示
formedNotestring"已成團,等待主購結單"成團後顯示於進度列右側的提示
joinedstring"{count} 人已跟團"已跟團人數
untilFormedstring"再 {count} 人成團"距離成團還差幾人
nextTierstring"再 {count} 人降至 {price}"距離下一階還差幾人(顯示於價格下方)
bestPricestring"已達最低團購價"已達最後一階時的提示
originalstring"原價"劃線價為原價時的前綴
countdownstring"成團倒數"倒數區標題
endedstring"已截止"截止後倒數區的文字
tierstring"{count} 人"進度條上階梯節點的人數籤
thresholdstring"成團"成團門檻節點的籤(門檻與任何階梯人數都不同時才顯示)
morestring"還有 {count} 人"頭像堆疊「+N」的無障礙文字
day / hour / minute / secondstring"天" / "時" / "分" / "秒"倒數單位

另外具名匯出 resolveGroupBuyTier(tiers, count)(回傳目前落在的階與下一階)與 formatGroupBuyPrice(amount, currency)(千分位金額字串),方便在購物車或結帳頁沿用同一套價格規則。

細節

  • 價格落階:目前單價 = 已達到的最高階價格,未達任何階時為 product.price。價格一變,前一個價格就成為劃線舊價(初始已達階時舊價為原價),新價從下方滾入
  • 進度刻度:滿刻度為 maxSpots;未提供時取最高階人數與成團門檻的較大者。每個階梯在軌道上都有節點與「人數/價格」籤;兩端的籤自動靠邊對齊,不會凸出軌道
  • 成團與未成團:非受控模式下,募集中人數達 minCount 即自動轉為 formeddeadline 到期仍未達門檻則轉為 failed。受控模式元件只透過 onChange 建議下一個狀態,由你決定是否更新 value。成團與未成團都是終態,之後不會再自動改變
  • 倒數Date.now() 只在 effect 內讀取;SSR 與首次渲染顯示 -- 佔位,掛載後才每秒更新,避免水合不一致。無效的 deadline 會隱藏倒數且永不到期
  • 頭像:不用圖片;底色由 id 雜湊出色相(同一人每次相同),也可用 color 指定。堆疊顯示最新加入的 maxAvatars 位,較早的成員彙整為「+N」

可及性

  • 進度條為 role="progressbar"aria-valuenowaria-valuemax 以人數為單位,aria-valuetext 朗讀「N 人已跟團」;人數文字置於 aria-live="polite" 區域,跟團後會被螢幕閱讀器朗讀
  • 倒數為 role="timer"aria-label 提供完整的「天/時/分/秒」文字;未成團的退款提示為 role="status"
  • 頭像堆疊為語意 list,每位成員的 li 帶名字,「+N」帶「還有 N 人」;視覺首字對輔助科技隱藏
  • 跟團按鈕為原生 buttontype="button"),成團/未成團/額滿時 disabled 並同步 aria-disabled;鍵盤操作與 focus-visible 外框沿用瀏覽器原生行為
  • 使用者系統開啟「減少動態效果」時,停用價格滾動、頭像滑入、整卡脈動與彩帶粒子,只保留即時的文字與顏色變化

本頁目錄