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:
parent
60d7e8ce84
commit
7003560569
11 changed files with 646 additions and 400 deletions
|
|
@ -141,25 +141,9 @@
|
|||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* `filterMode: "multiselect"` — a scrollable checkbox list rather than
|
||||
a `<select multiple>`, which needs a modifier key to pick more than
|
||||
one option and reads as unfamiliar to most operators; a plain
|
||||
checkbox list needs neither. `max-height` + scroll keeps a
|
||||
long option set (many labels) from pushing the popover off-screen. */
|
||||
.ui-table-filter-multiselect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3em;
|
||||
max-height: 12em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.ui-table-filter-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35em;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
/* `filterMode: "multiselect"`'s actual checkbox-list content is
|
||||
`MultiselectFilterOptions` (`ui/multiselect-filter/`) now — its own
|
||||
colocated CSS, not here. */
|
||||
|
||||
/* The negate toggle sits under every filter mode's own control (mara:
|
||||
"you should be able to change if the filter is negated or not") —
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { createPortal } from "preact/compat";
|
|||
import type { ComponentChildren } from "preact";
|
||||
import { FilterIcon } from "@hive/shared/icons.js";
|
||||
import { useLocalSetting } from "@hive/shared/settings-storage.js";
|
||||
import { MultiselectFilterOptions } from "../multiselect-filter/MultiselectFilter.js";
|
||||
import "./Table.css";
|
||||
|
||||
export interface TableColumn<T> {
|
||||
|
|
@ -426,21 +427,16 @@ export function Table<T>({
|
|||
|
||||
function renderFilterControl(c: TableColumn<T>) {
|
||||
if (c.filterMode === "multiselect") {
|
||||
const f = getFilter(c.key);
|
||||
// Content shared with `AgentsPage`'s card-view filter toolbar —
|
||||
// see `MultiselectFilter.tsx`'s own file-top comment for why this
|
||||
// moved out of here.
|
||||
return (
|
||||
<>
|
||||
<div class="ui-table-filter-multiselect">
|
||||
{multiselectOptionsFor(c).map((v) => (
|
||||
<label key={v} class="ui-table-filter-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={f.values.includes(v)}
|
||||
onChange={() => toggleFilterValue(c.key, v)}
|
||||
/>
|
||||
{v}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<MultiselectFilterOptions
|
||||
options={multiselectOptionsFor(c)}
|
||||
filter={getFilter(c.key)}
|
||||
onToggle={(v) => toggleFilterValue(c.key, v)}
|
||||
/>
|
||||
{renderNegateToggle(c)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue