PaymentMethods
Ways to pay as radio cards with their marks: card (with the brands taken), PayPal, Apple Pay, Google Pay, bank, cash.
Demo
Paying with card (on_change).
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/payment_methods.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/payment_methods.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/payment_methods.qwe
component PaymentMethodsDemoLive() { state way = "card" <div.demo-stack.demo-narrow> <PaymentMethods name="payment-demo-live" value={way} on_change={(v) => { way = v }} options={[{ value: "card", brands: ["visa", "mastercard", "amex"] }, "paypal", "apple", "google"]}/> <p.demo-note>Paying with {way} (on_change).</p> </div>}view PaymentMethodsDemo() = <div.demo-row style="align-items: flex-start; gap: 32px"> <PaymentMethodsDemoLive/> <div.demo-narrow> <PaymentMethods name="payment-demo-more" label="Other ways" required={true} options={["bank", "later", { value: "invoice", label: "Pay by invoice", icon: "🧾", hint: "Business accounts only" }, { value: "cash", disabled: true }]}/> </div> </div>Source
ui/payment_methods.qwe
// PaymentMethods: the ways to pay, as cards to pick one from, each with its// mark: a card, PayPal, Apple Pay, Google Pay, a bank transfer, cash. They are// real radio buttons, so a plain <form> sends the chosen `value` as `name`, and// the arrow keys move between them.//// <PaymentMethods value="card" options={["card", "paypal", "apple", "google"]}/>// <PaymentMethods options={[{ value: "card", brands: ["visa", "mastercard", "amex"] }, "bank",// { value: "invoice", label: "Pay by invoice", icon: "🧾", hint: "Business accounts" }]}/>//// options: the built-in names, or maps of { value, label?, icon?, hint?, brands?,// disabled? }. The built-in names and their words: "card" (Credit or debit// card), "paypal" (PayPal), "apple" (Apple Pay), "google" (Google Pay), "bank"// (Bank transfer), "cash" (Cash on delivery), "later" (Pay later), "crypto"// (Cryptocurrency); a map with one of them as its value takes its words and mark.// icon: one of those names, or any text or emoji. brands: card marks to show// after the label, from "visa", "mastercard", "amex", "discover", "jcb",// "unionpay". value: the one chosen. name ("payment"), label ("Pay with"),// required, disabled, error as for every field. on_change(value): inside one// of your components, given the value chosen; pass value from your state, so// the choice stays as the component redraws.import ui/theme.qwe use ui_clslet payment_methods_css = css`.ui-pay { min-width: 0; margin: 0; padding: 0; border: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-pay-legend { padding: 0; margin-bottom: 8px; font-size: 13px; font-weight: 500; }.ui-pay-list { display: grid; gap: 8px; }.ui-pay-opt { position: relative; display: grid; grid-template-columns: auto auto 1fr auto; gap: 12px; align-items: center; padding: 10px 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); cursor: pointer; transition: border-color .15s, background .15s, box-shadow .15s; }.ui-pay-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }.ui-pay-dot { position: relative; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--ui-strong); background: var(--ui-surface); transition: border-color .15s; }.ui-pay-dot::after { content: ""; position: absolute; inset: 4px; border-radius: 50%; background: var(--ui-accent); transform: scale(0); transition: transform .12s; }.ui-pay-opt:hover { border-color: var(--ui-strong); }.ui-pay-opt:has(input:checked) { border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent-soft) 60%, var(--ui-surface)); box-shadow: inset 0 0 0 1px var(--ui-accent); }.ui-pay-opt input:checked + .ui-pay-dot { border-color: var(--ui-accent); border-width: 1.5px; }.ui-pay-opt input:checked + .ui-pay-dot::after { transform: scale(1); }.ui-pay-opt:has(input:focus-visible) { box-shadow: var(--ui-focus); }.ui-pay-opt:has(input:disabled) { opacity: .55; cursor: not-allowed; }.ui-pay-icon { display: inline-grid; place-items: center; width: 40px; height: 28px; border: 1px solid var(--ui-border); border-radius: 5px; background: var(--ui-subtle); color: var(--ui-fg); font-size: 16px; }.ui-pay-icon svg { width: 22px; height: 22px; }.ui-pay-text { display: grid; gap: 1px; min-width: 0; }.ui-pay-name { font-weight: 500; }.ui-pay-hint { font-size: 13px; color: var(--ui-muted); }.ui-pay-brands { display: flex; gap: 4px; }.ui-pay-brand { display: inline-grid; place-items: center; height: 20px; padding: 0 5px; border: 1px solid var(--ui-border); border-radius: 3px; background: var(--ui-surface); font: 700 9px/1 var(--ui-font); letter-spacing: .04em; color: var(--ui-muted); text-transform: uppercase; }.ui-pay-invalid .ui-pay-opt { border-color: var(--ui-danger); }.ui-pay-error { margin: 8px 0 0; font-size: 13px; color: var(--ui-danger); }@media (prefers-reduced-motion: reduce) { .ui-pay-opt, .ui-pay-dot, .ui-pay-dot::after { transition: none; } }`// The built-in ways: their words and the path of their mark (24x24, stroked).let payment_methods_known = { card: { label: "Credit or debit card", d: "M3 6h18v12H3zM3 10h18M7 14h4" }, paypal: { label: "PayPal", d: "M7 20l2-14h5a4 4 0 0 1 0 8h-3l-1 6zm3-14l-1 6" }, apple: { label: "Apple Pay", d: "M12 6c1-2 3-2 3-2s0 2-1 3-3 1-3 1 0-1 1-2zm-4 5c0-2 2-3 3-3s2 1 3 1 2-1 3-1 3 1 3 3c0 4-3 9-5 9s-2-1-3-1-1 1-3 1-4-5-4-9z" }, google: { label: "Google Pay", d: "M12 11h8a8 8 0 1 1-2.3-5.7M12 11v3h5" }, bank: { label: "Bank transfer", d: "M3 9l9-5 9 5H3zM5 9v8m4-8v8m6-8v8m4-8v8M3 20h18" }, cash: { label: "Cash on delivery", d: "M2 7h20v10H2zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM5 10v4m14-4v4" }, later: { label: "Pay later", hint: "Pay in 30 days, or in 3 parts", d: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18zM12 7v5l3 2" }, crypto: { label: "Cryptocurrency", d: "M9 4v16m2-16v16M8 8h6a2.5 2.5 0 0 1 0 5H8m0 0h7a2.5 2.5 0 0 1 0 5H8" },}let payment_methods_brands = { visa: "Visa", mastercard: "MC", amex: "Amex", discover: "Disc", jcb: "JCB", unionpay: "UnionPay" }view PaymentMethods(options: list = ["card", "paypal"], value: str? = null, name: str = "payment", label: str? = "Pay with", required: bool = false, disabled: bool = false, error: str? = null, on_change = null, class: str? = null) { style global payment_methods_css let id = `payment-methods-${name}` <fieldset class={ui_cls(["ui-pay", error != null && "ui-pay-invalid", class])} id={id} disabled={disabled} aria-describedby={error != null ? `${id}-error` : null}> {label != null && <legend.ui-pay-legend>{label}</legend>} <div.ui-pay-list> {for [i, o] in enumerate(options) => { let it = type_of(o) == "str" ? { value: o } : o let known = get(payment_methods_known, get(it, "icon") ?? it.value) let icon = get(it, "icon") ?? it.value let text = get(it, "label") ?? known.label ?? it.value let hint = get(it, "hint") ?? known.hint let oid = `${id}-${i}` <label.ui-pay-opt for={oid} key={it.value}> <input type="radio" id={oid} name={name} value={it.value} checked={it.value == value} required={required} disabled={get(it, "disabled") == true} @change={(e) => { on_change && on_change(e.value) }}/> <span.ui-pay-dot aria-hidden="true"></span> <span.ui-pay-icon aria-hidden="true"> {known != null ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d={known.d}/></svg> : icon} </span> <span.ui-pay-text> <span.ui-pay-name>{text}</span> {hint != null && <span.ui-pay-hint>{hint}</span>} </span> {len(get(it, "brands") ?? []) > 0 && <span.ui-pay-brands> {for b in it.brands => <span.ui-pay-brand key={b} aria-label={get(payment_methods_brands, b) ?? b}>{get(payment_methods_brands, b) ?? b}</span>} </span>} </label> }} </div> {error != null && <p.ui-pay-error id={`${id}-error`} role="alert">{error}</p>} </fieldset>}Related
ShippingOptionsShipping methods as radio cards, each with its price and how long it takes; a plain form sends the choice.7 views · 7 downloadsAddressCardA saved address with a Default badge and Edit and Remove, as submit buttons in a plain form or callbacks.6 views · 7 downloadsCouponInputA promo code box with Apply: on_apply(code) says whether it took, and an applied code shows with Remove.6 views · 5 downloadsPlanToggleMonthly or yearly billing as a pill of two radios, with the saving shown on the yearly side.6 views · 4 downloadsVariantPickerSizes as buttons and colours as swatches, sold-out ones crossed out: radio groups for the keyboard, hidden inputs for forms.6 views · 6 downloadsAddressFormStreet, city, region, postcode and country in the usual layout, with the autocomplete names browsers fill from.5 views · 7 downloads
Thread
Thread 0 posts
On PaymentMethods: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.