swarm-ui: give Panel an optional header icon slot
Adds an `icon` prop to Panel (small emoji glyph left of the title, aria-hidden, chosen per panel with no default) and wires it into every current Panel caller: hives (bee), create-agent's form (robot) and info panel (identity card, moved off the info panel's body copy where it started as a one-off), jobs (puzzle piece), the components gallery itself (toolbox), and the 404 page (compass). Adds a components-page demo section and a whimsy-section pointer in the design guide. Closes: #3508
This commit is contained in:
parent
582b5cf83d
commit
b25aa157df
9 changed files with 49 additions and 15 deletions
|
|
@ -24,7 +24,13 @@ repeated here.
|
||||||
home.js`, is the reference example — currently in the dashboard
|
home.js`, is the reference example — currently in the dashboard
|
||||||
package, not swarm-ui itself, but the pattern it sets applies here
|
package, not swarm-ui itself, but the pattern it sets applies here
|
||||||
too). Whimsy still has to clear the accessibility bar below (motion,
|
too). Whimsy still has to clear the accessibility bar below (motion,
|
||||||
in particular).
|
in particular). swarm-ui's own reference example: `Panel`'s optional
|
||||||
|
`icon` prop (`src/ui/panel/Panel.tsx`), a small emoji glyph in a
|
||||||
|
panel's header, chosen per panel with no default — grew out of a
|
||||||
|
one-off emoji dropped into a single page's copy, which wasn't whimsy
|
||||||
|
in this sense (a *consistent*, reusable touch) until it became a real
|
||||||
|
prop every panel can opt into. `aria-hidden`, since it's decorative —
|
||||||
|
the title text is still the actual label.
|
||||||
- **Efficient navigation** — minimize clicks/hops for a common task.
|
- **Efficient navigation** — minimize clicks/hops for a common task.
|
||||||
- **Avoid junk drawers.** A control belongs next to the thing it
|
- **Avoid junk drawers.** A control belongs next to the thing it
|
||||||
affects, not tucked into a catch-all menu. Concrete anti-example (not
|
affects, not tucked into a catch-all menu. Concrete anti-example (not
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { Panel } from './ui/panel/Panel.js';
|
||||||
|
|
||||||
function NotFound() {
|
function NotFound() {
|
||||||
return (
|
return (
|
||||||
<Panel title="404">
|
<Panel title="404" icon="🧭">
|
||||||
<p>no route here.</p>
|
<p>no route here.</p>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ function RefreshIntervalPickerSample() {
|
||||||
|
|
||||||
export function ComponentsPage() {
|
export function ComponentsPage() {
|
||||||
return (
|
return (
|
||||||
<Panel title="components">
|
<Panel title="components" icon="🧰">
|
||||||
<p class="components-intro">
|
<p class="components-intro">
|
||||||
Every primitive in <code>src/ui/</code>, shown in each mode it supports. Sample data
|
Every primitive in <code>src/ui/</code>, shown in each mode it supports. Sample data
|
||||||
only — nothing here calls the API.
|
only — nothing here calls the API.
|
||||||
|
|
@ -101,6 +101,11 @@ export function ComponentsPage() {
|
||||||
<Sample label="without title">
|
<Sample label="without title">
|
||||||
<Panel>panel body content, no title</Panel>
|
<Panel>panel body content, no title</Panel>
|
||||||
</Sample>
|
</Sample>
|
||||||
|
<Sample label="with icon (per panel, no default — see the doc comment)">
|
||||||
|
<Panel title="example title" icon="✨">
|
||||||
|
panel body content
|
||||||
|
</Panel>
|
||||||
|
</Sample>
|
||||||
<Sample label="with actions (e.g. HivesPage's refresh picker)">
|
<Sample label="with actions (e.g. HivesPage's refresh picker)">
|
||||||
<Panel
|
<Panel
|
||||||
title="example title"
|
title="example title"
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,6 @@
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
min-width: 16em;
|
min-width: 16em;
|
||||||
}
|
}
|
||||||
.create-agent-info-glyph {
|
|
||||||
margin: 0 0 0.5em;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
.create-agent-intro {
|
.create-agent-intro {
|
||||||
margin: 0 0 1.5em;
|
margin: 0 0 1.5em;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ export function CreateAgentPage() {
|
||||||
return (
|
return (
|
||||||
<div class="create-agent-layout">
|
<div class="create-agent-layout">
|
||||||
<div class="create-agent-form-col">
|
<div class="create-agent-form-col">
|
||||||
<Panel title="create agent">
|
<Panel title="create agent" icon="🤖">
|
||||||
<p class="create-agent-intro">
|
<p class="create-agent-intro">
|
||||||
Create a new agent's swarm-level identity. This only queues the job — check{' '}
|
Create a new agent's swarm-level identity. This only queues the job — check{' '}
|
||||||
<Link href="/jobs">jobs</Link> to watch it settle.
|
<Link href="/jobs">jobs</Link> to watch it settle.
|
||||||
|
|
@ -180,11 +180,11 @@ export function CreateAgentPage() {
|
||||||
literal behaviour (deploying the container onto the hive isn't
|
literal behaviour (deploying the container onto the hive isn't
|
||||||
wired up server-side yet — see this file's top comment) —
|
wired up server-side yet — see this file's top comment) —
|
||||||
mara's explicit call on this copy: read as finished, not as a
|
mara's explicit call on this copy: read as finished, not as a
|
||||||
running commentary on partial implementation. */}
|
running commentary on partial implementation. The 🪪 glyph that
|
||||||
<Panel title="what this creates">
|
used to sit here as body copy now lives on `Panel`'s own `icon`
|
||||||
<p class="create-agent-info-glyph" aria-hidden="true">
|
prop instead — it's what prompted that prop to exist at all
|
||||||
🪪
|
(see Panel.tsx's doc comment) */}
|
||||||
</p>
|
<Panel title="what this creates" icon="🪪">
|
||||||
<p>
|
<p>
|
||||||
Submitting this queues everything a new agent needs: a swarm-level identity, a config
|
Submitting this queues everything a new agent needs: a swarm-level identity, a config
|
||||||
repo on the forge with the operator added as a collaborator, and a container running
|
repo on the forge with the operator added as a collaborator, and a container running
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ export function HivesPage() {
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
title="hives"
|
title="hives"
|
||||||
|
icon="🐝"
|
||||||
actions={<RefreshIntervalPicker id="hives-refresh" value={intervalMs} onChange={setIntervalMs} />}
|
actions={<RefreshIntervalPicker id="hives-refresh" value={intervalMs} onChange={setIntervalMs} />}
|
||||||
>
|
>
|
||||||
{error ? <ApiErrorPanel context="failed to load the hive roster" problem={error} /> : null}
|
{error ? <ApiErrorPanel context="failed to load the hive roster" problem={error} /> : null}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import './JobsPage.css';
|
||||||
|
|
||||||
export function JobsPage() {
|
export function JobsPage() {
|
||||||
return (
|
return (
|
||||||
<Panel title="jobs">
|
<Panel title="jobs" icon="🧩">
|
||||||
<JobqRollup endpoint="/api/jobq/rollup" />
|
<JobqRollup endpoint="/api/jobq/rollup" />
|
||||||
<JobqGraph endpoint="/api/jobq/graph" />
|
<JobqGraph endpoint="/api/jobq/graph" />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.ui-panel-icon {
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
/* `margin-left: auto` (not `justify-content: space-between` on the
|
/* `margin-left: auto` (not `justify-content: space-between` on the
|
||||||
header) so actions still land at the right edge even on the rare
|
header) so actions still land at the right edge even on the rare
|
||||||
panel that has actions but no title. */
|
panel that has actions but no title. */
|
||||||
|
|
|
||||||
|
|
@ -11,22 +11,44 @@
|
||||||
// dedicated row stealing vertical space from the panel's actual
|
// dedicated row stealing vertical space from the panel's actual
|
||||||
// content either). `HivesPage`'s refresh-interval picker is the
|
// content either). `HivesPage`'s refresh-interval picker is the
|
||||||
// motivating caller.
|
// motivating caller.
|
||||||
|
//
|
||||||
|
// `icon` is a small header glyph, left of the title — the swarm-ui
|
||||||
|
// design guide's own whimsy reference (before this, the guide only
|
||||||
|
// pointed at the dashboard's matrix-rain background). Grew out of
|
||||||
|
// CreateAgentPage's one-off 🪪 dropped straight into a panel's body
|
||||||
|
// copy: mara's call on review was that a single ad-hoc emoji isn't
|
||||||
|
// whimsy in the guide's sense (small, delightful, *consistent*), it
|
||||||
|
// should be a real theme every panel can opt into the same way. Plain
|
||||||
|
// `string` (an emoji literal), not an icon-library asset — same
|
||||||
|
// lightweight-glyph precedent `RefreshIntervalPicker`'s 🕐 set before
|
||||||
|
// this. Chosen per panel, no default: not every panel needs one, and
|
||||||
|
// there's no single semantic mapping (e.g. "jobs" pages) worth
|
||||||
|
// hardcoding. `aria-hidden` — decorative only, the title text still
|
||||||
|
// carries the actual label, so this never becomes a second source of
|
||||||
|
// truth an assistive-tech user has to parse.
|
||||||
import type { ComponentChildren } from 'preact';
|
import type { ComponentChildren } from 'preact';
|
||||||
import './Panel.css';
|
import './Panel.css';
|
||||||
|
|
||||||
export function Panel({
|
export function Panel({
|
||||||
title,
|
title,
|
||||||
|
icon,
|
||||||
actions,
|
actions,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
icon?: string;
|
||||||
actions?: ComponentChildren;
|
actions?: ComponentChildren;
|
||||||
children: ComponentChildren;
|
children: ComponentChildren;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section class="ui-panel">
|
<section class="ui-panel">
|
||||||
{title || actions ? (
|
{title || icon || actions ? (
|
||||||
<div class="ui-panel-header">
|
<div class="ui-panel-header">
|
||||||
|
{icon ? (
|
||||||
|
<span class="ui-panel-icon" aria-hidden="true">
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
{title ? <h2 class="ui-panel-title">{title}</h2> : null}
|
{title ? <h2 class="ui-panel-title">{title}</h2> : null}
|
||||||
{actions ? <div class="ui-panel-actions">{actions}</div> : null}
|
{actions ? <div class="ui-panel-actions">{actions}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue