Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui/agents: split the page, extract FilterableView per mara's follow-up

Three more asks from the same review thread:

- "agentspage is now giant and deserves a split" - AgentsPage.tsx was
  1047 lines. Split into AgentTypes.ts (AgentRow and friends),
  WantedMenu.tsx, AgentCard.tsx (+ its own CSS), leaving AgentsPage.tsx
  as state/actions/columns/the render tree - 649 lines, and every piece
  it composes is now independently readable.

- "what about the component that represents filtered data ... that the
  card view and table can both use?" - extracted FilterableView
  (ui/filterable-view/): takes columns/rows/rowKey/storageKey/view/
  renderCard, builds its filter bar from *every* filterable column (not
  a hand-picked subset - the old AgentFilterBar only showed 4 of the
  agent columns' 6 filterable fields, an accidental gap the table's own
  popovers didn't have), and renders either the card list or Table.
  AgentsPage now just tells it which view to show; the view toggle
  itself stays page-side since it's Panel-header chrome, not filtering.
  Disclosed side effect: card view's filter bar now also covers
  message/config-PR (text filters), matching table view exactly instead
  of a narrower subset.

- CSS audit: AgentsPage.css now holds only what's genuinely page-specific
  (the view toggle, the detail-panel field grid) - everything else moved
  to its owning component's own colocated CSS.

FilterableView gets a /components demo (view toggle + filter bar + both
render modes, same day per the design guide). Verified: AgentsPage
still renders the same (real screenshot), and the demo's own table
toggle produces a real Table with the same rows.
This commit is contained in:
iris 2026-09-11 22:30:38 +02:00
commit 601068f1eb
9 changed files with 591 additions and 491 deletions

View file

@ -8,6 +8,7 @@
import { useRef, useState } from "preact/hooks";
import type { ComponentChildren } from "preact";
import { Card } from "../ui/card/Card.js";
import { FilterableView } from "../ui/filterable-view/FilterableView.js";
import {
MultiselectFilter,
type MultiselectFilterState,
@ -243,6 +244,46 @@ function MultiselectFilterSample() {
);
}
// A `view` toggle here is caller-owned (same contract `AgentsPage` uses)
// — `FilterableView` itself only renders whichever one this sample
// says to. Reuses `TABLE_COLUMNS`/`TABLE_ROWS` from the `Table` section
// above so the filter behavior is directly comparable between the two.
function FilterableViewSample() {
const [view, setView] = useState<"cards" | "table">("cards");
return (
<div>
<div class="components-chip-row" style={{ marginBottom: "0.5em" }}>
<Button
variant={view === "cards" ? "primary" : "default"}
onClick={() => setView("cards")}
>
cards
</Button>
<Button
variant={view === "table" ? "primary" : "default"}
onClick={() => setView("table")}
>
table
</Button>
</div>
<FilterableView
columns={TABLE_COLUMNS}
rows={TABLE_ROWS}
rowKey={(r) => r.name}
storageKey="swarm-ui:components-demo:filterable-view"
view={view}
emptyMessage="no rows"
renderCard={(r) => (
<Card onClick={() => {}}>
<strong>{r.name}</strong>
<div>{r.detail}</div>
</Card>
)}
/>
</div>
);
}
function ConfirmDialogSample() {
const [open, setOpen] = useState(false);
return (
@ -365,6 +406,12 @@ export function ComponentsPage() {
</Sample>
</Section>
<Section title="FilterableView">
<Sample label="filter bar (built from every filterable column) + cards/table, same filter state either way">
<FilterableViewSample />
</Sample>
</Section>
<Section title="RelativeTime">
<div class="components-chip-row">
<Sample label="5s ago, ticking">