Overlays

Popover

A small panel of anything beside a button, on the browser's popover: Escape and clicks outside close it.

#overlay#popover#no-js

Demo

Preview

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

Example

The code of the preview above.

demos/popover.qwe
view PopoverDemo() =    <div.demo-row>        <Popover id="popover-demo-share" trigger="Share" title="Share this page">            <p>Anyone with the link can view it.</p>            <Button size="sm" full={true}>Copy link</Button>        </Popover>        <Popover id="popover-demo-top" trigger="Above" placement="top" width={220}>            <p>Opens above the button, or below it when there is no room.</p>        </Popover>        <Popover id="popover-demo-right" trigger="To the right" placement="right" variant="ghost" width={220}>            <p>Any content: Escape or a click elsewhere closes it.</p>        </Popover>    </div>

Source

ui/popover.qwe
// Popover: a button that opens a small panel beside it, of anything: a short// form, a colour picker, details. It is the browser's `popover`, so Escape or a// click elsewhere closes it. Where the browser can anchor it (CSS anchor// positioning) it opens beside the button, turning to the other side if there// is no room; elsewhere, in the middle of the screen.////   <Popover id="share" trigger="Share" title="Share this page">//       <p>Anyone with the link can view it.</p>//   </Popover>//// id (required): unique on the page; the button opens the panel by it.// trigger: the button's text, styled as a Button (when button_css is on the page)// of `variant` ("outline", the default, "soft", "ghost" or "solid").// title: a heading for the panel, with a close button; also its name for screen// readers (else the trigger's text is).// placement: "bottom" (default), "top", "left", "right", "bottom-start",// "bottom-end", "top-start" or "top-end". width: the panel's width in pixels (300).import ui/theme.qwe use ui_clslet popover_css = css`.ui-popover {  box-sizing: border-box; width: min(var(--w, 300px), calc(100vw - 16px)); max-height: calc(100dvh - 16px); margin: auto;  padding: 14px 16px; 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: 14px/1.5 var(--ui-font);}.ui-popover > :first-child { margin-top: 0; }.ui-popover > :last-child { margin-bottom: 0; }.ui-popover-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }.ui-popover-head h2 { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.3; }.ui-popover-x {  display: grid; place-items: center; flex: none; width: 26px; height: 26px; margin: -4px -6px -4px 0; border: 0; border-radius: var(--ui-radius-sm);  background: transparent; color: var(--ui-muted); cursor: pointer;}.ui-popover-x:hover { background: var(--ui-subtle); color: var(--ui-fg); }.ui-popover-x:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-popover[popover]:popover-open { animation: ui-popover-in .12s ease-out; }@keyframes ui-popover-in { from { opacity: 0; } }@supports (position-area: bottom) {  .ui-popover {    inset: auto; margin: 8px;    position-try-fallbacks: flip-block, flip-inline, top, bottom;  }}@supports not (position-area: bottom) { .ui-popover::backdrop { background: var(--ui-overlay); } }@media (prefers-reduced-motion: reduce) { .ui-popover[popover]:popover-open { animation: none; } }`fn popover_area(placement: str) =    match placement {        "top" => "top"        "left" => "left"        "right" => "right"        "bottom-start" => "bottom span-right"        "bottom-end" => "bottom span-left"        "top-start" => "top span-right"        "top-end" => "top span-left"        _ => "bottom"    }view Popover(id: str, trigger: str, title: str? = null, placement: str = "bottom", width: int = 300,             variant: str = "outline", class: str? = null, children) {    style global popover_css    <span class={class}>        <button type="button" popovertarget={id} class={ui_cls(["ui-btn", `ui-btn-${variant}`])}                style={`anchor-name: --${id}`}>{trigger}</button>        <div id={id} popover="auto" class="ui-popover" role="dialog" aria-label={title ?? trigger}             style={`--w: ${width}px; position-anchor: --${id}; position-area: ${popover_area(placement)}`}>            {title != null && <div.ui-popover-head>                <h2>{title}</h2>                <button.ui-popover-x type="button" popovertarget={id} popovertargetaction="hide" aria-label="Close">                    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"                         stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>                </button>            </div>}            {children}        </div>    </span>}
Thread
55Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.