swarm-ui: shared Hint, ActionRow, Toolbar, EmptyState, ExternalLink and ExpandableCard replace per-page copies
Hint, ActionRow, Toolbar, EmptyState and ExternalLink replace the muted text, button row, refresh bar, empty line and _blank anchor that each page restyled under its own class. ExpandableCard (with a loading twin) holds the builds and recent-runs cards, which had identical head and body CSS. The create form's raw checkbox becomes CheckboxField. The components page shows each new piece.
This commit is contained in:
parent
b74b5e77e4
commit
8a2e5203ad
14 changed files with 689 additions and 427 deletions
|
|
@ -8,6 +8,5 @@
|
|||
margin-top: 1em;
|
||||
}
|
||||
.claude-login-ok {
|
||||
margin-top: 1em;
|
||||
color: var(--green);
|
||||
margin-block: 1em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
|||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { ExternalLink } from "../../ui/external-link/ExternalLink.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { Panel } from "../../ui/panel/Panel.js";
|
||||
import { TextField } from "../../ui/text-field/TextField.js";
|
||||
import {
|
||||
|
|
@ -98,9 +100,7 @@ export function ClaudeLoginDialog({
|
|||
<>
|
||||
<p>
|
||||
Open{" "}
|
||||
<a href={step.url} target="_blank" rel="noopener noreferrer">
|
||||
the claude login page
|
||||
</a>
|
||||
<ExternalLink href={step.url}>the claude login page</ExternalLink>
|
||||
, sign in with this agent's account, and paste the code it shows.
|
||||
</p>
|
||||
<form class="claude-login-form" onSubmit={submit}>
|
||||
|
|
@ -122,10 +122,10 @@ export function ClaudeLoginDialog({
|
|||
</>
|
||||
)}
|
||||
{step.step === "stored" && (
|
||||
<p class="claude-login-ok">
|
||||
<Hint tone="ok" class="claude-login-ok">
|
||||
Stored. <strong>{agent}</strong> picks the login up within a minute
|
||||
and resumes.
|
||||
</p>
|
||||
</Hint>
|
||||
)}
|
||||
{step.step === "failed" && (
|
||||
<ApiErrorPanel
|
||||
|
|
|
|||
|
|
@ -17,11 +17,6 @@
|
|||
flex-wrap: wrap;
|
||||
gap: 0.4em 1em;
|
||||
}
|
||||
.ui-agent-detail-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75em;
|
||||
}
|
||||
|
||||
/* The folded sections of an opened card sit one under another, each with the
|
||||
shared disclosure look; the fields keep the grid above. */
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@
|
|||
// change or a link made here shows wherever else the same row is drawn.
|
||||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { ExternalLink } from "../../ui/external-link/ExternalLink.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
|
||||
import type { AgentActions } from "../roster/AgentActions.js";
|
||||
import { AgentInference } from "./AgentInference.js";
|
||||
|
|
@ -118,12 +121,10 @@ function OverviewRows({
|
|||
<>
|
||||
<dt>forge</dt>
|
||||
<dd class="ui-agent-detail-links">
|
||||
<a href={forgeLinks.profile} target="_blank" rel="noreferrer">
|
||||
profile
|
||||
</a>
|
||||
<a href={forgeLinks.configRepo} target="_blank" rel="noreferrer">
|
||||
<ExternalLink href={forgeLinks.profile}>profile</ExternalLink>
|
||||
<ExternalLink href={forgeLinks.configRepo}>
|
||||
config repo
|
||||
</a>
|
||||
</ExternalLink>
|
||||
</dd>
|
||||
</>
|
||||
) : null}
|
||||
|
|
@ -134,13 +135,9 @@ function OverviewRows({
|
|||
tone="warning"
|
||||
value={
|
||||
row.config_pr.html_url ? (
|
||||
<a
|
||||
href={row.config_pr.html_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ExternalLink href={row.config_pr.html_url}>
|
||||
#{row.config_pr.pr_number}
|
||||
</a>
|
||||
</ExternalLink>
|
||||
) : (
|
||||
`#${row.config_pr.pr_number}`
|
||||
)
|
||||
|
|
@ -211,7 +208,7 @@ function GrantRows({ agent }: { agent: string }) {
|
|||
<dt>permissions</dt>
|
||||
<dd>
|
||||
{g.grant === null ? (
|
||||
<span class="ui-agent-limits-muted">—</span>
|
||||
<Hint as="span">—</Hint>
|
||||
) : (
|
||||
<>
|
||||
{g.readFailure}
|
||||
|
|
@ -257,13 +254,13 @@ function LinkAccountAction({
|
|||
actions: AgentActions;
|
||||
}) {
|
||||
return (
|
||||
<div class="ui-agent-detail-actions">
|
||||
<ActionRow class="ui-agent-detail-actions">
|
||||
<LinkAccountMenu
|
||||
agent={row.name}
|
||||
hasHive={row.hive !== null}
|
||||
onSelect={(kind) => actions.openLink(row, kind)}
|
||||
/>
|
||||
</div>
|
||||
</ActionRow>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@
|
|||
.ui-agent-inference-source {
|
||||
margin-left: -0.25em;
|
||||
}
|
||||
.ui-agent-inference-source,
|
||||
.ui-agent-inference-muted {
|
||||
.ui-agent-inference-source {
|
||||
color: var(--muted);
|
||||
}
|
||||
.ui-agent-inference-form {
|
||||
|
|
@ -28,12 +27,3 @@
|
|||
gap: 0.75em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.ui-agent-inference-problem {
|
||||
margin: 0;
|
||||
color: var(--red);
|
||||
}
|
||||
.ui-agent-inference-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ import { useEffect, useState } from "preact/hooks";
|
|||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { Panel } from "../../ui/panel/Panel.js";
|
||||
import { SelectField } from "../../ui/select-field/SelectField.js";
|
||||
import { TextField } from "../../ui/text-field/TextField.js";
|
||||
|
|
@ -171,7 +173,7 @@ export function InferenceFields({
|
|||
pattern="[0-9]*"
|
||||
onInput={(maxSubagents) => onChange({ ...draft, maxSubagents })}
|
||||
/>
|
||||
{problem ? <p class="ui-agent-inference-problem">{problem}</p> : null}
|
||||
{problem ? <Hint tone="error">{problem}</Hint> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -215,7 +217,7 @@ export function AgentInference({ agent }: { agent: string }) {
|
|||
/>
|
||||
) : null;
|
||||
if (view === null) {
|
||||
return errorPanel ?? <span class="ui-agent-inference-muted">…</span>;
|
||||
return errorPanel ?? <Hint as="span">…</Hint>;
|
||||
}
|
||||
|
||||
const { configured, effective } = view;
|
||||
|
|
@ -368,7 +370,7 @@ function InferenceForm({
|
|||
{problem ? (
|
||||
<ApiErrorPanel context="saving failed" problem={problem} />
|
||||
) : null}
|
||||
<div class="ui-agent-inference-actions">
|
||||
<ActionRow>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
|
|
@ -382,7 +384,7 @@ function InferenceForm({
|
|||
>
|
||||
clear the swarm's setting
|
||||
</Button>
|
||||
</div>
|
||||
</ActionRow>
|
||||
</form>
|
||||
</Panel>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -38,9 +38,3 @@
|
|||
border: 1px solid var(--red);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.ui-agent-instructions-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
|
|||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { Panel } from "../../ui/panel/Panel.js";
|
||||
|
|
@ -235,9 +236,9 @@ function InstructionsForm({
|
|||
context="loading AGENTS.md failed"
|
||||
problem={load.problem}
|
||||
/>
|
||||
<div class="ui-agent-instructions-actions">
|
||||
<ActionRow class="ui-agent-instructions-actions">
|
||||
<Button onClick={reload}>try again</Button>
|
||||
</div>
|
||||
</ActionRow>
|
||||
</>
|
||||
) : null}
|
||||
{load.status === "ready" ? (
|
||||
|
|
@ -263,18 +264,18 @@ function InstructionsForm({
|
|||
<pre class="ui-agent-instructions-theirs" tabIndex={0}>
|
||||
{theirs.content === "" ? "(empty)" : theirs.content}
|
||||
</pre>
|
||||
<div class="ui-agent-instructions-actions">
|
||||
<ActionRow class="ui-agent-instructions-actions">
|
||||
<Button onClick={keepMine}>
|
||||
keep my text, replace {agent}'s changes
|
||||
</Button>
|
||||
<Button onClick={takeTheirs}>use {agent}'s version</Button>
|
||||
</div>
|
||||
</ActionRow>
|
||||
</section>
|
||||
) : null}
|
||||
{problem ? (
|
||||
<ApiErrorPanel context="saving failed" problem={problem} />
|
||||
) : null}
|
||||
<div class="ui-agent-instructions-actions">
|
||||
<ActionRow class="ui-agent-instructions-actions">
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
|
|
@ -305,7 +306,7 @@ function InstructionsForm({
|
|||
>
|
||||
reload from {agent}
|
||||
</Button>
|
||||
</div>
|
||||
</ActionRow>
|
||||
</form>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,10 +23,4 @@
|
|||
}
|
||||
.ui-agent-limits-hint {
|
||||
margin: 0 0 0.5em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.ui-agent-limits-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ import { useEffect, useState } from "preact/hooks";
|
|||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { Panel } from "../../ui/panel/Panel.js";
|
||||
import { TextField } from "../../ui/text-field/TextField.js";
|
||||
import { BadgeSkeleton } from "../../ui/skeleton/BadgeSkeleton.js";
|
||||
|
|
@ -71,9 +73,9 @@ export function LimitsFields({
|
|||
placeholder="e.g. 400%"
|
||||
onInput={onCpu}
|
||||
/>
|
||||
<p class="ui-agent-limits-hint">
|
||||
<Hint class="ui-agent-limits-hint">
|
||||
A percentage of one core: 100% is one core, 400% is four.
|
||||
</p>
|
||||
</Hint>
|
||||
<TextField
|
||||
id={`${idPrefix}-memory-max`}
|
||||
label="memory max"
|
||||
|
|
@ -81,10 +83,10 @@ export function LimitsFields({
|
|||
placeholder="e.g. 8G"
|
||||
onInput={onMemory}
|
||||
/>
|
||||
<p class="ui-agent-limits-hint">
|
||||
<Hint class="ui-agent-limits-hint">
|
||||
A size such as 512M or 8G, a percentage of the machine's memory such as
|
||||
50%, or infinity.
|
||||
</p>
|
||||
</Hint>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -239,7 +241,7 @@ function LimitsForm({
|
|||
{problem ? (
|
||||
<ApiErrorPanel context="the caps were not saved" problem={problem} />
|
||||
) : null}
|
||||
<div class="ui-agent-limits-actions">
|
||||
<ActionRow>
|
||||
<Button variant="primary" type="submit" disabled={busy || empty}>
|
||||
{busy ? "saving…" : "save"}
|
||||
</Button>
|
||||
|
|
@ -249,7 +251,7 @@ function LimitsForm({
|
|||
>
|
||||
clear the swarm's caps
|
||||
</Button>
|
||||
</div>
|
||||
</ActionRow>
|
||||
</form>
|
||||
</Panel>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { Badge } from "@hive/shared/badge.js";
|
|||
import { BadgePopover } from "../../ui/badge-popover/BadgePopover.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { BadgeSkeleton } from "../../ui/skeleton/BadgeSkeleton.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { MultiselectFilterOptions } from "../../ui/multiselect-filter/MultiselectFilter.js";
|
||||
import {
|
||||
requestPermissions,
|
||||
|
|
@ -225,9 +226,9 @@ export function AgentPermissions({
|
|||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span class="ui-agent-limits-muted" role="status">
|
||||
<Hint as="span" role="status">
|
||||
{catalogueError ? "could not load the permissions" : "loading…"}
|
||||
</span>
|
||||
</Hint>
|
||||
)}
|
||||
{saveError ? (
|
||||
<ApiErrorPanel
|
||||
|
|
@ -287,7 +288,7 @@ export function AgentInternet({ agent, g }: { agent: string; g: GrantState }) {
|
|||
})
|
||||
}
|
||||
/>{" "}
|
||||
<span class="ui-agent-limits-muted">{meaning}</span>
|
||||
<Hint as="span">{meaning}</Hint>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { useState } from "preact/hooks";
|
|||
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
|
||||
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
||||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { ActionRow } from "../../ui/action-row/ActionRow.js";
|
||||
import { BadgePopover } from "../../ui/badge-popover/BadgePopover.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
|
||||
|
|
@ -232,7 +233,7 @@ export function AddForm({
|
|||
{problem ? (
|
||||
<ApiErrorPanel context="the domain was not added" problem={problem} />
|
||||
) : null}
|
||||
<div class="ui-agent-limits-actions">
|
||||
<ActionRow>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
|
|
@ -240,7 +241,7 @@ export function AddForm({
|
|||
>
|
||||
{busy ? "adding…" : "add"}
|
||||
</Button>
|
||||
</div>
|
||||
</ActionRow>
|
||||
</form>
|
||||
</Panel>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
gap: 0.5em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ui-linked-accounts-host,
|
||||
.ui-linked-accounts-muted {
|
||||
.ui-linked-accounts-host {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
|
|||
import { Badge } from "@hive/shared/badge.js";
|
||||
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
|
||||
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
|
||||
import { Hint } from "../../ui/hint/Hint.js";
|
||||
import { accountKindLabel } from "./account-kind.js";
|
||||
import { deleteUrl, listUrl } from "./account-api.js";
|
||||
import "./LinkedAccounts.css";
|
||||
|
|
@ -145,13 +146,13 @@ export function LinkedAccounts({
|
|||
<ApiErrorPanel context="listing linked accounts failed" problem={error} />
|
||||
) : null;
|
||||
if (accounts === null) {
|
||||
return errorPanel ?? <span class="ui-linked-accounts-muted">…</span>;
|
||||
return errorPanel ?? <Hint as="span">…</Hint>;
|
||||
}
|
||||
if (accounts.length === 0) {
|
||||
return (
|
||||
<>
|
||||
{errorPanel}
|
||||
<span class="ui-linked-accounts-muted">none linked</span>
|
||||
<Hint as="span">none linked</Hint>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue