電商購物體驗
折扣碼輸入
電商折扣碼輸入 React 元件:套用成功時輸入框變成一張左緣打孔的票券 chip、票根撕開飄落、折抵金額逐位滾動進場;失敗時抖動並顯示原因,已套用可一鍵移除,輸入自動轉大寫去空白。
結帳頁必備的折扣碼欄位。輸入會自動轉大寫、移除空白並截斷至長度上限;按「套用」或 Enter 後把碼交給你提供的 onApply 驗證,期間按鈕顯示載入動畫並設定 aria-busy。成功時輸入列切換成一張左緣打孔的票券 chip——票根撕開飄落、折抵金額逐位滾動進場,點右側的 × 即可移除重新輸入;失敗時輸入框水平抖動、轉紅框並顯示原因,可直接再試。輸入值與已套用狀態各自支援受控/非受控雙模式。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add coupon-code-inputPlayground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
20
編輯中的呼叫預設屬性
<CouponCodeInput/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
安裝
npx webberui@latest add https://webberui.com/r/coupon-code-input.json或在 components.json 設定 registries 後,改用 @webberui/coupon-code-input 安裝。
使用
import { CouponCodeInput } from "@/components/ui/coupon-code-input";
<CouponCodeInput
label="折扣碼"
currency="NT$"
onApply={async (code) => {
// 呼叫你的 API 驗證;回傳 ok 與折抵資訊
if (code === "WELCOME100") {
return { ok: true, discount: 100, label: "新客禮" };
}
return { ok: false, reason: "查無此折扣碼" };
}}
onRemove={(code) => console.log(`已移除 ${code}`)}
/>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
value | string | — | 受控輸入值;未提供時由元件內部管理(非受控) |
defaultValue | string | "" | 非受控模式的初始輸入值 |
onChange | (value: string) => void | — | 輸入值變動時回呼,參數為規格化後(大寫、去空白)的字串 |
onApply | (code: string) => Promise<CouponApplyResult> | CouponApplyResult | — | 按下套用或 Enter 時的驗證函式;未提供時一律視為套用成功 |
onRemove | (code: string) => void | — | 移除已套用折扣碼時回呼,參數為被移除的碼 |
applied | AppliedCoupon | null | — | 受控的已套用狀態(null 表示未套用);未提供時由元件內部管理 |
currency | string | "NT$" | 折抵金額前的貨幣前綴 |
placeholder | string | "輸入折扣碼" | 輸入框佔位文字 |
label | string | "折扣碼" | 欄位標籤文字,傳空字串可隱藏 |
maxLength | number | 20 | 折扣碼長度上限,超過自動截斷 |
disabled | boolean | false | 停用輸入與按鈕 |
className | string | — | 透傳到最外層容器 |
AppliedCoupon
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
code | string | — | 已套用的折扣碼(規格化後) |
discount | number | — | 折抵金額(正數);有提供才會顯示金額與滾動動畫 |
label | string | — | 附加說明文字(例如「新客禮」) |
CouponApplyResult
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
ok | boolean | — | 是否套用成功 |
discount | number | — | 成功時的折抵金額 |
label | string | — | 成功時的附加說明 |
reason | string | — | 失敗原因,會顯示於錯誤訊息並由螢幕閱讀器朗讀 |
另外具名匯出 normalizeCouponCode(raw, maxLength),方便在表單層重複使用同一套規格化邏輯。
細節
- 票券視覺:左緣的鋸齒打孔用 CSS
mask(radial-gradient 重複挖半圓)實現,缺口是真正透明的,疊在任何背景上都成立;套用成功後右端有一小片票根覆蓋在票券上,進場後撕離、旋轉飄落。 - 大小寫規格化:所有輸入路徑(打字、貼上、受控值)都會經過
normalizeCouponCode——轉大寫、移除所有空白、截斷至maxLength。 - Enter 送出有 IME 組字保護:中文注音等輸入法組字中按 Enter 不會誤送出,並處理 Safari 在
compositionend後才發 keydown 的情況。 - 驗證是非同步的:期間再次按套用會被忽略;
onApply拋出例外視同失敗並顯示通用錯誤訊息;若使用者在等待期間移除重試,較早的過期結果會被丟棄,不會覆蓋新狀態。
可及性
label以htmlFor關聯至輸入框;驗證失敗時設定aria-invalid,並以aria-describedby指向訊息區- 狀態訊息(驗證中/失敗原因/套用成功)位於
role="status"、aria-live="polite"區域,變化會被螢幕閱讀器朗讀 - 套用按鈕於驗證期間設定
aria-busy並停用,避免重複送出;移除按鈕帶有含折扣碼的aria-label - 已套用的票券 chip 為
role="group",aria-label完整描述折扣碼、折抵金額與說明;逐位滾動的金額動畫對輔助科技隱藏 - 使用者系統開啟「減少動態效果」時,停用撕票、金額滾動與抖動動畫,狀態直接切換