Dashboard

QuickActions

A grid of big icon buttons for the common actions: links, submit buttons in a form, or on_select.

#dashboard#button#grid#navigation#forms#no-js

Demo

Preview

Pick a button (not a link) to call on_select.

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

Example

The code of the preview above.

demos/quick_actions.qwe
let quick_actions_demo_icon = (d: str) =>    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d={d}/></svg>component QuickActionsDemoLive() {    state picked = null    <div.demo-stack>        <QuickActions id="quick-actions-demo" on_select={(a) => { picked = a.label }} actions={[            { label: "New order", href: "#quick_actions", icon: quick_actions_demo_icon("M12 5v14M5 12h14") },            { label: "Add customer", icon: quick_actions_demo_icon("M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM19 8v6M22 11h-6"), tone: "success" },            { label: "Export", hint: "CSV of this month", icon: quick_actions_demo_icon("M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"), tone: "info" },            { label: "Billing", href: "#quick_actions", icon: "💳", tone: "warning" },            { label: "Settings", href: "#quick_actions", icon: quick_actions_demo_icon("M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"), tone: "neutral" },            { label: "Delete all", icon: "🗑️", tone: "danger", disabled: true },        ]}/>        <p.demo-note>{picked == null ? "Pick a button (not a link) to call on_select." : `Picked: ${picked}.`}</p>    </div>}view QuickActionsDemo() =    <div.demo-stack>        <QuickActionsDemoLive/>        <form method="post" action="#quick_actions">            <QuickActions columns={3} label="Reports" actions={[                { label: "Sales", name: "report", value: "sales", icon: "📈" },                { label: "Stock", name: "report", value: "stock", icon: "📦" },                { label: "Taxes", name: "report", value: "taxes", icon: "🧾" },            ]}/>        </form>    </div>

Source

ui/quick_actions.qwe
// QuickActions: the things people do most, as a grid of big buttons with an// icon and a word each: New order, Add customer, Export.////   <QuickActions actions={[//       { label: "New order", href: "/orders/new", icon: "➕" },//       { label: "Export", icon: <svg>...</svg>, hint: "CSV of this month", name: "export", value: "csv" },//       { label: "Billing", href: "/billing", icon: "💳", tone: "warning" },//   ]}/>//   <QuickActions actions={actions} on_select={(a) => { do_it(a) }}/>//// actions: a list of { label, icon?, href?, hint?, tone?, name?, value?, disabled? }.// One with an href is a link; without, a button: with a name and value a submit// button in the form around it, else a plain button that, inside one of your// components, calls on_select(action) with the map. icon: HTML (an SVG) or a short// text (an emoji). hint: a small line under the label. tone: the icon's colour,// "accent" (default), "neutral", "success", "warning", "danger" or "info".// columns: a fixed number; without it, as many as fit at min pixels wide (130).// label: the group's name for screen readers ("Quick actions").import ui/theme.qwe use ui_clslet quick_actions_css = css`.ui-qa { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none;  grid-template-columns: repeat(auto-fill, minmax(min(var(--ui-qa-min, 130px), 100%), 1fr)); font: 14px/1.3 var(--ui-font); }.ui-qa-cols { grid-template-columns: repeat(var(--ui-qa-cols), minmax(0, 1fr)); }.ui-qa-item { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; width: 100%; min-height: 104px;  padding: 16px 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface);  color: var(--ui-fg); font: inherit; text-align: center; text-decoration: none; cursor: pointer; --t: var(--ui-accent); --ts: var(--ui-accent-soft);  transition: border-color .15s, box-shadow .15s, transform .15s; }.ui-qa-item:hover { border-color: var(--t); box-shadow: var(--ui-shadow); transform: translateY(-1px); }.ui-qa-item:focus-visible { outline: none; border-color: var(--t); box-shadow: var(--ui-focus); }.ui-qa-item:disabled, .ui-qa-item[aria-disabled="true"] { opacity: .5; cursor: not-allowed; pointer-events: none; }.ui-qa-neutral { --t: var(--ui-fg); --ts: var(--ui-subtle); }.ui-qa-success { --t: var(--ui-success); --ts: var(--ui-success-soft); }.ui-qa-warning { --t: var(--ui-warning); --ts: var(--ui-warning-soft); }.ui-qa-danger { --t: var(--ui-danger); --ts: var(--ui-danger-soft); }.ui-qa-info { --t: var(--ui-info); --ts: var(--ui-info-soft); }.ui-qa-icon { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: var(--ts); color: var(--t); font-size: 22px; line-height: 1; }.ui-qa-icon svg { width: 22px; height: 22px; }.ui-qa-label { font-weight: 600; }.ui-qa-hint { color: var(--ui-muted); font-size: 12px; }@media (max-width: 559px) { .ui-qa-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }@media (prefers-reduced-motion: reduce) { .ui-qa-item { transition: none; } .ui-qa-item:hover { transform: none; } }`view QuickActions(actions: list, columns: int? = null, min: int = 130, label: str = "Quick actions", on_select = null,                  id: str? = null, class: str? = null) {    style global quick_actions_css    <ul id={id} class={ui_cls(["ui-qa", columns != null && "ui-qa-cols", class])} aria-label={label}        style={columns != null ? `--ui-qa-cols: ${columns}` : `--ui-qa-min: ${min}px`}>        {for a in actions => {            let cls = ui_cls(["ui-qa-item", a.tone != null && a.tone != "accent" && `ui-qa-${a.tone}`])            let body = [                a.icon != null && <span.ui-qa-icon aria-hidden="true">{a.icon}</span>,                <span.ui-qa-label>{a.label}</span>,                a.hint != null && <span.ui-qa-hint>{a.hint}</span>,            ]            <li>{a.href != null                ? <a class={cls} href={a.href} aria-disabled={a.disabled == true ? "true" : null}>{body}</a>                : <button class={cls} type={a.name != null ? "submit" : "button"} name={a.name} value={a.value}                          disabled={a.disabled == true} @click={() => { if a.name == null { on_select && on_select(a) } }}>{body}</button>}</li>        }}    </ul>}
Thread
65Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.