DataList
Records as rows with a title, a line of facts, a status badge and actions as links or buttons.
Demo
- On track
New home page, pricing and docs layout.
- At risk
Hiring: support lead
Invoice #1041
OverdueInvoice #1042
Open
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.qwemain.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>}Related
DescriptionListTerms and their details, stacked or in two columns that stack on phones, on a real <dl>.5 views · 7 downloadsActivityFeedA stream of what people did, with their pictures, an icon for each kind of action, when, quotes and day headings.5 views · 7 downloadsChangelogReleases with version, date and label, their changes grouped by kind: Added, Changed, Fixed, Security and the rest.6 views · 7 downloadsFileListFiles as a table or a grid, with kind icons, sizes and dates, sorted, picked, downloaded or deleted.7 views · 6 downloadsKanbanBoardColumns of cards moved by dragging, by keyboard (Space, the arrows, Space) or with each card's arrows.9 views · 6 downloadsLeaderboardPeople ranked by score with medals for the top three, shared places for ties, and your own row kept in view.7 views · 7 downloads
Thread
Thread 0 posts
On DataList: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.