Data

DataList

Records as rows with a title, a line of facts, a status badge and actions as links or buttons.

#data#list#record

Demo

Preview
  • Invoice #1041

    Overdue
    • Northwind Traders
    • $1,240.50
    • Due Nov 4
  • Invoice #1042

    Open
    • Acme Corp
    • $310.00
    • Due Nov 18

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

Example

The code of the preview above.

demos/data_list.qwe
component DataListDemoLive() {    state invoices = [        { id: "inv-1041", title: "Invoice #1041", icon_text: "PDF", meta: ["Northwind Traders", "$1,240.50", "Due Nov 4"], badge: "Overdue", badge_tone: "danger",          actions: [{ label: "Remind", key: "remind" }, { label: "Void", key: "void", tone: "danger" }] },        { id: "inv-1042", title: "Invoice #1042", icon_text: "PDF", meta: ["Acme Corp", "$310.00", "Due Nov 18"], badge: "Open", badge_tone: "info",          actions: [{ label: "Remind", key: "remind" }, { label: "Void", key: "void", tone: "danger" }] },    ]    state last = "nothing yet"    <div.demo-stack>        <DataList id="data-list-demo-invoices" label="Invoices" items={invoices} empty="No invoices left."                  on_action={(rid, key) => {                      last = `${key} ${rid}`                      if key == "void" { invoices = invoices | filter((x) => x.id != rid) }                  }}/>        <p.demo-note>on_action: {last}.</p>    </div>}view DataListDemo() =    <div.demo-stack>        <DataList id="data-list-demo-projects" label="Projects" items={[            { id: "p1", title: "Website redesign", href: "#p1", meta: ["Design", "Due Nov 4", "6 tasks"], badge: "On track", badge_tone: "success",              description: "New home page, pricing and docs layout.", actions: [{ label: "Open", href: "#p1" }] },            { id: "p2", title: "Billing migration", href: "#p2", meta: ["Engineering", "Due Dec 1", "14 tasks"], badge: "At risk", badge_tone: "warning",              actions: [{ label: "Open", href: "#p2" }] },            { id: "p3", title: "Hiring: support lead", meta: ["People", "No due date"] },        ]}/>        <DataListDemoLive/>    </div>

Source

ui/data_list.qwe
// DataList: records as a list of rows, each with a title line, a line of facts// (meta), an optional description, a status badge and actions at the right:// projects, pull requests, invoices, members. Lighter than a table, and it reads// well on a phone.////   <DataList label="Projects" items={[//       { id: "p1", title: "Website redesign", href: "/p/1", meta: ["Design", "Due Nov 4", "6 tasks"],//         badge: "On track", badge_tone: "success", actions: [{ label: "Open", href: "/p/1" }] },//   ]}/>//   <DataList items={invoices} on_action={(id, action) => { run(action, id) }}/>    buttons, inside your component//// items: maps of { id?, title, href?, meta?, description?, badge?, badge_tone?,// avatar?, icon_text?, actions? }: meta is a list of short facts shown with dots// between them; avatar a picture's URL, or icon_text a few letters in a square// ("PDF"); badge a status ("neutral" default tone, or "accent", "success",// "warning", "danger", "info"); actions a list of { label, href?, key?, tone? }:// with href each is a link; otherwise, inside one of your components,// on_action(id, key) is called with the item's id (its place by default) and the// action's key (its label by default). tone "danger" colours one. empty: what it// says with no items. dividers: lines between rows (true). label: the list's// name for screen readers. id: the element's id.import ui/theme.qwe use ui_clslet data_list_css = css`.ui-datalist { 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.45 var(--ui-font); color: var(--ui-fg); container-type: inline-size; }.ui-datalist-row { display: flex; align-items: center; gap: 14px; padding: 12px 16px; }.ui-datalist-lines .ui-datalist-row + .ui-datalist-row { border-top: 1px solid var(--ui-border); }.ui-datalist-pic { position: relative; flex: none; display: grid; place-items: center; width: 40px; height: 40px; overflow: hidden;  border-radius: var(--ui-radius-sm); background: var(--ui-accent-soft); color: var(--ui-accent); font: 700 11px/1 var(--ui-font); letter-spacing: .03em; }.ui-datalist-pic img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }.ui-datalist-main { flex: 1; min-width: 0; display: grid; gap: 2px; }.ui-datalist-top { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }.ui-datalist-title { min-width: 0; margin: 0; overflow: hidden; font-size: 14px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; color: inherit; }.ui-datalist-title a { color: inherit; text-decoration: none; }.ui-datalist-title a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-datalist-title a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-datalist-badge { flex: none; padding: 1px 8px; border-radius: 999px; font-size: 12px; font-weight: 600; --t: var(--ui-muted);  color: var(--t); background: color-mix(in srgb, var(--t) 13%, transparent); }.ui-datalist-accent { --t: var(--ui-accent); }.ui-datalist-success { --t: var(--ui-success); }.ui-datalist-warning { --t: var(--ui-warning); }.ui-datalist-danger { --t: var(--ui-danger); }.ui-datalist-info { --t: var(--ui-info); }.ui-datalist-meta { display: flex; flex-wrap: wrap; gap: 2px 0; margin: 0; padding: 0; list-style: none; color: var(--ui-muted); font-size: 13px; }.ui-datalist-meta li + li::before { content: "·"; margin: 0 7px; }.ui-datalist-desc { margin: 2px 0 0; color: var(--ui-muted); font-size: 13px; }.ui-datalist-actions { flex: none; display: flex; gap: 6px; }.ui-datalist-act { display: inline-flex; align-items: center; height: 30px; padding: 0 11px; border: 1px solid var(--ui-border);  border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 500 13px/1 var(--ui-font);  text-decoration: none; cursor: pointer; }.ui-datalist-act:hover { border-color: var(--ui-strong); background: var(--ui-subtle); }.ui-datalist-act:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-datalist-act.ui-datalist-danger { color: var(--ui-danger); }.ui-datalist-act.ui-datalist-danger:hover { border-color: var(--ui-danger); background: var(--ui-danger-soft); }.ui-datalist-empty { padding: 28px 16px; color: var(--ui-muted); text-align: center; }@container (max-width: 480px) {  .ui-datalist-row { flex-wrap: wrap; }  .ui-datalist-actions { width: 100%; padding-left: 54px; }}`view DataList(items: list, on_action = null, empty: str = "Nothing here yet.", dividers: bool = true, label: str? = null,              id: str? = null, class: str? = null) {    style global data_list_css    <ul class={ui_cls(["ui-datalist", dividers && "ui-datalist-lines", class])} id={id} aria-label={label}>        {len(items) == 0 && <li.ui-datalist-empty>{empty}</li>}        {for [i, it] in enumerate(items) => {            let rid = it.id ?? i            let acts = it.actions ?? []            <li.ui-datalist-row data-id={str(rid)}>                {(it.avatar != null || it.icon_text != null) && <span.ui-datalist-pic aria-hidden="true">                    {it.icon_text}                    {it.avatar != null && <img src={it.avatar} alt="" loading="lazy"/>}                </span>}                <div.ui-datalist-main>                    <div.ui-datalist-top>                        <p.ui-datalist-title>{it.href != null ? <a href={it.href}>{it.title}</a> : it.title}</p>                        {it.badge != null && <span class={`ui-datalist-badge ui-datalist-${it.badge_tone ?? "neutral"}`}>{it.badge}</span>}                    </div>                    {len(it.meta ?? []) > 0 && <ul.ui-datalist-meta>{for m in it.meta => <li>{m}</li>}</ul>}                    {it.description != null && <p.ui-datalist-desc>{it.description}</p>}                </div>                {len(acts) > 0 && <div.ui-datalist-actions>                    {for a in acts => {                        let cls = ui_cls(["ui-datalist-act", a.tone == "danger" && "ui-datalist-danger"])                        let named = `${a.label}: ${it.title}`                        a.href != null                            ? <a class={cls} href={a.href} aria-label={named}>{a.label}</a>                            : <button class={cls} type="button" aria-label={named} data-action={a.key ?? a.label}                                      @click={() => { on_action && on_action(rid, a.key ?? a.label) }}>{a.label}</button>                    }}                </div>}            </li>        }}    </ul>}
Thread
68Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.