Actions

DropdownMenu

A button that opens a short list of links and actions; Escape or a click elsewhere closes it.

#menu#button#overlay#no-js

Demo

Preview
Picked: nothing yet

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.qwe
main.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>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.