Add Badge + Dropdown Preact components, demo on swarm-ui /components
New shared Preact primitives for the per-agent terminal redesign: - Badge (@hive/shared/badge.js): a labelled status pill. Plain <span> when static (e.g. "alive"), a real <button> with a disclosure caret when given onClick (e.g. opens a Dropdown, or toggles itself in place). Same component either way so a status row reads as one consistent set of badges regardless of which are interactive. - Dropdown (@hive/shared/dropdown.js): a small option list anchored directly under whatever opened it (no portal, no native <dialog> — see the file comment for why). Closes on outside click or Escape. This is the fix for the design guide's own named anti-example: the agent page's model/effort pickers live in the overflow menu while the current model/effort only show as a disconnected chip. Badge+Dropdown composed together is that control moved inline, next to what it shows. Demoed on swarm-ui's /components page: all Badge tones, a label-prefixed badge, an interactive badge that opens a Dropdown (model-picker shape), and an interactive badge that toggles itself (pause/resume shape). Both packages build + tsc --noEmit clean.
This commit is contained in:
parent
c4a573d91d
commit
62cc0620c8
7 changed files with 365 additions and 1 deletions
|
|
@ -42,3 +42,10 @@
|
|||
font-size: 0.8em;
|
||||
color: var(--muted);
|
||||
}
|
||||
/* Anchor for a Badge + the Dropdown it opens (@hive/shared) — Dropdown
|
||||
positions `absolute` against this box, see Dropdown.css's file-top
|
||||
comment. */
|
||||
.components-badge-anchor {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { Table, type TableColumn } from '../ui/table/Table.js';
|
|||
import { TextField } from '../ui/text-field/TextField.js';
|
||||
import { SelectField, type SelectOption } from '../ui/select-field/SelectField.js';
|
||||
import { Button, type ButtonVariant } from '../ui/button/Button.js';
|
||||
import { Badge, type BadgeTone } from '@hive/shared/badge.js';
|
||||
import { Dropdown, type DropdownOption } from '@hive/shared/dropdown.js';
|
||||
import './ComponentsPage.css';
|
||||
|
||||
function Section({ title, children }: { title: string; children: ComponentChildren }) {
|
||||
|
|
@ -59,6 +61,13 @@ const SELECT_OPTIONS: SelectOption[] = [
|
|||
];
|
||||
|
||||
const BUTTON_VARIANTS: ButtonVariant[] = ['primary', 'default'];
|
||||
const BADGE_TONES: BadgeTone[] = ['neutral', 'positive', 'warning', 'negative', 'accent'];
|
||||
|
||||
const MODEL_OPTIONS: DropdownOption[] = [
|
||||
{ value: 'haiku', label: 'haiku', description: 'fast' },
|
||||
{ value: 'sonnet', label: 'sonnet', description: 'balanced' },
|
||||
{ value: 'opus', label: 'opus', description: 'powerful' },
|
||||
];
|
||||
|
||||
// Controlled samples need their own state to actually type/select into —
|
||||
// module-level consts can't do that, hence these two small wrappers
|
||||
|
|
@ -86,6 +95,51 @@ function RefreshIntervalPickerSample() {
|
|||
return <RefreshIntervalPicker id="sample-refresh-interval" value={value} onChange={setValue} />;
|
||||
}
|
||||
|
||||
// The badge-triggers-a-dropdown-right-underneath-itself pattern this is
|
||||
// meant to land — the model/effort picker shape from the per-agent
|
||||
// terminal, demoed here with sample data. The wrapper
|
||||
// needs `position: relative` (`.components-badge-anchor`) so `Dropdown`
|
||||
// anchors under this badge specifically, not the page.
|
||||
function BadgePickerSample() {
|
||||
const [value, setValue] = useState('sonnet');
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div class="components-badge-anchor">
|
||||
<Badge
|
||||
label="model"
|
||||
value={value}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
expanded={open}
|
||||
/>
|
||||
<Dropdown
|
||||
open={open}
|
||||
options={MODEL_OPTIONS}
|
||||
activeValue={value}
|
||||
label="select model"
|
||||
onSelect={(v) => {
|
||||
setValue(v);
|
||||
setOpen(false);
|
||||
}}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// The other interactive shape: a badge that's itself the control (no
|
||||
// dropdown) — e.g. the agent page's pause/resume action folded into the
|
||||
// status row instead of living in the `⋯` overflow menu.
|
||||
function BadgeToggleSample() {
|
||||
const [paused, setPaused] = useState(false);
|
||||
return (
|
||||
<Badge
|
||||
value={paused ? '▶ resume' : '⏸ pause'}
|
||||
tone={paused ? 'warning' : 'neutral'}
|
||||
onClick={() => setPaused((p) => !p)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ComponentsPage() {
|
||||
return (
|
||||
<Panel title="components" icon="🧰">
|
||||
|
|
@ -176,6 +230,25 @@ export function ComponentsPage() {
|
|||
</Sample>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Badge (@hive/shared — used by swarm-ui and the per-agent page)">
|
||||
<div class="components-chip-row">
|
||||
{BADGE_TONES.map((tone) => (
|
||||
<Sample key={tone} label={`display, ${tone}`}>
|
||||
<Badge value={tone} tone={tone} />
|
||||
</Sample>
|
||||
))}
|
||||
<Sample label="with a label prefix">
|
||||
<Badge label="ctx" value="534k" />
|
||||
</Sample>
|
||||
<Sample label="interactive, opens a Dropdown (model/effort picker shape)">
|
||||
<BadgePickerSample />
|
||||
</Sample>
|
||||
<Sample label="interactive, toggles itself (pause/resume shape)">
|
||||
<BadgeToggleSample />
|
||||
</Sample>
|
||||
</div>
|
||||
</Section>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue