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:
parent
592af562f9
commit
e1aae29068
5 changed files with 68 additions and 23 deletions
|
|
@ -14,6 +14,11 @@
|
|||
// link and rebuild; rebuild's gone outright, the dashboard's own
|
||||
// R3BU1LD button already covers it, dashboard-link moves here, so
|
||||
// `OverflowMenu` had nothing left to justify existing and is deleted).
|
||||
//
|
||||
// `variant="quiet"` — mara: "link and settings button should not have
|
||||
// the badge bg". `Badge`'s default filled-pill look is right for a
|
||||
// status/picker chip, wrong for this icon-only trigger, which should
|
||||
// read as header chrome. See `@hive/shared/badge.js`'s `BadgeVariant`.
|
||||
|
||||
// v1 of this rendered every link inline in the header's title row —
|
||||
// mara, live: "the login card looks like it is behind the header"
|
||||
|
|
@ -73,7 +78,13 @@ export function MetaNav({ links, forgePublicUrl, dashboardBase }: MetaNavProps)
|
|||
|
||||
return (
|
||||
<div class="meta-nav-anchor" ref={rootRef}>
|
||||
<Badge value={<LinkIcon />} title="agent links" onClick={() => setOpen((o) => !o)} expanded={open} />
|
||||
<Badge
|
||||
value={<LinkIcon />}
|
||||
title="agent links"
|
||||
variant="quiet"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
expanded={open}
|
||||
/>
|
||||
{open ? (
|
||||
<div class="meta-nav-popover" role="menu" aria-label="agent links">
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@
|
|||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
/* `variant="quiet"` — icon-only header triggers (settings/links), not
|
||||
status chips: no permanent fill, so it reads as chrome next to the
|
||||
rest of the header rather than another data pill. The `:hover` and
|
||||
`[aria-expanded='true']` rules above still apply — quiet only drops
|
||||
the idle background, which is what mara's report was actually about. */
|
||||
.ui-badge-quiet {
|
||||
background: none;
|
||||
}
|
||||
.ui-badge-interactive[aria-expanded='true'] {
|
||||
background: var(--bg-elev);
|
||||
outline: 1px solid var(--purple);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,19 @@ import type { ComponentChildren } from 'preact';
|
|||
import './Badge.css';
|
||||
|
||||
export type BadgeTone = 'neutral' | 'positive' | 'warning' | 'negative' | 'accent';
|
||||
/**
|
||||
* `'default'` — the filled pill every status/picker badge has always
|
||||
* been. `'quiet'` — no permanent fill, only a background on hover/
|
||||
* `expanded`; for an icon-only trigger that should read as chrome (a
|
||||
* header button) rather than a status chip — mara: "link and settings
|
||||
* button should not have the badge bg" against both the agent page's
|
||||
* `MetaNav` and the shared `SettingsMenu` (swarm-ui + agent both use
|
||||
* it). Same `variant` naming/shape as `Button`'s
|
||||
* `ButtonVariant` (`../../swarm-ui/src/ui/button/Button.tsx`) — visual
|
||||
* weight is orthogonal to `tone` (color semantics), so it's its own prop
|
||||
* rather than a new `tone` value.
|
||||
*/
|
||||
export type BadgeVariant = 'default' | 'quiet';
|
||||
|
||||
export interface BadgeProps {
|
||||
/** Dim prefix text, e.g. "model". Omit for a single-value badge like "alive". */
|
||||
|
|
@ -30,6 +43,7 @@ export interface BadgeProps {
|
|||
/** The value itself, e.g. "sonnet". Required — a badge always shows something. */
|
||||
value: ComponentChildren;
|
||||
tone?: BadgeTone;
|
||||
variant?: BadgeVariant;
|
||||
/** Decorative glyph before the label; `aria-hidden`, the text is still the real label. */
|
||||
icon?: ComponentChildren;
|
||||
/**
|
||||
|
|
@ -49,6 +63,7 @@ export function Badge({
|
|||
label,
|
||||
value,
|
||||
tone = 'neutral',
|
||||
variant = 'default',
|
||||
icon,
|
||||
onClick,
|
||||
expanded,
|
||||
|
|
@ -56,7 +71,13 @@ export function Badge({
|
|||
class: extraClass,
|
||||
title,
|
||||
}: BadgeProps) {
|
||||
const classes = ['ui-badge', `ui-badge-${tone}`, onClick && 'ui-badge-interactive', extraClass]
|
||||
const classes = [
|
||||
'ui-badge',
|
||||
`ui-badge-${tone}`,
|
||||
variant === 'quiet' && 'ui-badge-quiet',
|
||||
onClick && 'ui-badge-interactive',
|
||||
extraClass,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
const content = (
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ 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 { GearIcon } from '@hive/shared/icons.js';
|
||||
import { Dropdown, type DropdownOption } from '@hive/shared/dropdown.js';
|
||||
import './ComponentsPage.css';
|
||||
|
||||
|
|
@ -234,6 +235,9 @@ export function ComponentsPage() {
|
|||
<Sample label="interactive, toggles itself (pause/resume shape)">
|
||||
<BadgeToggleSample />
|
||||
</Sample>
|
||||
<Sample label="interactive, quiet variant (icon-only header trigger, e.g. settings)">
|
||||
<Badge value={<GearIcon />} title="settings" variant="quiet" onClick={() => {}} />
|
||||
</Sample>
|
||||
</div>
|
||||
</Section>
|
||||
</Panel>
|
||||
|
|
|
|||
Loading…
Reference in a new issue