-
Notifications
You must be signed in to change notification settings - Fork 0
components data table
Import: import { RowActions, DataTable } from "@tundralibs/ui/data-table"
Rendered in every variant on the catalogue (deno task build:demos, then demo/index.html). Guides: Getting started · rAPId integration · Theming.
RowActions(props: RowActionsProps): HtmlRow actions as an in-row strip instead of a floating menu: the kebab
opens a row-height ink strip of labelled buttons anchored at the row's
end (it covers the row's values while open, nothing moves, nothing
floats), with a close button; Escape and an outside click close it and
focus returns to the kebab. The same idiom as the bulk bar — one row
gets a row strip, many rows get the header strip. Pass it as
DataTable.rowActions.
DataTable<T>(props: DataTableProps): Html| Prop | Type | Required | Description |
|---|---|---|---|
key |
string |
yes | |
label |
string |
yes | |
sortable |
boolean |
||
numeric |
boolean |
Right-aligns and applies tabular figures. | |
mono |
boolean |
Monospace + smaller: use for ids, shas, codes. | |
pinned |
boolean |
Sticky first column. Only set this on one column. | |
render |
`(row: T) => Html | string` |
| Prop | Type | Required | Description |
|---|---|---|---|
id |
string |
yes | Required: history push refuses a region with no id. |
title |
string |
||
columns |
DataTableColumn[] |
yes | |
rows |
T[] |
yes | |
rowKey |
(row: T) => string |
yes | |
selectable |
boolean |
||
selected |
string[] |
||
selectName |
string |
Form field name the row checkboxes submit under (default "selected"). | |
sort |
`{ key: string; dir: "asc" | "desc" }` | |
buildSortHref |
`(key: string, dir: "asc" | "desc") => string` | |
bulkActions |
Html |
Bulk actions, shown only when selected is non-empty. With bulkAction set, make each one a submit button that names the operation — Button({ type: "submit", attrs: { name: "op", value: "archive" } }) — and the server receives op plus one selectName entry per checked row. |
|
bulkAction |
string |
URL the selection posts to. When set, the bulk bar and the rows sit in a <form method="post"> carrying data-action (a rAPId swap that replaces this table, outer into #<id>; a plain navigation without the runtime — answer with a redirect, PRG). The toolbar and footer stay outside the form, so a search box or a filter never submits it. Without it the bulk bar is a view concern only and its buttons need their own wiring. |
|
toolbar |
Html |
||
footer |
Html |
||
rowActions |
(row: T) => Html |
||
maxHeight |
`"sm" | "md" | "lg"` |
empty |
Html |
Shown instead of the body when rows is empty — an Empty, or anything. |
|
emptyMessage |
string |
Plain-text alternative to empty. @default "Nothing to show." |
|
attrs |
Attrs |
| Prop | Type | Required | Description |
|---|---|---|---|
label |
string |
yes | |
href |
string |
A link (GET). Without it the item is a <button> — give it attrs (a form's formaction, data-action, …). |
|
danger |
boolean |
The destructive one — rendered last, after a separator, in the danger colour. | |
attrs |
Attrs |
| Prop | Type | Required | Description |
|---|---|---|---|
id |
string |
yes | Base id; the strip is <id>-strip. Derive it from the row key (§4). |
label |
string |
yes | Accessible name of the group, e.g. "Actions for INV-2048". |
items |
RowAction[] |
yes |
Each example as the rAPId call and the HTML it renders — the markup a plain page writes by hand. Icons are inline SVG in the real output; they are shortened to <svg …>…</svg> here.
Sort links swap the table in place and push history; bulk buttons post op plus one selected per checked row through the table's own form; the kebab opens a row action strip.
DataTable<Invoice>({
id: "invoices",
title: "Invoices",
selectable: true,
sort: { key: "id", dir: "desc" },
buildSortHref: (key, dir) => `/invoices?sort=${key}&dir=${dir}`,
bulkAction: "/invoices/bulk",
columns: [
{ key: "id", label: "Invoice", pinned: true, mono: true, sortable: true },
{ key: "client", label: "Client", sortable: true },
{ key: "total", label: "Total", numeric: true },
],
rows: invoices,
rowKey: (r) => r.id,
bulkActions: html`${
Button({ label: "Archive", size: "sm", type: "submit", attrs: { name: "op", value: "archive" } })
}${Button({ label: "Clear", size: "sm", attrs: { "data-bulk-clear": "" } })}`,
rowActions: (r) =>
RowActions({
id: `inv-${r.id}`,
label: `Actions for ${r.id}`,
items: [{ label: "View", href: `/invoices/${r.id}` }, {
label: "Archive",
danger: true,
attrs: {
"data-action": `/invoices/${r.id}/archive`,
"data-method": "post",
"data-target": "#invoices",
"data-swap": "outer",
},
}],
}),
footer: Pagination({ page: 1, totalPages: 4, buildHref: (p) => `/invoices?page=${p}`, target: "#invoices" }),
})<div class="data-table" id="invoices">
<div class="data-table__toolbar">
<span class="data-table__title">Invoices</span>
</div>
<form class="data-table__form" method="post" action="/invoices/bulk" data-action="/invoices/bulk" data-target="#invoices" data-swap="outer">
<div class="data-table__scroll">
<div class="data-table__bulk" hidden data-bulk-bar>
<span class="data-table__bulk-count" data-bulk-count>0 rows selected</span>
<span class="data-table__bulk-sep"></span>
<button type="submit" class="btn btn--sm" name="op" value="archive">Archive</button>
<button type="button" class="btn btn--sm" data-bulk-clear="">Clear</button>
</div>
<table class="data-table__table">
<thead>
<tr>
<th class="data-table__select">
<input type="checkbox" aria-label="Select all rows" data-select-all>
</th>
<th class="data-table__cell--pinned" aria-sort="descending">
<a class="data-table__sort" href="/invoices?sort=id&dir=asc" data-action="/invoices?sort=id&dir=asc" data-target="#invoices" data-swap="outer" data-push>
INVOICE
<svg width="11" height="11" width="2" …>…</svg>
</a>
</th>
<th class="">
<a class="data-table__sort" href="/invoices?sort=client&dir=asc" data-action="/invoices?sort=client&dir=asc" data-target="#invoices" data-swap="outer" data-push>CLIENT</a>
</th>
<th class="data-table__num">TOTAL</th>
<th class="data-table__actions">
<span class="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody>
<tr class="data-table__row" data-row-key="INV-2048">
<td class="data-table__select">
<input type="checkbox" name="selected" aria-label="Select row INV-2048" data-select-row value="INV-2048">
</td>
<td class="data-table__cell--pinned data-table__mono">INV-2048</td>
<td class="">Northwind Traders</td>
<td class="data-table__num">$12,400.00</td>
<td class="data-table__actions">
<button type="button" class="btn btn--ghost btn--sm btn--icon" data-row-actions="#inv-INV-2048-strip" aria-expanded="false" aria-controls="inv-INV-2048-strip">
<svg width="16" height="16" width="2" …>…</svg>
<span class="sr-only">Actions for INV-2048</span>
</button>
<div class="data-table__row-actions" id="inv-INV-2048-strip" role="group" aria-label="Actions for INV-2048" hidden>
<a class="btn btn--sm" href="/invoices/INV-2048">View</a>
<span class="data-table__row-actions-sep"></span>
<button type="button" class="btn btn--sm btn--danger" data-action="/invoices/INV-2048/archive" data-method="post" data-target="#invoices" data-swap="outer">Archive</button>
<span class="data-table__row-actions-sep"></span>
<button type="button" class="btn btn--ghost btn--sm btn--icon" data-row-actions-close aria-label="Close actions">
<svg width="14" height="14" width="2" …>…</svg>
</button>
</div>
</td>
</tr>
<tr class="data-table__row" data-row-key="INV-2047">
<td class="data-table__select">
<input type="checkbox" name="selected" aria-label="Select row INV-2047" data-select-row value="INV-2047">
</td>
<td class="data-table__cell--pinned data-table__mono">INV-2047</td>
<td class="">Contoso Ltd</td>
<td class="data-table__num">$3,120.00</td>
<td class="data-table__actions">
<button type="button" class="btn btn--ghost btn--sm btn--icon" data-row-actions="#inv-INV-2047-strip" aria-expanded="false" aria-controls="inv-INV-2047-strip">
<svg width="16" height="16" width="2" …>…</svg>
<span class="sr-only">Actions for INV-2047</span>
</button>
<div class="data-table__row-actions" id="inv-INV-2047-strip" role="group" aria-label="Actions for INV-2047" hidden>
<a class="btn btn--sm" href="/invoices/INV-2047">View</a>
<span class="data-table__row-actions-sep"></span>
<button type="button" class="btn btn--sm btn--danger" data-action="/invoices/INV-2047/archive" data-method="post" data-target="#invoices" data-swap="outer">Archive</button>
<span class="data-table__row-actions-sep"></span>
<button type="button" class="btn btn--ghost btn--sm btn--icon" data-row-actions-close aria-label="Close actions">
<svg width="14" height="14" width="2" …>…</svg>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</form>
<div class="data-table__footer">
<nav aria-label="Pagination">
<ul class="pagination">
<li>
<span class="pagination__link pagination__link--disabled" aria-disabled="true" aria-label="Previous page">‹</span>
</li>
<li>
<a class="pagination__link" href="/invoices?page=1" aria-current="page" aria-label="Page 1" data-action="/invoices?page=1" data-target="#invoices" data-swap="outer" data-push="">1</a>
</li>
<li>
<a class="pagination__link" href="/invoices?page=2" aria-label="Page 2" data-action="/invoices?page=2" data-target="#invoices" data-swap="outer" data-push="">2</a>
</li>
<li>
<span class="pagination__ellipsis" aria-hidden="true">…</span>
</li>
<li>
<a class="pagination__link" href="/invoices?page=4" aria-label="Page 4" data-action="/invoices?page=4" data-target="#invoices" data-swap="outer" data-push="">4</a>
</li>
<li>
<a class="pagination__link" href="/invoices?page=2" aria-label="Next page" data-action="/invoices?page=2" data-target="#invoices" data-swap="outer" data-push="">›</a>
</li>
</ul>
</nav>
</div>
</div>DataTable<Invoice>({
id: "invoices",
columns: [{ key: "id", label: "Invoice" }, { key: "client", label: "Client" }],
rows: [],
rowKey: (r) => r.id,
empty: Empty({ variant: "inline", title: "No invoices yet", icon: "invoice" }),
})<div class="data-table" id="invoices">
<div class="data-table__scroll">
<table class="data-table__table">
<thead>
<tr>
<th class="">INVOICE</th>
<th class="">CLIENT</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="data-table__empty">
<div class="empty empty--inline">
<span class="empty__icon">
<svg width="16" height="16" width="2" …>…</svg>
</span>
<span class="empty__body">
<span class="empty__title">No invoices yet</span>
</span>
</div>
</div>
</div>
</div>Classes defined by components/data-table/data-table.css — structural, token-driven; override from an unlayered stylesheet (see Theming):
.btn, .btn--danger, .btn--icon, .data-table, .data-table__actions, .data-table__bulk, .data-table__bulk-count, .data-table__bulk-sep, .data-table__cell--pinned, .data-table__empty, .data-table__footer, .data-table__form, .data-table__mono, .data-table__num, .data-table__row--selected, .data-table__row-actions, .data-table__row-actions-sep, .data-table__scroll, .data-table__scroll--lg, .data-table__scroll--md, .data-table__scroll--sm, .data-table__select, .data-table__sort, .data-table__table, .data-table__title, .data-table__toolbar, .empty, .table
components/data-table/data-table.js ships in ui.js (delegated on document, re-initialised after a rAPId swap).
Attributes it reads or writes: data-bulk-bar, data-bulk-clear, data-bulk-count, data-row-actions, data-row-actions-close, data-select-all, data-select-row, data-table.
Events: rapid:swapped.
Guides
Reference