Commerce

WishlistButton

A heart that saves a product for later: a pressed-state toggle, named for screen readers, with a hidden input for forms.

#commerce#button#forms#interactive

Demo

Preview

Not saved.

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

Example

The code of the preview above.

demos/wishlist_button.qwe
component WishlistButtonDemoLive() {    state saved = false    <div.demo-row>        <WishlistButton id="sku-7" text={true} on_change={(on) => { saved = on }}/>        <p.demo-note>{saved ? "Saved for later (on_change)." : "Not saved."}</p>    </div>}view WishlistButtonDemo() =    <div.demo-stack>        <WishlistButtonDemoLive/>        <div.demo-row style="gap: 20px">            <WishlistButton name="wish" id="sku-1"/>            <WishlistButton saved={true} size="sm"/>            <WishlistButton size="lg"/>            <WishlistButton variant="plain" text={true} label="Save for later" saved_label="Saved for later"/>        </div>        <div style="position: relative; width: 160px; height: 110px; border-radius: var(--ui-radius); background: var(--ui-subtle)">            <span style="position: absolute; top: 8px; right: 8px"><WishlistButton/></span>        </div>    </div>

Source

ui/wishlist_button.qwe
// WishlistButton: a heart that saves a product for later and shows it is saved.// It says its state (aria-pressed) and keeps a hidden input, so a plain <form>// sends `name` while it is on.////   <WishlistButton id="sku-1"/>//   <WishlistButton saved={true} text={true} on_change={(on) => { saved = on }}/>//// saved: on to start with. text: the words beside the heart ("Save" / "Saved";// false, only the heart, named for screen readers and the tooltip). label,// saved_label: other words ("Add to wishlist", "Saved to wishlist"). name: the// hidden input's name, sent as value (the id, or "on") while saved. id: the// product's id. size: "sm", "md" (default) or "lg". variant: "round" (default,// a white disc for a picture's corner) or "plain" (just the heart, inline).// on_change(saved): inside one of your components, given true or false.import ui/theme.qwe use ui_clslet wishlist_button_css = css`.ui-wish { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 36px; min-width: 36px; padding: 0;  border: 1px solid var(--ui-border); border-radius: 999px; background: var(--ui-surface); color: var(--ui-muted);  font: 500 13px/1 var(--ui-font); cursor: pointer; box-shadow: var(--ui-shadow); transition: color .15s, background .15s, transform .15s; }.ui-wish-text { padding: 0 14px 0 10px; }.ui-wish:hover { color: var(--ui-danger); }.ui-wish:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-wish svg { width: 18px; height: 18px; flex: none; transition: transform .2s; }.ui-wish-on { color: var(--ui-danger); }.ui-wish-on svg { animation: ui-wish-pop .3s ease-out; }@keyframes ui-wish-pop { 40% { transform: scale(1.3); } }.ui-wish-plain { border: 0; background: transparent; box-shadow: none; height: auto; min-width: 0; }.ui-wish-plain.ui-wish-text { padding: 0; }.ui-wish-sm { height: 30px; min-width: 30px; font-size: 12px; }.ui-wish-sm svg { width: 15px; height: 15px; }.ui-wish-lg { height: 44px; min-width: 44px; font-size: 14px; }.ui-wish-lg svg { width: 22px; height: 22px; }@media (prefers-reduced-motion: reduce) { .ui-wish, .ui-wish svg { transition: none; animation: none; } }`component WishlistButton(saved: bool = false, text: bool = false, label: str = "Add to wishlist",                         saved_label: str = "Saved to wishlist", name: str? = null, id: str? = null, size: str = "md",                         variant: str = "round", on_change = null, class: str? = null) {    style global wishlist_button_css    state on = saved    let says = on ? saved_label : label    <button type="button" aria-pressed={on ? "true" : "false"} aria-label={says} title={says} data-product={id}            class={ui_cls(["ui-wish", on && "ui-wish-on", text && "ui-wish-text", variant == "plain" && "ui-wish-plain",                           size != "md" && `ui-wish-${size}`, class])}            @click={() => {                on = !on                on_change && on_change(on)            }}>        <svg viewBox="0 0 24 24" fill={on ? "currentColor" : "none"} stroke="currentColor" stroke-width="2" stroke-linecap="round"             stroke-linejoin="round" aria-hidden="true"><path d="M12 20.5s-7.5-4.6-9.3-9.2C1.4 8 3.3 4.5 6.8 4.5c2 0 3.6 1.1 5.2 3 1.6-1.9 3.2-3 5.2-3 3.5 0 5.4 3.5 4.1 6.8-1.8 4.6-9.3 9.2-9.3 9.2z"/></svg>        {text && <span aria-hidden="true">{on ? "Saved" : "Save"}</span>}        {name != null && <input type="hidden" name={name} value={id ?? "on"} disabled={!on}/>}    </button>}
Thread
76Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.