Commerce

CartSummary

Subtotal, shipping, tax, a discount and the total added up, with a Checkout button as a link, a form or a callback.

#commerce#cart#checkout#price#no-js

Demo

Preview

Order summary

Subtotal
$213.50
Shipping
Free
Tax
$42.70
Total
$256.20
Checkout

Continue shopping

Order summary

3 items

Subtotal
$213.50
Shipping
$9.90
SAVE10
−$21.35
Total
$202.05

Taxes are included.

Checkout not clicked.

Your order

Subtotal
¥1,250
Shipping
Calculated at checkout
Gift wrap
¥300
Total
¥1,550

Get it

It needs theme.qwe, as every component does, and price.qwe; 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/price.qwe$ curl -O https://quillolang.org/components/ui/cart_summary.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/cart_summary.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/cart_summary.qwe
component CartSummaryDemoLive() {    state clicked = 0    <div.demo-stack>        <CartSummary subtotal={213.5} shipping={9.9} discount={21.35} discount_label="SAVE10" count={3}                     note="Taxes are included." on_checkout={() => { clicked += 1 }}/>        <p.demo-note>{clicked == 0 ? "Checkout not clicked." : `on_checkout called ${clicked} ${clicked == 1 ? "time" : "times"}.`}</p>    </div>}view CartSummaryDemo() =    <div.demo-row style="align-items: flex-start; gap: 24px">        <div style="width: 300px">            <CartSummary subtotal={213.5} shipping={0} tax={42.7} checkout_href="#cart_summary" continue_href="#product_grid"/>        </div>        <div style="width: 300px"><CartSummaryDemoLive/></div>        <div style="width: 300px">            <CartSummary subtotal={1250} shipping="Calculated at checkout" currency="JPY" locale="ja-JP" title="Your order"                         lines={[{ label: "Gift wrap", amount: 300 }]} action="/checkout" disabled={true}/>        </div>    </div>

Source

ui/cart_summary.qwe
// CartSummary: the sums at the side of a cart: subtotal, shipping, tax, a// discount, the total, and the Checkout button. Numbers are added up and written// as Price writes them (this file needs price.qwe).////   <CartSummary subtotal={213.5} shipping={0} tax={42.7} checkout_href="/checkout"/>//   <CartSummary subtotal={213.5} shipping={9.9} discount={21.35} discount_label="SAVE10"//                count={3} note="Taxes are included." on_checkout={() => { step = 2 }}/>//// subtotal: the lines added up. shipping: a number (0 reads "Free"), text// ("Free", "Calculated at checkout"), or left out (not shown). tax: a number, or// left out. discount: taken off, shown in green with a minus; discount_label// names it ("Discount", or the code). total: given, or worked out from the// numbers above. lines: more lines, { label, amount, tone? }, before the total.// count: how many items, said under the title ("3 items"). currency, locale,// cents as for Price. title ("Order summary"). note: a quieter line under the// total. checkout_href makes the button a link; action a form that posts to it;// otherwise, inside one of your components, it calls on_checkout().// checkout_label ("Checkout"). continue_href: a "Continue shopping" link under// it. disabled: the button cannot be clicked (an empty cart).import ui/price.qwe use price_textimport ui/theme.qwe use ui_clslet cart_summary_css = css`.ui-cart-sum { display: grid; gap: 10px; min-width: 0; padding: 18px 20px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius);  background: var(--ui-surface); font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-cart-sum-title { margin: 0; font-size: 16px; font-weight: 600; }.ui-cart-sum-count { margin: -6px 0 0; font-size: 13px; color: var(--ui-muted); }.ui-cart-sum-lines { display: grid; gap: 8px; margin: 0; }.ui-cart-sum-line { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }.ui-cart-sum-line dt { color: var(--ui-muted); }.ui-cart-sum-line dd { margin: 0; font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-cart-sum-success dt, .ui-cart-sum-success dd { color: var(--ui-success); }.ui-cart-sum-muted dd { color: var(--ui-muted); }.ui-cart-sum-total { padding-top: 10px; border-top: 1px solid var(--ui-border); font-size: 16px; font-weight: 700; }.ui-cart-sum-total dt { color: var(--ui-fg); }.ui-cart-sum-note { margin: 0; font-size: 12px; color: var(--ui-muted); }.ui-cart-sum-form { display: contents; }.ui-cart-sum-btn { display: flex; align-items: center; justify-content: center; width: 100%; height: 42px; margin-top: 4px; border: 1px solid transparent;  border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg); font: 600 15px/1 var(--ui-font); text-decoration: none;  cursor: pointer; transition: background .15s, box-shadow .15s; }.ui-cart-sum-btn:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }.ui-cart-sum-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-cart-sum-btn[disabled], .ui-cart-sum-btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; pointer-events: none; }.ui-cart-sum-more { text-align: center; font-size: 13px; }.ui-cart-sum-more a { color: var(--ui-accent); }@media (prefers-reduced-motion: reduce) { .ui-cart-sum-btn { transition: none; } }`view CartSummary(subtotal, shipping = null, tax = null, discount = null, discount_label: str = "Discount", total = null,                 lines: list = [], count: int? = null, currency: str = "USD", locale: str? = null, cents: bool = true,                 title: str? = "Order summary", note: str? = null, checkout_href: str? = null, action: str? = null,                 checkout_label: str = "Checkout", continue_href: str? = null, disabled: bool = false, on_checkout = null,                 class: str? = null) {    style global cart_summary_css    let num = (x) => x != null && type_of(x) != "str" ? dec(x) : dec(0)    let sum = num(subtotal) + num(shipping) + num(tax) - num(discount)        + sum(lines | map((l) => num(l.amount)))    let grand = total ?? sum    let money_of = (x) => <span>{price_text(x, currency, locale, cents)}</span>    let ship = shipping == null ? null : type_of(shipping) == "str" ? shipping : dec(shipping) == 0 ? "Free" : price_text(shipping, currency, locale, cents)    let button = checkout_href != null        ? <a.ui-cart-sum-btn href={checkout_href} aria-disabled={disabled ? "true" : null}>{checkout_label}</a>        : <button.ui-cart-sum-btn type={action != null ? "submit" : "button"} disabled={disabled}                  @click={() => { if action == null && on_checkout != null { on_checkout() } }}>{checkout_label}</button>    <section class={ui_cls(["ui-cart-sum", class])} aria-label={title}>        {title != null && <h3.ui-cart-sum-title>{title}</h3>}        {count != null && <p.ui-cart-sum-count>{count} {count == 1 ? "item" : "items"}</p>}        <dl.ui-cart-sum-lines>            <div.ui-cart-sum-line><dt>Subtotal</dt><dd>{money_of(subtotal)}</dd></div>            {ship != null && <div class={ui_cls(["ui-cart-sum-line", ship == "Free" && "ui-cart-sum-success"])}><dt>Shipping</dt><dd>{ship}</dd></div>}            {tax != null && <div.ui-cart-sum-line><dt>Tax</dt><dd>{money_of(tax)}</dd></div>}            {discount != null && <div.ui-cart-sum-line.ui-cart-sum-success><dt>{discount_label}</dt><dd>−{price_text(discount, currency, locale, cents)}</dd></div>}            {for l in lines => <div key={l.label} class={ui_cls(["ui-cart-sum-line", get(l, "tone") != null && `ui-cart-sum-${l.tone}`])}>                <dt>{l.label}</dt><dd>{money_of(l.amount)}</dd>            </div>}            <div.ui-cart-sum-line.ui-cart-sum-total><dt>Total</dt><dd>{money_of(grand)}</dd></div>        </dl>        {note != null && <p.ui-cart-sum-note>{note}</p>}        {action != null ? <form.ui-cart-sum-form method="post" action={action}>{button}</form> : button}        {continue_href != null && <p.ui-cart-sum-more><a href={continue_href}>Continue shopping</a></p>}    </section>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

On CartSummary: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.