swarm-ui: agents page — explainer message when the roster is empty

Table gains an optional emptyMessage prop, rendered as a single
full-width row in place of a bare empty tbody; AgentsPage is the first
consumer. Screenshot-verified against a mock server returning an empty
roster.
This commit is contained in:
iris 2026-08-24 00:14:36 +02:00
commit 46d1271603
3 changed files with 34 additions and 7 deletions

View file

@ -21,3 +21,8 @@
.ui-table tr:last-child td {
border-bottom: none;
}
.ui-table-empty {
color: var(--muted);
text-align: center;
padding: 1.25em 0.75em;
}

View file

@ -22,10 +22,17 @@ export function Table<T>({
columns,
rows,
rowKey,
emptyMessage,
}: {
columns: TableColumn<T>[];
rows: T[];
rowKey: (row: T) => string;
// Rendered as a single full-width row when `rows` is empty. Omit to
// leave a bare empty `<tbody>` — a caller whose "no rows yet" is
// covered by its own loading/error state (rendered instead of the
// table entirely) has nothing useful to add here, so this stays
// opt-in rather than every table growing a mandatory default string.
emptyMessage?: ComponentChildren;
}) {
return (
<div class="ui-table-scroll">
@ -38,13 +45,21 @@ export function Table<T>({
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={rowKey(row)}>
{columns.map((c) => (
<td key={c.key}>{c.render(row)}</td>
))}
{rows.length === 0 && emptyMessage ? (
<tr>
<td class="ui-table-empty" colSpan={columns.length}>
{emptyMessage}
</td>
</tr>
))}
) : (
rows.map((row) => (
<tr key={rowKey(row)}>
{columns.map((c) => (
<td key={c.key}>{c.render(row)}</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>