Commerce

AddressCard

A saved address with a Default badge and Edit and Remove, as submit buttons in a plain form or callbacks.

#commerce#checkout#card#record#user#no-js

Demo

Preview
ShippingDefault
Ada Lovelace12 St James's Square
London SW1Y 4JH
United Kingdom+44 20 7946 0000
Billing
Grace Hopper1 Infinite Loop
Cupertino, CA 95014
United Statesgrace@example.com

Edit and Remove call on_edit and on_remove.

Linus TorvaldsKaivokatu 8
Helsinki 00100
Finland

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

Example

The code of the preview above.

demos/address_card.qwe
component AddressCardDemoLive() {    state did = null    <div.demo-stack style="width: 300px">        <AddressCard id="a2" name="Grace Hopper" street="1 Infinite Loop" city="Cupertino" region="CA" postcode="95014" country="United States"                     email="grace@example.com" kind="Billing" on_edit={() => { did = "edit" }} on_remove={() => { did = "remove" }}/>        <p.demo-note>{did == null ? "Edit and Remove call on_edit and on_remove." : `Clicked ${did}.`}</p>    </div>}view AddressCardDemo() =    <div.demo-row style="align-items: flex-start; gap: 20px">        <div style="width: 300px">            <AddressCard id="a1" name="Ada Lovelace" lines={["12 St James's Square", "London SW1Y 4JH", "United Kingdom"]}                         phone="+44 20 7946 0000" kind="Shipping" default={true} action="/addresses" removable={false}/>        </div>        <AddressCardDemoLive/>        <div style="width: 300px">            <AddressCard name="Linus Torvalds" street="Kaivokatu 8" city="Helsinki" postcode="00100" country="Finland" selected={true} edit_href="#address_card"/>        </div>    </div>

Source

ui/address_card.qwe
// AddressCard: a saved address: the name, the lines, the phone, a Default// badge, and Edit and Remove. In a plain <form> the two are submit buttons// (`action`), so they work without a script; inside one of your components// they call on_edit() and on_remove().////   <AddressCard id="a1" name="Ada Lovelace" lines={["12 St James's Square", "London SW1Y 4JH", "United Kingdom"]}//                phone="+44 20 7946 0000" kind="Shipping" default={true} action="/addresses"/>//   <AddressCard name="Ada Lovelace" street="12 St James's Square" city="London" postcode="SW1Y 4JH" country="United Kingdom"//                on_edit={() => { editing = true }} on_remove={() => { gone = true }}/>//// name: whose address. lines: its lines, as written; or street, street2, city,// region, postcode, country, laid out "city, region postcode" and the country// last. phone, email: lines under it with their links. kind: a word above// ("Shipping", "Billing"). default: the Default badge; default_label other// words. id: the address's id. action: a URL the buttons post to, the id as// `address` and `do` as "edit" or "remove"; edit_href makes Edit a link// instead. on_edit(), on_remove(): inside one of your components. removable:// false hides Remove (a default address, say). selected: outlined in the accent,// for a list to pick from.import ui/theme.qwe use ui_clslet address_card_css = css`.ui-address { position: relative; display: grid; gap: 8px; min-width: 0; padding: 16px 18px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius);  background: var(--ui-surface); font: 14px/1.5 var(--ui-font); color: var(--ui-fg); }.ui-address-selected { border-color: var(--ui-accent); box-shadow: inset 0 0 0 1px var(--ui-accent); }.ui-address-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }.ui-address-kind { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--ui-muted); }.ui-address-default { display: inline-flex; align-items: center; height: 20px; padding: 0 8px; border-radius: 999px; background: var(--ui-accent-soft); color: var(--ui-accent); font: 600 11px/1 var(--ui-font); }.ui-address-body { margin: 0; font-style: normal; }.ui-address-name { display: block; font-weight: 600; }.ui-address-contact { display: grid; gap: 2px; margin: 0; font-size: 13px; color: var(--ui-muted); }.ui-address-contact a { color: inherit; text-decoration: none; }.ui-address-contact a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-address-actions { display: flex; gap: 14px; margin-top: 2px; padding-top: 10px; border-top: 1px solid var(--ui-border); font-size: 13px; }.ui-address-actions a, .ui-address-actions button { padding: 0; border: 0; background: none; color: var(--ui-accent); font: inherit; font-weight: 500; text-decoration: none; cursor: pointer; border-radius: 3px; }.ui-address-actions a:hover, .ui-address-actions button:hover { text-decoration: underline; }.ui-address-actions a:focus-visible, .ui-address-actions button:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-address-actions .ui-address-remove { color: var(--ui-danger); }.ui-address-form { display: contents; }`view AddressCard(name: str? = null, lines: list = [], street: str? = null, street2: str? = null, city: str? = null, region: str? = null,                 postcode: str? = null, country: str? = null, phone: str? = null, email: str? = null, kind: str? = null,                 default: bool = false, default_label: str = "Default", id: str? = null, action: str? = null, edit_href: str? = null,                 removable: bool = true, selected: bool = false, on_edit = null, on_remove = null, class: str? = null) {    style global address_card_css    let town = join([city, region] | filter((x) => x != null && x != ""), ", ")    let place = trim(town + (postcode != null ? ` ${postcode}` : ""))    let made = len(lines) > 0 ? lines : [street, street2, place, country] | filter((l) => l != null && l != "")    let has_actions = action != null || edit_href != null || on_edit != null || (removable && on_remove != null)    let edit = action != null        ? <button type="submit" name="do" value="edit">Edit<span.ui-sr> address{name != null && ` of ${name}`}</span></button>        : edit_href != null ? <a href={edit_href}>Edit<span.ui-sr> address{name != null && ` of ${name}`}</span></a>        : on_edit != null ? <button type="button" @click={() => { on_edit() }}>Edit<span.ui-sr> address{name != null && ` of ${name}`}</span></button> : null    let remove = !removable ? null : action != null        ? <button.ui-address-remove type="submit" name="do" value="remove">Remove<span.ui-sr> address{name != null && ` of ${name}`}</span></button>        : on_remove != null ? <button.ui-address-remove type="button" @click={() => { on_remove() }}>Remove<span.ui-sr> address{name != null && ` of ${name}`}</span></button> : null    let actions = <div.ui-address-actions>{edit}{remove}</div>    <div class={ui_cls(["ui-address", selected && "ui-address-selected", class])} data-address={id}>        {(kind != null || default) && <div.ui-address-head>            {kind != null && <span.ui-address-kind>{kind}</span>}            {default && <span.ui-address-default>{default_label}</span>}        </div>}        <address.ui-address-body>            {name != null && <span.ui-address-name>{name}</span>}            {for [i, l] in enumerate(made) => [i > 0 && <br/>, l]}            {(phone != null || email != null) && <span.ui-address-contact>                {phone != null && <a href={`tel:${replace(phone, " ", "")}`}>{phone}</a>}                {email != null && <a href={`mailto:${email}`}>{email}</a>}            </span>}        </address>        {has_actions && (action != null            ? <form.ui-address-form method="post" action={action}>                  {id != null && <input type="hidden" name="address" value={id}/>}                  {actions}              </form>            : actions)}    </div>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.