Overlays

QuickView

A Quick view button on a card that opens a preview of the item, its picture, price, text and actions, in a dialog.

#dialog#card#commerce#overlay#interactive

Demo

Preview
Linen shirt
€49
Linen shirt, a placeholder picture

Linen shirt

€49

Loose fit, washed linen, mother-of-pearl buttons.

View details →
Wool scarf
€35
Wool scarf, a placeholder picture

Wool scarf

€35

Merino, knitted in Porto, long enough to wrap twice.

View details →
Canvas tote
€22
Canvas tote, a placeholder picture

Canvas tote

€22

Heavy cotton canvas with a leather strap.

View details →

The cart is empty.

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

Example

The code of the preview above.

demos/quick_view.qwe
let quick_view_demo_items = [    { name: "Linen shirt", price: "€49", text: "Loose fit, washed linen, mother-of-pearl buttons.", hue: "accent" },    { name: "Wool scarf", price: "€35", text: "Merino, knitted in Porto, long enough to wrap twice.", hue: "success" },    { name: "Canvas tote", price: "€22", text: "Heavy cotton canvas with a leather strap.", hue: "warning" },]// A placeholder picture: a tinted square with the item's initial, as an SVG data URL.fn quick_view_demo_img(name: str) =    `data:image/svg+xml,${url_encode(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><rect width="400" height="400" fill="#e8e6f7"/><text x="200" y="240" font-size="140" text-anchor="middle" fill="#5b4bdb" font-family="sans-serif">${slice(name, 0, 1)}</text></svg>`)}`component QuickViewDemoLive() {    state cart = []    <div.demo-stack>        <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 14px">            {for it in quick_view_demo_items => <QuickView key={it.name} title={it.name} image={quick_view_demo_img(it.name)}                    alt={`${it.name}, a placeholder picture`} meta={it.price} text={it.text} href="#quick_view"                    action="Add to cart" on_action={() => { cart = cart | push(it.name) }}>                <div style="overflow: hidden; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface)">                    <img src={quick_view_demo_img(it.name)} alt="" style="display: block; width: 100%; aspect-ratio: 1"/>                    <div style="padding: 10px 12px"><strong>{it.name}</strong><div style="color: var(--ui-muted)">{it.price}</div></div>                </div>            </QuickView>}        </div>        <p.demo-note>{len(cart) == 0 ? "The cart is empty." : `In the cart: ${join(cart, ", ")}.`}</p>    </div>}view QuickViewDemo() = <QuickViewDemoLive/>

Source

ui/quick_view.qwe
// QuickView: a card (yours, as children) with a "Quick view" button on it that opens a// preview of the item in a dialog: its image, title, a line such as the price, a// description, and the actions (a main button and a link to the full page). The// button shows when the card is hovered or has the focus, and always on touch// screens. The dialog is the browser's own <dialog>: focus stays in it, Escape or a// click outside closes it.////   <QuickView title="Linen shirt" image="/img/shirt.jpg" alt="A white linen shirt" meta="€49"//              text="Loose fit, washed linen, mother-of-pearl buttons." href="/shop/linen-shirt">//       <ProductCard product={p}/>//   </QuickView>//   <QuickView title={p.name} image={p.img} action="Add to cart" on_action={() => { add(p) }}> ... </QuickView>//// title: the item's name, the dialog's heading and name. image and alt: its picture// (alt says what it shows). meta: a line under the title (a price, a date). text: the// description. href: a link to the item's own page, "View details" (details: its text).// action: a main button's text; on_action(), inside one of your components, is what it// does (then the dialog closes). label ("Quick view"): the button's text.// children: the card the button sits on.import ui/theme.qwe use ui_clslet quick_view_css = css`.ui-quick-view { position: relative; display: block; }.ui-quick-view-open {  position: absolute; left: 50%; bottom: 14px; z-index: 2; translate: -50% 0; display: inline-flex; align-items: center; gap: 6px;  height: 34px; padding: 0 14px; border: 1px solid var(--ui-border); border-radius: 999px; background: var(--ui-surface); color: var(--ui-fg);  box-shadow: var(--ui-shadow); font: 500 13px/1 var(--ui-font); white-space: nowrap; cursor: pointer;  opacity: 0; transition: opacity .15s, translate .15s;}.ui-quick-view-open svg { width: 16px; height: 16px; }.ui-quick-view:hover .ui-quick-view-open, .ui-quick-view:focus-within .ui-quick-view-open { opacity: 1; translate: -50% -2px; }.ui-quick-view-open:hover { background: var(--ui-subtle); }.ui-quick-view-open:focus-visible { outline: none; box-shadow: var(--ui-shadow), var(--ui-focus); }@media (hover: none) { .ui-quick-view-open { opacity: 1; } }.ui-quick-view-dialog {  width: min(820px, calc(100vw - 32px)); max-height: calc(100dvh - 48px); padding: 0; overflow: auto; border: 1px solid var(--ui-border);  border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow-lg); font: 15px/1.5 var(--ui-font);}.ui-quick-view-dialog[open] { animation: ui-quick-view-in .18s ease-out; }.ui-quick-view-dialog::backdrop { background: var(--ui-overlay); }@keyframes ui-quick-view-in { from { opacity: 0; transform: scale(.97); } }.ui-quick-view-box { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }.ui-quick-view-box.ui-quick-view-plain { grid-template-columns: minmax(0, 1fr); }.ui-quick-view-img { display: block; width: 100%; height: 100%; min-height: 260px; object-fit: cover; background: var(--ui-subtle); }.ui-quick-view-info { display: flex; flex-direction: column; gap: 10px; padding: 28px 26px 24px; }.ui-quick-view-info h2 { margin: 0; padding-right: 30px; font-size: 22px; font-weight: 650; line-height: 1.25; }.ui-quick-view-meta { margin: 0; font-size: 18px; font-weight: 600; color: var(--ui-accent); }.ui-quick-view-text { margin: 0; color: var(--ui-muted); }.ui-quick-view-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: auto; padding-top: 12px; }.ui-quick-view-act {  height: 40px; padding: 0 18px; border: 0; border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg);  font: 600 14px/1 var(--ui-font); cursor: pointer;}.ui-quick-view-act:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }.ui-quick-view-link { color: var(--ui-accent); font-weight: 500; text-underline-offset: 3px; }.ui-quick-view-x {  position: absolute; top: 12px; right: 12px; display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0;  border-radius: 50%; background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow); cursor: pointer;}.ui-quick-view-x:hover { background: var(--ui-subtle); }.ui-quick-view-x svg { width: 18px; height: 18px; }.ui-quick-view-act:focus-visible, .ui-quick-view-x:focus-visible, .ui-quick-view-link:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: var(--ui-radius-sm); }@media (max-width: 640px) {  .ui-quick-view-box { grid-template-columns: minmax(0, 1fr); }  .ui-quick-view-img { min-height: 0; height: 240px; }}@media (prefers-reduced-motion: reduce) { .ui-quick-view-dialog[open] { animation: none; } .ui-quick-view-open { transition: none; } }`component QuickView(title: str, image: str? = null, alt: str = "", meta: str? = null, text: str? = null, href: str? = null,                    details: str = "View details", action: str? = null, on_action = null, label: str = "Quick view",                    class: str? = null, children) {    style global quick_view_css    state dlg = null    <div class={ui_cls(["ui-quick-view", class])}>        {children}        <button.ui-quick-view-open type="button" aria-haspopup="dialog" aria-label={`${label}: ${title}`} @click={() => dlg.showModal()}>            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"                 aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>            <span aria-hidden="true">{label}</span>        </button>        <dialog.ui-quick-view-dialog aria-label={title}                @mount={(el) => { dlg = el }}                @click={(e) => { if e.target.tagName == "DIALOG" { dlg.close() } }}>            <div class={ui_cls(["ui-quick-view-box", image == null && "ui-quick-view-plain"])}>                {image != null && <img.ui-quick-view-img src={image} alt={alt} loading="lazy"/>}                <div.ui-quick-view-info>                    <h2>{title}</h2>                    {meta != null && <p.ui-quick-view-meta>{meta}</p>}                    {text != null && <p.ui-quick-view-text>{text}</p>}                    {(action != null || href != null) && <div.ui-quick-view-acts>                        {action != null && <button.ui-quick-view-act type="button" @click={() => {                            on_action && on_action()                            dlg.close()                        }}>{action}</button>}                        {href != null && <a.ui-quick-view-link href={href}>{details} →</a>}                    </div>}                </div>                <button.ui-quick-view-x type="button" aria-label="Close" @click={() => dlg.close()}>                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"                         aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>                </button>            </div>        </dialog>    </div>}
Thread
55Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.