Data

ListGroup

A bordered list of items with descriptions, badges and notes, as links, buttons or text, one active and some disabled.

#data#list#navigation#no-js

Demo

Preview
  • SpamOff

Showing inbox.

  1. Tag the commitdone
  2. Build the binaries3 min
  3. Publish the notes

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

Example

The code of the preview above.

demos/list_group.qwe
component ListGroupDemoLive() {    state folder = "inbox"    <div.demo-stack.demo-narrow>        <ListGroup id="list-group-demo-folders" label="Folders" on_select={(v) => { folder = v }}                   items={[                       { label: "Inbox", value: "inbox", badge: 12, badge_tone: "accent", active: folder == "inbox" },                       { label: "Starred", value: "starred", active: folder == "starred" },                       { label: "Drafts", value: "drafts", description: "Not sent yet", badge: 2, badge_tone: "warning", active: folder == "drafts" },                       { label: "Spam", value: "spam", disabled: true, meta: "Off" },                   ]}/>        <p.demo-note>Showing {folder}.</p>    </div>}view ListGroupDemo() =    <div.demo-stack>        <div.demo-row style="align-items: flex-start; gap: 24px">            <div style="flex: 1 1 260px; max-width: 340px">                <ListGroup id="list-group-demo-links" label="Settings" items={[                    { label: "Profile", href: "#profile", description: "Name, photo and bio", active: true },                    { label: "Notifications", href: "#notifications", badge: "New", badge_tone: "success" },                    { label: "Billing", href: "#billing", meta: "Pro plan" },                    { label: "Danger zone", href: "#danger", badge: 1, badge_tone: "danger" },                ]}/>            </div>            <div style="flex: 1 1 260px; max-width: 340px">                <ListGroupDemoLive/>            </div>        </div>        <div.demo-row style="align-items: flex-start; gap: 24px">            <div style="flex: 1 1 260px; max-width: 340px">                <ListGroup id="list-group-demo-steps" numbered={true} size="sm" label="Release steps" items={[                    { label: "Tag the commit", meta: "done" }, { label: "Build the binaries", meta: "3 min" }, { label: "Publish the notes" },                ]}/>            </div>            <div style="flex: 1 1 260px; max-width: 340px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); padding: 0 12px">                <ListGroup id="list-group-demo-flush" flush={true} label="Recent">                    <ListGroupItem href="#q3" meta="2 h ago">Q3 <b>report</b>.pdf</ListGroupItem>                    <ListGroupItem href="#logo" meta="Yesterday" badge="v2">logo.svg</ListGroupItem>                    <ListGroupItem disabled={true}>archive.zip (deleted)</ListGroupItem>                </ListGroup>            </div>        </div>    </div>

Source

ui/list_group.qwe
// ListGroup: a bordered list of items, each a line with optional text under it,// a badge or a note on the right, as a link, a button or plain; one can be the// active one and some disabled: folders in a mailbox, settings sections, results.////   <ListGroup items={[//       { label: "Inbox", href: "/inbox", badge: 12, active: true },//       { label: "Drafts", href: "/drafts", description: "Not sent yet", badge: 2, badge_tone: "warning" },//       { label: "Spam", disabled: true, meta: "Off" },//   ]}/>//   <ListGroup items={folders} on_select={(v) => { folder = v }}/>     buttons, inside your component//   <ListGroup><ListGroupItem href="/a" active={true}>Any <b>HTML</b></ListGroupItem></ListGroup>//// items: maps of { label, description?, href?, value?, badge?, badge_tone?, meta?,// active?, disabled? }: href makes the item a link; on_select(value) (inside one// of your components) makes every item a button called with its value (the label// by default); else it is plain text. badge: a number or short text in a pill,// badge_tone "neutral" (default), "accent", "success", "warning", "danger" or// "info"; meta: muted text on the right (a time, a size). active: the current// one (aria-current). disabled: greyed, not a link or button.// ListGroupItem, as children of a ListGroup, takes the same props (href,// description, badge, badge_tone, meta, active, disabled) with any HTML as its// text, and on_click() instead of a value. Items come first, then children.// flush: no outer border or rounding, for a list inside a card. size: "sm" or// "md" (default). numbered: an <ol> with numbers. label: the list's name for// screen readers. id: the element's id.import ui/theme.qwe use ui_clslet list_group_css = css`.ui-listgroup { margin: 0; padding: 0; list-style: none; border: 1px solid var(--ui-border); border-radius: var(--ui-radius);  background: var(--ui-surface); font: 14px/1.4 var(--ui-font); color: var(--ui-fg); counter-reset: ui-listgroup; }.ui-listgroup-flush { border: 0; border-radius: 0; background: none; }.ui-listgroup > li + li { border-top: 1px solid var(--ui-border); }.ui-listgroup-item { display: flex; align-items: center; gap: 12px; width: 100%; padding: 11px 14px; border: 0; background: none;  color: inherit; font: inherit; text-align: left; text-decoration: none; }.ui-listgroup > li:first-child > .ui-listgroup-item { border-radius: calc(var(--ui-radius) - 1px) calc(var(--ui-radius) - 1px) 0 0; }.ui-listgroup > li:last-child > .ui-listgroup-item { border-radius: 0 0 calc(var(--ui-radius) - 1px) calc(var(--ui-radius) - 1px); }a.ui-listgroup-item, button.ui-listgroup-item { cursor: pointer; transition: background .15s; }a.ui-listgroup-item:hover, button.ui-listgroup-item:hover { background: var(--ui-subtle); }a.ui-listgroup-item:focus-visible, button.ui-listgroup-item:focus-visible { outline: none; box-shadow: inset var(--ui-focus); }.ui-listgroup-item[aria-current="true"] { background: var(--ui-accent-soft); color: var(--ui-accent); }.ui-listgroup-item[aria-current="true"] .ui-listgroup-desc { color: color-mix(in srgb, var(--ui-accent) 70%, var(--ui-fg)); }.ui-listgroup-item[aria-disabled="true"] { color: var(--ui-muted); cursor: not-allowed; }.ui-listgroup-numbered .ui-listgroup-item::before { counter-increment: ui-listgroup; content: counter(ui-listgroup) "."; flex: none;  min-width: 1.4em; color: var(--ui-muted); font-variant-numeric: tabular-nums; }.ui-listgroup-text { flex: 1; min-width: 0; display: grid; gap: 2px; }.ui-listgroup-label { font-weight: 500; }.ui-listgroup-desc { color: var(--ui-muted); font-size: 13px; }.ui-listgroup-meta { flex: none; color: var(--ui-muted); font-size: 13px; font-variant-numeric: tabular-nums; }.ui-listgroup-badge { flex: none; display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 22px;  padding: 0 7px; border-radius: 999px; font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;  --t: var(--ui-muted); --ts: var(--ui-subtle); background: var(--ts); color: var(--t); }.ui-listgroup-badge-accent { --t: var(--ui-accent); --ts: var(--ui-accent-soft); }.ui-listgroup-badge-success { --t: var(--ui-success); --ts: var(--ui-success-soft); }.ui-listgroup-badge-warning { --t: var(--ui-warning); --ts: var(--ui-warning-soft); }.ui-listgroup-badge-danger { --t: var(--ui-danger); --ts: var(--ui-danger-soft); }.ui-listgroup-badge-info { --t: var(--ui-info); --ts: var(--ui-info-soft); }.ui-listgroup-sm .ui-listgroup-item { padding: 7px 12px; font-size: 13px; }.ui-listgroup-sm .ui-listgroup-badge { min-width: 18px; height: 18px; font-size: 11px; }@media (prefers-reduced-motion: reduce) { a.ui-listgroup-item, button.ui-listgroup-item { transition: none; } }`// One item: a link with href, a button with on_click(), else plain; disabled ones are plain.view ListGroupItem(href: str? = null, description: str? = null, badge = null, badge_tone: str = "neutral", meta: str? = null,                   active: bool = false, disabled: bool = false, on_click = null, class: str? = null, children) {    style global list_group_css    let cls = ui_cls(["ui-listgroup-item", class])    let body = [        <span.ui-listgroup-text>            <span.ui-listgroup-label>{children}</span>            {description != null && <span.ui-listgroup-desc>{description}</span>}        </span>,        meta != null && <span.ui-listgroup-meta>{meta}</span>,        badge != null && <span class={ui_cls(["ui-listgroup-badge", badge_tone != "neutral" && `ui-listgroup-badge-${badge_tone}`])}>{badge}</span>,    ]    let current = active ? "true" : null    <li>        {disabled            ? <div class={cls} aria-disabled="true" aria-current={current}>{body}</div>            : href != null                ? <a class={cls} href={href} aria-current={current}>{body}</a>                : on_click != null                    ? <button class={cls} type="button" aria-current={current} @click={() => { on_click() }}>{body}</button>                    : <div class={cls} aria-current={current}>{body}</div>}    </li>}view ListGroup(items: list = [], on_select = null, flush: bool = false, size: str = "md", numbered: bool = false,               label: str? = null, id: str? = null, class: str? = null, children) {    style global list_group_css    let cls = ui_cls(["ui-listgroup", flush && "ui-listgroup-flush", size == "sm" && "ui-listgroup-sm", numbered && "ui-listgroup-numbered", class])    let rows = [        for it in items => <ListGroupItem href={it.href} description={it.description} badge={it.badge} badge_tone={it.badge_tone ?? "neutral"}                                         meta={it.meta} active={it.active == true} disabled={it.disabled == true}                                         on_click={on_select != null ? (() => { on_select(it.value ?? it.label) }) : null}>{it.label}</ListGroupItem>,        children,    ]    numbered        ? <ol class={cls} id={id} aria-label={label}>{rows}</ol>        : <ul class={cls} id={id} aria-label={label}>{rows}</ul>}
Thread
76Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.