DropdownMenu
A button that opens a short list of links and actions; Escape or a click elsewhere closes it.
Demo
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/dropdown_menu.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/dropdown_menu.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/dropdown_menu.qwe
component DropdownMenuDemoLive() { state picked = "nothing yet" <div.demo-row> <DropdownMenu id="dropdown-demo-sort" label="Sort by" variant="soft" items={[{ label: "Newest" }, { label: "Oldest" }, { label: "Most liked" }]} on_select={(it) => { picked = it.label }}/> <span.demo-note>Picked: {picked}</span> </div>}view DropdownMenuDemo() = <div.demo-stack> <div.demo-row> <DropdownMenu id="dropdown-demo-project" label="Project" items={[ { label: "Settings", href: "#dropdown_menu" }, { label: "Export", href: "#dropdown_menu" }, { label: "Duplicate", disabled: true }, { divider: true }, { label: "Delete project", href: "#dropdown_menu", tone: "danger" } ]}/> <DropdownMenu id="dropdown-demo-account" label="Account" align="end" variant="ghost" items={[ { label: "Profile", href: "#dropdown_menu" }, { label: "Sign out", href: "#dropdown_menu" } ]}/> </div> <DropdownMenuDemoLive/> </div>Source
ui/dropdown_menu.qwe
// DropdownMenu: a button that opens a short list of links and actions under it.// It is the browser's `popover`, so Escape or a click elsewhere closes it, and// Tab moves through the items. Where the browser can anchor it (CSS anchor// positioning) it opens under the button; elsewhere, in the middle of the screen.//// <DropdownMenu id="project-menu" label="Project" items={[// { label: "Settings", href: "/project/settings" },// { label: "Export", href: "/project/export" },// { divider: true },// { label: "Delete project", href: "/project/delete", tone: "danger" }// ]}/>//// id (required): unique on the page; the button opens the menu by it.// label: the button's text. variant: "outline" (default), "soft", "ghost" or// "solid", as a Button (it is styled as one when button_css is on the page).// align: "start" (default: the menu's left edge under the button's) or "end".// items: a list of { label, href?, tone?: "danger", disabled?: true }, or// { divider: true } for a line between groups. An item with an href is a link;// one without is a button that closes the menu and, inside one of your// components, calls on_select(item).import ui/theme.qwe use ui_clslet dropdown_menu_css = css`.ui-dropdown-btn svg { width: 16px; height: 16px; margin-right: -4px; transition: transform .15s; }.ui-dropdown-btn:has(+ :popover-open) svg { transform: rotate(180deg); }.ui-dropdown { min-width: 190px; max-width: min(320px, calc(100vw - 16px)); margin: auto; padding: 5px; 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.4 var(--ui-font);}.ui-dropdown ul { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; }.ui-dropdown-item { display: flex; align-items: center; width: 100%; padding: 8px 10px; border: 0; border-radius: var(--ui-radius-sm); background: transparent; color: var(--ui-fg); font: inherit; text-align: left; text-decoration: none; cursor: pointer;}.ui-dropdown-item:hover, .ui-dropdown-item:focus-visible { outline: none; background: var(--ui-subtle); }.ui-dropdown-item:focus-visible { box-shadow: var(--ui-focus); }.ui-dropdown-item[disabled], .ui-dropdown-item[aria-disabled="true"] { opacity: .5; cursor: not-allowed; pointer-events: none; }.ui-dropdown-danger { color: var(--ui-danger); }.ui-dropdown-danger:hover, .ui-dropdown-danger:focus-visible { background: var(--ui-danger-soft); }.ui-dropdown-sep { height: 1px; margin: 4px 6px; background: var(--ui-border); }.ui-dropdown[popover]:popover-open { animation: ui-dropdown-in .12s ease-out; }@keyframes ui-dropdown-in { from { opacity: 0; transform: translateY(-4px); } }@supports (position-area: bottom) { .ui-dropdown { inset: auto; margin: 4px 0; position-area: bottom span-right; position-try-fallbacks: flip-block, flip-inline; } .ui-dropdown-end { position-area: bottom span-left; }}@supports not (position-area: bottom) { .ui-dropdown::backdrop { background: var(--ui-overlay); } }@media (prefers-reduced-motion: reduce) { .ui-dropdown[popover]:popover-open { animation: none; } .ui-dropdown-btn svg { transition: none; } }`view DropdownMenu(id: str, label: str = "Options", items: list = [], align: str = "start", variant: str = "outline", size: str = "md", on_select = null, class: str? = null) { style global dropdown_menu_css <span class={class}> <button type="button" popovertarget={id} class={ui_cls(["ui-btn", `ui-btn-${variant}`, size != "md" && `ui-btn-${size}`, "ui-dropdown-btn"])} style={`anchor-name: --${id}`}> {label} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg> </button> <div id={id} popover="auto" class={ui_cls(["ui-dropdown", align == "end" && "ui-dropdown-end"])} style={`position-anchor: --${id}`}> <ul> {for it in items => get(it, "divider") == true ? <li.ui-dropdown-sep role="separator"></li> : <li>{get(it, "href") != null ? <a class={ui_cls(["ui-dropdown-item", get(it, "tone") == "danger" && "ui-dropdown-danger"])} href={it.href} aria-disabled={get(it, "disabled") == true ? "true" : null}>{it.label}</a> : <button type="button" popovertarget={id} popovertargetaction="hide" class={ui_cls(["ui-dropdown-item", get(it, "tone") == "danger" && "ui-dropdown-danger"])} disabled={get(it, "disabled") == true} @click={() => { on_select && on_select(it) }}>{it.label}</button>}</li>} </ul> </div> </span>}Related
SpeedDialA floating action button that opens a column of smaller labelled actions, with the keyboard and a click elsewhere handled.5 views · 8 downloadsSplitButtonThe usual action as a button, joined to a menu of the other ways to do it, with the keyboard.5 views · 4 downloadsBookmarkButtonA save-for-later toggle whose bookmark fills when pressed, with or without its label.8 views · 8 downloadsButtonA button, or a link that looks like one: four variants, three tones, three sizes, loading.7 views · 7 downloadsButtonGroupButtons joined edge to edge into one control, or spaced in a row; across or upright.6 views · 6 downloadsCopyButtonCopies text to the clipboard and says so for a moment, to the eye and to screen readers.4 views · 6 downloads
Thread
Thread 0 posts
On DropdownMenu: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.