Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: extract Card/MultiselectFilter/SplitView per component-first design

Mara: "result looks like the shape i am looking for, but the code does
not. you did not follow component first principle" - the card's
clickable/selectable mechanics, the card-view filter trigger+popover,
and the list+detail split layout were all one-off page-local JSX in
AgentsPage.tsx instead of docs/web-ui/design-guide.md's "Component-first
design" primitives. Three new ui/ components, each with a same-day
/components demo section per that doc's own rule:

- ui/card/Card.tsx - the role=button/keyboard-activation/selected
  mechanics AgentCard now wraps agent-specific content around, instead
  of owning them itself.
- ui/multiselect-filter/MultiselectFilter.tsx - the checkbox-list
  trigger+popover control. This was also a straight duplicate of
  Table's own inline popover content once AgentsPage's filter toolbar
  needed the identical thing; Table now renders the same
  MultiselectFilterOptions piece too (keeping its own th-anchored
  trigger and fixed+portal positioning, which are genuinely
  table-specific), not a second copy.
- ui/split-view/SplitView.tsx - the list+detail flex-wrap layout, no
  opinion on what's inside either pane.

AgentsPage.tsx's own CSS shrinks to just the agent-specific content
inside these primitives (card line/message layout, detail-panel field
grid, the name-search input) - the container/positioning rules moved
to each component's own colocated CSS.

No behavior change for any other Table caller (HivesPage,
IssueReportPage, the components demo's own Table samples) - the
popover's visual output is identical, just sourced from the shared
component instead of inline JSX.

Verified: typecheck/build clean, real screenshots of both AgentsPage
(pixel-identical to before) and the three new /components sections,
plus a live click confirming MultiselectFilter's popover opens
correctly on the demo page too.
This commit is contained in:
iris 2026-09-11 22:14:21 +02:00
commit 7003560569
11 changed files with 646 additions and 400 deletions

View file

@ -2,7 +2,9 @@
old table body (mara: "more like card per agent"). Full-width rows,
not a grid: the second line (the agent's free-form status message)
reads better wrapping the card's own width than squeezed into a fixed
grid cell. */
grid cell. Each card's own clickable/selected/border styling is
`Card`'s (`ui/card/`) — this file only lays out agent-specific
content inside one. */
.ui-agent-card-list {
display: flex;
flex-direction: column;
@ -45,29 +47,6 @@
.ui-agents-view-toggle button:not(.active):hover {
color: var(--fg);
}
.ui-agent-card {
display: flex;
flex-direction: column;
gap: 0.35em;
padding: 0.75em 1em;
border: 1px solid var(--border);
border-radius: 0.5em;
background: var(--bg-elev);
cursor: pointer;
}
.ui-agent-card:hover,
.ui-agent-card:focus-visible {
border-color: var(--purple);
outline: none;
}
/* The row currently shown in the detail panel — a solid border (not
just the hover tint above, which needs to keep meaning "hovering",
not double as "selected") plus a faint fill so it still reads once
the pointer moves away. */
.ui-agent-card-selected {
border-color: var(--purple);
background: color-mix(in srgb, var(--purple) 10%, var(--bg-elev));
}
.ui-agent-card-line1 {
display: flex;
align-items: center;
@ -110,27 +89,11 @@
gap: 0.75em;
}
/* List panel + detail panel side by side — `flex-wrap`, not a
`@media` breakpoint, so a narrow viewport stacks them the same
content-driven way the shell's own nav already wraps (`Shell.css`),
rather than picking a second, independent magic-number breakpoint. */
.ui-agents-layout {
display: flex;
flex-wrap: wrap;
gap: 1em;
align-items: flex-start;
}
.ui-agents-list-panel {
flex: 2 1 480px;
min-width: 0;
}
.ui-agents-detail-panel {
flex: 1 1 320px;
min-width: 0;
}
/* Card view's filter toolbar — `Table`'s per-column popovers, minus the
table to hang them off of. */
table to hang them off of. Each `MultiselectFilter` (`ui/
multiselect-filter/`) owns its own trigger/popover styling; this file
only lays out the row they sit in plus the name-search input, which
has no shared-component equivalent (a plain text input). */
.ui-agents-filter-bar {
display: flex;
flex-wrap: wrap;
@ -147,17 +110,3 @@
font: inherit;
font-size: 0.9em;
}
.ui-agents-filter {
position: relative;
}
/* `.ui-table-filter-popover` (Table.css) supplies the panel chrome
(background/border/shadow) — this just positions it under the
trigger, `position: absolute` rather than the fixed/portal recipe
`Table`'s own version needs: nothing here clips this popover, so the
simpler positioning is enough (see this component's own comment). */
.ui-agents-filter-popover {
position: absolute;
top: 100%;
left: 0;
margin-top: 0.25em;
}