Popover
A small panel of anything beside a button, on the browser's popover: Escape and clicks outside close it.
Demo
Opens above the button, or below it when there is no room.
Any content: Escape or a click elsewhere closes it.
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.qwemain.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>}Related
FlyoutA panel that opens beside its button at a chosen side, keeps open while you work in it, and closes on a click outside.5 views · 10 downloadsHoverCardA card of details on hover or focus, after a short wait, that stays under the pointer; Escape hides it.5 views · 6 downloadsNotificationCenterA bell with the unread count, opening a panel of notifications to filter and mark read, one by one or all at once.5 views · 6 downloadsTooltipA short note shown while something is hovered or focused, in CSS alone, on any of its four sides.5 views · 9 downloadsAlertDialogA modal that only informs, with one OK button, on the browser's own <dialog> and no JavaScript.6 views · 7 downloadsChatWidgetA chat button in the corner that opens a small chat panel, with replies from your server and a count for unseen ones.4 views · 7 downloads
Thread
Thread 0 posts
On Popover: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.