badge: add a quiet variant for icon-only header triggers

Badge's default look is a filled pill, right for status/picker chips
but wrong for an icon-only header button like the settings gear or
agent links trigger, which should read as chrome. Add variant='quiet'
(same naming/shape as Button's ButtonVariant) that drops the idle
background, keeping the existing hover/expanded feedback.

Apply it to the two icon-only Badge triggers: shared SettingsMenu
(used by both swarm-ui and the agent page) and the agent page's own
MetaNav links trigger. Add a components-page showcase sample so the
variant has a visible regression check going forward.
This commit is contained in:
iris 2026-08-29 20:23:00 +02:00 committed by mara
commit e1aae29068
5 changed files with 68 additions and 23 deletions

View file

@ -1,26 +1,21 @@
// <SettingsMenu> — a single header icon-button + popover holding the
// client-local overrides a page has (theme, reduced motion). Originated
// in swarm-ui (its own `shell/SettingsMenu.tsx`, trigger a raw SVG
// gear icon); the agent terminal page needed the identical panel
// (mara: "agent terminal page should get the settings panel from swarm
// ui as well"). First pass duplicated it with an agent-specific trigger
// (`Badge`, matching the rest of that page's header pills — `MetaNav`
// already established `Badge` as the one interactive-chip language
// there); mara's review on that PR: "i think the component should be
// shared. motion setting is missing." — this is that: one component,
// both pages import it, `Badge` trigger everywhere (already a
// `@hive/shared` component itself, and already used elsewhere in
// swarm-ui — `pages/ComponentsPage.tsx` etc. — so this isn't a new
// visual language for that package either), motion included
// unconditionally rather than agent-only.
// in swarm-ui, duplicated to the agent terminal page (mara: "agent
// terminal page should get the settings panel from swarm ui as well"),
// then unified here per her review on that PR: "i think the component
// should be shared. motion setting is missing." — one component, both
// pages import it, motion included unconditionally rather than
// agent-only.
//
// That first merge swapped the trigger's icon from swarm-ui's original
// inline SVG gear to a plain `⚙` text glyph (matching `MetaNav`'s own
// `value="🔗"` emoji at the time) — mara caught it: "settings icon
// looks weird since component extract". `../icons.js`'s `GearIcon` is
// swarm-ui's original SVG, restored here so both pages get the crisp
// version now instead of just one — see that file's comment for why a
// text/emoji glyph is the wrong tool for a trigger's own icon.
// Trigger is `Badge` (`../badge/Badge.js`) with `../icons.js`'s
// `GearIcon` — an inline SVG, not a text/emoji glyph (see that file's
// comment for why a glyph is the wrong tool for a trigger's own icon;
// this file went through one regression on exactly that, caught by
// mara: "settings icon looks weird since component extract").
// `variant="quiet"` drops `Badge`'s default filled-pill look, which is
// right for a status/picker chip but wrong for this icon-only header
// button (mara again: "link and settings button should not have the
// badge bg") — see `Badge.tsx`'s `BadgeVariant` comment.
//
// Storage keys are the caller's responsibility (`themeKey`/`motionKey`
// props), not derived here — a page mounts `useApplyThemeOverride`/
@ -68,7 +63,13 @@ export function SettingsMenu({ themeKey, motionKey }: SettingsMenuProps) {
return (
<div class="settings-menu-anchor" ref={rootRef}>
<Badge value={<GearIcon />} title="settings" onClick={() => setOpen((v) => !v)} expanded={open} />
<Badge
value={<GearIcon />}
title="settings"
variant="quiet"
onClick={() => setOpen((v) => !v)}
expanded={open}
/>
{open ? (
<div class="settings-menu-popover" role="menu" aria-label="settings">
<label class="settings-menu-row">