電商購物體驗

折扣碼輸入

電商折扣碼輸入 React 元件:套用成功時輸入框變成一張左緣打孔的票券 chip、票根撕開飄落、折抵金額逐位滾動進場;失敗時抖動並顯示原因,已套用可一鍵移除,輸入自動轉大寫去空白。

結帳頁必備的折扣碼欄位。輸入會自動轉大寫、移除空白並截斷至長度上限;按「套用」或 Enter 後把碼交給你提供的 onApply 驗證,期間按鈕顯示載入動畫並設定 aria-busy。成功時輸入列切換成一張左緣打孔的票券 chip——票根撕開飄落、折抵金額逐位滾動進場,點右側的 × 即可移除重新輸入;失敗時輸入框水平抖動、轉紅框並顯示原因,可直接再試。輸入值與已套用狀態各自支援受控/非受控雙模式。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add coupon-code-input

還沒有專案,或安裝遇到問題?看快速開始 →

Playground

即時調整 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型別預設值說明
valuestring—受控輸入值;未提供時由元件內部管理(非受控)
defaultValuestring""非受控模式的初始輸入值
onChange(value: string) => void—輸入值變動時回呼,參數為規格化後(大寫、去空白)的字串
onApply(code: string) => Promise<CouponApplyResult> | CouponApplyResult—按下套用或 Enter 時的驗證函式;未提供時一律視為套用成功
onRemove(code: string) => void—移除已套用折扣碼時回呼,參數為被移除的碼
appliedAppliedCoupon | null—受控的已套用狀態(null 表示未套用);未提供時由元件內部管理
currencystring"NT$"折抵金額前的貨幣前綴
placeholderstring"輸入折扣碼"輸入框佔位文字
labelstring"折扣碼"欄位標籤文字,傳空字串可隱藏
maxLengthnumber20折扣碼長度上限,超過自動截斷
disabledbooleanfalse停用輸入與按鈕
classNamestring—透傳到最外層容器

AppliedCoupon

欄位型別預設值說明
codestring—已套用的折扣碼(規格化後)
discountnumber—折抵金額(正數);有提供才會顯示金額與滾動動畫
labelstring—附加說明文字(例如「新客禮」)

CouponApplyResult

欄位型別預設值說明
okboolean—是否套用成功
discountnumber—成功時的折抵金額
labelstring—成功時的附加說明
reasonstring—失敗原因,會顯示於錯誤訊息並由螢幕閱讀器朗讀

另外具名匯出 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 完整描述折扣碼、折抵金額與說明;逐位滾動的金額動畫對輔助科技隱藏
  • 使用者系統開啟「減少動態效果」時,停用撕票、金額滾動與抖動動畫,狀態直接切換