hyperhive/frontend/packages/swarm-ui/src/ui/table/Table.css
iris 247ff4498d swarm-ui: sortable + filterable table columns
hyperhive#4020, mara. TableColumn gets two new optional extractors,
each one the whole signal for its capability (mara: 'why separate
selector and flag?' — dropped the sortable/filterable booleans that
would've said the same thing twice and could disagree with the
extractor's presence):

- sortBy: (row) => string | number — column is click-to-sort iff
  present. Table owns the sort state (one active column, header click
  cycles none -> ascending -> descending), since a rendered cell often
  isn't the sortable value itself (e.g. AgentsPage's status column
  renders a Badge, not a plain string).
- filterValue: (row) => string — column is filterable iff present. A
  text input row under the headers, substring match case-insensitive.

Client-side only, no backend change - every consuming page already
fetches its full row set. Wired into AgentsPage and HivesPage, the two
pages with a real per-row Table. Left IssueReportPage alone (already
has its own purpose-built sort + label/hide-blocked filters, predates
this and covers its own domain better than a generic per-column text
filter would) and JobsPage alone (renders an indented state tree via
JobqGraph, no column table at all despite what my original scoping
comment assumed).

Verified: typecheck + build clean, nix fmt clean, static render of the
actual built CSS confirms the new sort-header + filter-row markup
doesn't break table layout.
2026-09-03 01:36:36 +02:00

83 lines
2 KiB
CSS

/* Scrolls horizontally within its own box on a narrow viewport instead
of forcing the whole page to overflow — see Table.tsx's comment. */
.ui-table-scroll {
overflow-x: auto;
}
.ui-table {
width: 100%;
border-collapse: collapse;
}
.ui-table th {
text-align: left;
font-weight: 600;
color: var(--muted);
padding: 0.5em 0.75em;
border-bottom: 1px solid var(--border);
}
.ui-table td {
padding: 0.5em 0.75em;
border-bottom: 1px solid var(--border);
}
.ui-table tr:last-child td {
border-bottom: none;
}
.ui-table-empty {
color: var(--muted);
text-align: center;
padding: 1.25em 0.75em;
}
/* A cell holding free-form prose (an operator- or agent-provided string,
not a short discrete label) rather than open-ended growth that drags
the whole table wider — caps the column and wraps within it instead. */
.ui-table-prose {
max-width: 32em;
white-space: normal;
word-break: break-word;
}
/* Sortable header — the whole header cell is the click target, not a
separate icon-only button next to it, same "control lives on the
thing it affects" reasoning as `Badge`'s own click-to-toggle shape. */
.ui-table-sort-button {
display: inline-flex;
align-items: center;
gap: 0.35em;
background: none;
border: none;
padding: 0;
margin: 0;
font: inherit;
font-weight: 600;
color: inherit;
cursor: pointer;
}
.ui-table-sort-button:hover {
color: var(--fg);
}
.ui-table-sort-glyph {
opacity: 0.6;
font-size: 0.85em;
}
.ui-table-sort-button:hover .ui-table-sort-glyph {
opacity: 1;
}
/* The per-column filter inputs' row — same cell padding as a header row,
no bottom border of its own (the header row above already has one). */
.ui-table-filter-row th {
padding-top: 0;
padding-bottom: 0.5em;
font-weight: 400;
}
.ui-table-filter-input {
width: 100%;
box-sizing: border-box;
background: var(--bg);
color: var(--fg);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.25em 0.5em;
font: inherit;
font-size: 0.9em;
}