電商購物體驗

出單機收據

付款完成後模擬熱感應出單機逐行送紙的收據元件:處理中、出單、完成三階段,紙底鋸齒、可撕下與重播。

這是 WebberUI Pro 元件

下方的安裝指令需要授權才能執行,未授權時會回 401。

怎麼安裝 Pro 元件 →查看方案 →

付款成功的那一刻,讓收據像熱感應出單機一樣一格一格吐出來。元件分成處理中、出單、完成三個階段:處理中時機身指示燈脈動、小螢幕顯示狀態;出單時紙張以 20 段頓挫從出紙口送出(也可切成一段平滑送出);完成後收據可以按鈕或往下拖曳撕下,紙張帶著隨機旋轉掉落。紙底鋸齒用 clip-path 生成、齒數可調,機身與紙面質感全由 CSS 漸層與內嵌 SVG 噪點組成,不引用任何外部圖檔。收據內容就是 children,品項、稅額、訂單號怎麼排都由你決定。

把這個效果裝進你的專案

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

npx webberui@latest add "https://webberui.com/r/receipt-printer.json?t=<安裝 token>"

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

Playground

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

1.75
40
編輯中的呼叫預設屬性
<ReceiptPrinter/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

安裝

npx webberui@latest add "https://webberui.com/r/receipt-printer.json?t=<安裝 token>"

或在 components.json 設定 registries 後,改用 @webberui/receipt-printer 安裝。

使用

import * as React from "react";
import {
  ReceiptPrinter,
  type ReceiptStage,
} from "@/components/ui/receipt-printer";

export function CheckoutReceipt() {
  const [stage, setStage] = React.useState<ReceiptStage>("idle");

  async function pay() {
    setStage("processing");
    await chargeCard(); // 你的付款 API
    setStage("printing");
  }

  return (
    <>
      <button type="button" onClick={pay}>
        確認付款
      </button>
      <ReceiptPrinter stage={stage} onStageChange={setStage}>
        <p className="text-center font-bold">拾光雜貨</p>
        <dl className="mt-2 space-y-1 border-t border-dashed pt-2">
          <div className="flex justify-between">
            <dt>年度訂閱方案</dt>
            <dd>NT$ 2,880</dd>
          </div>
          <div className="flex justify-between font-semibold">
            <dt>合計</dt>
            <dd>NT$ 3,024</dd>
          </div>
        </dl>
      </ReceiptPrinter>
    </>
  );
}

不需要接付款流程時,直接用非受控模式:掛載後停留 processingMs 自動出單,再用 ref 的 replay() 重播。

const ref = React.useRef<ReceiptPrinterHandle>(null);

<ReceiptPrinter ref={ref} mode="smooth" duration={2.4} teeth={28}>
  {/* 收據明細 */}
</ReceiptPrinter>;

ref.current?.replay();

需要自訂排版(例如把狀態列放在別處)時改用複合元件:

<ReceiptPrinter.Root stage={stage} onStageChange={setStage}>
  <ReceiptPrinter.Machine>
    <ReceiptPrinter.Header>TP-58</ReceiptPrinter.Header>
    <ReceiptPrinter.Screen />
  </ReceiptPrinter.Machine>
  <ReceiptPrinter.Output>
    <ReceiptPrinter.Paper>{/* 收據明細 */}</ReceiptPrinter.Paper>
  </ReceiptPrinter.Output>
  <ReceiptPrinter.Status className="mt-3" />
</ReceiptPrinter.Root>

Props

ReceiptPrinter 與 ReceiptPrinter.Root 接受相同的 props(ReceiptPrinter 的 children 會放進紙張本體,Root 的 children 則是自由組合的子元件)。

Prop型別預設值說明
stage"idle" | "processing" | "printing" | "complete"—受控的階段;未提供時由元件內部管理
defaultStageReceiptStage"processing"非受控模式的初始階段
onStageChange(stage: ReceiptStage) => void—階段變更時回呼;出紙結束會以 "complete" 通知,受控時由父層決定是否採用
mode"stepped" | "smooth""stepped"stepped 以 20 段頓挫逐行送紙;smooth 一段 ease-in-out 送出
durationnumber1.75出紙總長(秒);未提供時讀取 CSS 變數 --wb-receipt-duration
teethnumber40紙底鋸齒的齒數,齒深固定 4px
processingMsnumber1200非受控時 processing 階段停留的毫秒數,之後自動進入 printing
tearablebooleantrue完成後是否可撕下(按鈕或往下拖曳超過 60px)
onTear() => void—撕下動畫結束後回呼
labelsReceiptPrinterLabels—覆寫介面與螢幕閱讀器文案
grainbooleanfalse機身與紙面額外疊一層顆粒質感
refReact.Ref<ReceiptPrinterHandle>—取得 replay():把收據收回機內、階段回到 processing
classNamestring—透傳到最外層容器

子元件 Machine、Header、Output、Paper 接受 children 與 className;Screen 與 Status 只接受 className。

ReceiptPrinterLabels

欄位型別預設值說明
devicestring"出單機"機身頂部的品名(Header 沒有 children 時顯示)
idlestring"待命"待命階段的狀態文字
processingstring"處理中…"處理中階段的狀態文字
printingstring"列印收據…"出單階段的狀態文字
completestring"完成"完成階段的狀態文字
srProcessingstring"正在處理付款"螢幕閱讀器播報:處理中
srPrintingstring"正在列印收據"螢幕閱讀器播報:出單中
srCompletestring"收據已完成"螢幕閱讀器播報:完成
srTornstring"收據已撕下"螢幕閱讀器播報:已撕下
tearstring"撕下收據"撕下按鈕文字
paperstring"收據"紙張區塊的 aria-label

另外具名匯出 ReceiptStage、ReceiptFeedMode、ReceiptPrinterHandle、ReceiptPrinterProps 與 ReceiptPrinterRootProps 型別。

細節

  • 逐行頓挫:stepped 模式把 0→100% 的送紙切成 20 段,keyframes 共 41 個值、「前進、停住」交替,times 均分、ease 為 linear,前進與停頓各佔一半時間,看起來就是熱感應機一格一格吐紙
  • 出紙口:紙張初始 translateY(-100%) 藏在出紙口內,出單時移到 0。出紙口用 clip-path: inset() 只裁上緣,撕下時的旋轉、下墜與陰影才不會被切掉
  • 受控與非受控:非受控時 processing 停留 processingMs 後自動出單、出紙結束自動完成;受控時元件只在出紙結束時以 onStageChange("complete") 通知,何時從 processing 進入 printing 由父層決定(通常是付款 API 回應成功之後)
  • 節奏變數:--wb-receipt-duration(出紙總長)、--wb-receipt-accent(指示燈與完成圖示,預設 #34d399)、--wb-receipt-processing(處理中指示燈,預設 #fbbf24)、--wb-receipt-screen(小螢幕文字,預設 #bef264)
  • 撕下:旋轉角在撕下當下於 −8° 到 8° 之間隨機產生,紙張以 500ms ease-in 往下 120%、淡出;回到 processing 或呼叫 replay() 時自動復原
  • 深色模式:紙面維持淺色紙張(neutral-100),機身本來就是深色,兩種主題下都像真實的出單機

可及性

  • Root 內有一個 sr-only 的 role="status"、aria-live="polite" 區域,依階段播報「正在處理付款」「正在列印收據」「收據已完成」,撕下後播報「收據已撕下」;Screen 與 Status 的視覺文字為 aria-hidden,避免重複朗讀
  • 紙張是帶 aria-label="收據" 的 section,尚未出單或已撕下時設為 aria-hidden 與 inert;明細建議用 dl 或 table 標記
  • 「撕下收據」是真正的 button,可用 Tab 聚焦、Enter/空白鍵觸發,帶 focus-visible 外框;拖曳撕下只是額外的指標操作
  • 使用者系統開啟「減少動態效果」時,不出紙、不頓挫,進入 printing 就直接顯示完整收據與完成狀態;狀態文字只淡入淡出不位移,指示燈不脈動,撕下改為淡出