mara: non-interactive Badge and StatusChip render identically (same padding/radius/font, same tone-to-color mapping) and StatusChip's 4-tone/single-label shape is a strict subset of Badge's — no real reason to keep both. Deletes StatusChip.tsx/.css, migrates AgentsPage's config-PR chip, HivesPage's freshness chip, and ComponentsPage's own table-status sample to Badge (tone/value, no onClick). Also drops the now-redundant standalone StatusChip demo section on /components (the Badge section already covers all 4 former chip tones plus accent). Updated the two stale StatusChip references outside swarm-ui too: design-guide.md's component-list example and colors.css's WCAG- contrast-rationale comment.
97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
// <Badge> — a labelled status pill, the "next to the thing it affects"
|
|
// building block for the per-agent terminal redesign.
|
|
// Design-guide anti-example this exists to fix: the agent page's model/
|
|
// effort *pickers* used to live in a `⋯` overflow menu while the current
|
|
// model/effort only showed as a separate, non-interactive chip — control
|
|
// disconnected from display. `Badge` is that chip PLUS, optionally, the
|
|
// control: pass `onClick` and it renders as a real `<button>` (so it can
|
|
// open a `Dropdown` right underneath itself, or toggle in place — e.g.
|
|
// pause/resume), pass nothing and it stays a plain status `<span>` (e.g.
|
|
// "alive"). Same component either way so a page reads as one consistent
|
|
// row of badges regardless of which ones happen to be interactive.
|
|
//
|
|
// Formerly two components: swarm-ui had its own presentational-only
|
|
// `StatusChip`, a strict subset of this shape (no interactivity, no
|
|
// label/value split, no icon). Deleted in favour of this one — a
|
|
// non-interactive `Badge` renders identically, and having two components
|
|
// for the same visual pill just meant a page had to pick between them
|
|
// with no real distinction to justify it. Lives in `shared` because both
|
|
// swarm-ui *and* the per-agent page need the interactive shape, and
|
|
// `shared` is the one package both already depend on (see
|
|
// `docs/web-ui/design-guide.md`'s component-first + junk-drawer rules).
|
|
import type { ComponentChildren } from 'preact';
|
|
import './Badge.css';
|
|
|
|
export type BadgeTone = 'neutral' | 'positive' | 'warning' | 'negative' | 'accent';
|
|
|
|
export interface BadgeProps {
|
|
/** Dim prefix text, e.g. "model". Omit for a single-value badge like "alive". */
|
|
label?: ComponentChildren;
|
|
/** The value itself, e.g. "sonnet". Required — a badge always shows something. */
|
|
value: ComponentChildren;
|
|
tone?: BadgeTone;
|
|
/** Decorative glyph before the label; `aria-hidden`, the text is still the real label. */
|
|
icon?: ComponentChildren;
|
|
/**
|
|
* Present → renders a `<button>` (real control, min touch target) and
|
|
* shows a disclosure caret. Absent → renders a plain `<span>` (status
|
|
* display only, e.g. "alive"/"idle 6m").
|
|
*/
|
|
onClick?: (e: MouseEvent) => void;
|
|
/** Only meaningful with `onClick` — drives the caret direction + `aria-expanded`. */
|
|
expanded?: boolean;
|
|
disabled?: boolean;
|
|
class?: string;
|
|
title?: string;
|
|
}
|
|
|
|
export function Badge({
|
|
label,
|
|
value,
|
|
tone = 'neutral',
|
|
icon,
|
|
onClick,
|
|
expanded,
|
|
disabled,
|
|
class: extraClass,
|
|
title,
|
|
}: BadgeProps) {
|
|
const classes = ['ui-badge', `ui-badge-${tone}`, onClick && 'ui-badge-interactive', extraClass]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
const content = (
|
|
<>
|
|
{icon ? (
|
|
<span class="ui-badge-icon" aria-hidden="true">
|
|
{icon}
|
|
</span>
|
|
) : null}
|
|
{label ? <span class="ui-badge-label">{label}</span> : null}
|
|
<span class="ui-badge-value">{value}</span>
|
|
{onClick ? (
|
|
<span class="ui-badge-caret" aria-hidden="true">
|
|
{expanded ? '▴' : '▾'}
|
|
</span>
|
|
) : null}
|
|
</>
|
|
);
|
|
if (onClick) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
class={classes}
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
aria-expanded={expanded}
|
|
title={title}
|
|
>
|
|
{content}
|
|
</button>
|
|
);
|
|
}
|
|
return (
|
|
<span class={classes} title={title}>
|
|
{content}
|
|
</span>
|
|
);
|
|
}
|