Commerce

CheckoutSteps

Cart → Details → Payment → Done: the steps of a checkout, those done as links back, the current one marked.

#commerce#checkout#navigation#progress#no-js

Demo

Preview
  1. Cart (done)
  2. Details
  3. Payment
  4. Done
  1. Cart (done)
  2. Address (done)
  3. Payment
  4. Review
  1. Cart (done)
  2. Details (done)
  3. Payment (done)
  4. Done (done)
  1. Bag
  2. Pay
  3. Done

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/checkout_steps.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/checkout_steps.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/checkout_steps.qwe
view CheckoutStepsDemo() =    <div.demo-stack style="gap: 24px">        <CheckoutSteps current={1}/>        <CheckoutSteps current={2} steps={[{ label: "Cart", href: "#cart_item" }, { label: "Address", href: "#checkout_steps" }, "Payment", "Review"]}/>        <CheckoutSteps current={4} numbers={false}/>        <div.demo-narrow><CheckoutSteps current={0} steps={["Bag", "Pay", "Done"]}/></div>    </div>

Source

ui/checkout_steps.qwe
// CheckoutSteps: where someone is in a checkout: Cart → Details → Payment →// Done. The steps done are links back when they have an href; the ones to come// are not.////   <CheckoutSteps current={1}/>//   <CheckoutSteps current={2} steps={[{ label: "Cart", href: "/cart" }, { label: "Address", href: "/checkout/address" },//                                      "Payment", "Review"]}/>//// steps: strings, or maps of { label, href? } (["Cart", "Details", "Payment",// "Done"]). current: the index of the step being done, from 0; set it to// len(steps) when all are. label: what screen readers call it ("Checkout").// numbers: a number in each step's circle (true); false shows only dots.// On narrow screens only the current step's name shows.import ui/theme.qwe use ui_clslet checkout_steps_css = css`.ui-checkout-steps { display: flex; align-items: center; margin: 0; padding: 0; list-style: none; font: 14px/1.3 var(--ui-font); color: var(--ui-fg); }.ui-checkout-step { display: flex; align-items: center; gap: 8px; min-width: 0; }.ui-checkout-step a { display: inherit; align-items: inherit; gap: inherit; color: inherit; text-decoration: none; border-radius: var(--ui-radius-sm); }.ui-checkout-step a:hover .ui-checkout-label { color: var(--ui-accent); text-decoration: underline; }.ui-checkout-step a:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-checkout-mark { display: inline-grid; place-items: center; flex: none; width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--ui-border);  background: var(--ui-surface); color: var(--ui-muted); font: 600 12px/1 var(--ui-font); font-variant-numeric: tabular-nums; }.ui-checkout-mark svg { width: 13px; height: 13px; }.ui-checkout-done .ui-checkout-mark { border-color: var(--ui-accent); background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-checkout-current .ui-checkout-mark { border-color: var(--ui-accent); color: var(--ui-accent); }.ui-checkout-label { font-weight: 500; white-space: nowrap; }.ui-checkout-coming .ui-checkout-label { color: var(--ui-muted); font-weight: 400; }.ui-checkout-current .ui-checkout-label { color: var(--ui-accent); }.ui-checkout-sep { flex: 1 1 24px; min-width: 16px; height: 2px; margin: 0 10px; border-radius: 2px; background: var(--ui-border); }.ui-checkout-sep-done { background: var(--ui-accent); }.ui-checkout-dots .ui-checkout-mark { width: 12px; height: 12px; border-width: 2px; font-size: 0; }.ui-checkout-dots .ui-checkout-mark svg { display: none; }@media (max-width: 600px) { .ui-checkout-step:not(.ui-checkout-current) .ui-checkout-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); } }`view CheckoutSteps(steps: list = ["Cart", "Details", "Payment", "Done"], current: int = 0, label: str = "Checkout",                   numbers: bool = true, class: str? = null) {    style global checkout_steps_css    let n = len(steps)    <ol class={ui_cls(["ui-checkout-steps", !numbers && "ui-checkout-dots", class])} aria-label={label}>        {for [i, s] in enumerate(steps) => {            let text = type_of(s) == "str" ? s : s.label            let href = type_of(s) == "map" ? get(s, "href") : null            let phase = i < current ? "done" : i == current ? "current" : "coming"            let inner = [                <span.ui-checkout-mark aria-hidden="true">                    {phase == "done"                        ? <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-7"/></svg>                        : i + 1}                </span>,                <span.ui-checkout-label>{text}{phase == "done" && <span.ui-sr> (done)</span>}</span>,            ]            [                <li class={`ui-checkout-step ui-checkout-${phase}`} aria-current={phase == "current" ? "step" : null}>                    {phase == "done" && href != null ? <a href={href}>{inner}</a> : inner}                </li>,                i < n - 1 && <li class={ui_cls(["ui-checkout-sep", i < current && "ui-checkout-sep-done"])} role="presentation" aria-hidden="true"></li>,            ]        }}    </ol>}
Thread
57Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.