Adopt shared WebUI structural primitives

This commit is contained in:
2026-08-18 13:17:32 +02:00
parent 225a3a233b
commit 6b0e3ae37d
3 changed files with 24 additions and 88 deletions
@@ -26,6 +26,7 @@ import { FormGrid,
SegmentedControl, SegmentedControl,
SelectionList, SelectionList,
SelectionListItem, SelectionListItem,
StatePanel,
StatusBadge, StatusBadge,
ToggleSwitch, ToggleSwitch,
i18nMessage, i18nMessage,
@@ -800,11 +801,7 @@ function TemplateWorkspace({
</div> </div>
</> </>
) : ( ) : (
<div className="postbox-empty"> <StatePanel size="fill" icon={<Boxes size={24} />} title="Select a template" description="Published revisions lazily resolve stable unit-specific addresses." />
<Boxes size={24} />
<strong>Select a template</strong>
<p>Published revisions lazily resolve stable unit-specific addresses.</p>
</div>
)} )}
</section> </section>
</div> </div>
@@ -878,11 +875,7 @@ function PostboxWorkspace({
</dl> </dl>
</> </>
) : ( ) : (
<div className="postbox-empty"> <StatePanel size="fill" icon={<Inbox size={24} />} title="Select a Postbox" description="Materialized Postboxes remain durable through vacancy and reassignment." />
<Inbox size={24} />
<strong>Select a Postbox</strong>
<p>Materialized Postboxes remain durable through vacancy and reassignment.</p>
</div>
)} )}
</section> </section>
</div> </div>
+20 -31
View File
@@ -20,17 +20,21 @@ import {
} from "lucide-react"; } from "lucide-react";
import { FormGrid, import { FormGrid,
ActionBlockerHint, ActionBlockerHint,
ActionToolbar,
Button, Button,
ConfirmDialog, ConfirmDialog,
CountBadge,
DataGridPaginationBar, DataGridPaginationBar,
Dialog, Dialog,
DismissibleAlert, DismissibleAlert,
DocumentationHelpLink, DocumentationHelpLink,
FormField, FormField,
FilterBar,
IconButton, IconButton,
SegmentedControl, SegmentedControl,
SelectionList, SelectionList,
SelectionListItem, SelectionListItem,
StatePanel,
StatusBadge, StatusBadge,
ToggleSwitch, ToggleSwitch,
hasScope, hasScope,
@@ -585,7 +589,7 @@ export default function PostboxPage({
<main className="workspace-data-page module-entry-page postbox-page"> <main className="workspace-data-page module-entry-page postbox-page">
<div className="postbox-shell"> <div className="postbox-shell">
<aside className="postbox-directory" data-view-surface="postbox.inbox.directory"> <aside className="postbox-directory" data-view-surface="postbox.inbox.directory">
<div className="postbox-bar"> <ActionToolbar surface="panel-header" className="postbox-bar">
<div className="postbox-bar-title"> <div className="postbox-bar-title">
<Inbox size={17} aria-hidden="true" /> <Inbox size={17} aria-hidden="true" />
<strong>Postbox</strong> <strong>Postbox</strong>
@@ -607,7 +611,7 @@ export default function PostboxPage({
disabledReason={postboxBusyReason(loadingDirectory, busy)} disabledReason={postboxBusyReason(loadingDirectory, busy)}
/> />
</div> </div>
</div> </ActionToolbar>
<div className="postbox-scope-control"> <div className="postbox-scope-control">
<label htmlFor="postbox-scope">Inbox view</label> <label htmlFor="postbox-scope">Inbox view</label>
@@ -639,10 +643,7 @@ export default function PostboxPage({
<div className="postbox-directory-list"> <div className="postbox-directory-list">
{loadingDirectory ? <p className="postbox-note">Loading postboxes</p> : null} {loadingDirectory ? <p className="postbox-note">Loading postboxes</p> : null}
{!loadingDirectory && !postboxes.length ? ( {!loadingDirectory && !postboxes.length ? (
<div className="postbox-empty compact"> <StatePanel size="compact" icon={<Archive size={20} />} title="No assigned postboxes" description="Postboxes appear when your account has a current matching function assignment.">
<Archive size={20} />
<strong>No assigned postboxes</strong>
<p>Postboxes appear when your account has a current matching function assignment.</p>
<ActionBlockerHint <ActionBlockerHint
reason={{ reason={{
summary: POSTBOX_INTERFACE_I18N.noPostbox, summary: POSTBOX_INTERFACE_I18N.noPostbox,
@@ -657,10 +658,10 @@ export default function PostboxPage({
}} }}
documentation={POSTBOX_DOCUMENTATION} documentation={POSTBOX_DOCUMENTATION}
/> />
</div> </StatePanel>
) : null} ) : null}
{postboxes.length ? ( {postboxes.length ? (
<SelectionList label="Assigned postboxes"> <SelectionList variant="navigation" label="Assigned postboxes">
{postboxes.map((postbox) => ( {postboxes.map((postbox) => (
<SelectionListItem <SelectionListItem
key={postbox.id} key={postbox.id}
@@ -687,7 +688,7 @@ export default function PostboxPage({
</aside> </aside>
<section className="postbox-message-list" data-view-surface="postbox.inbox.messages"> <section className="postbox-message-list" data-view-surface="postbox.inbox.messages">
<div className="postbox-bar"> <ActionToolbar surface="panel-header" className="postbox-bar">
<div className="postbox-bar-title"> <div className="postbox-bar-title">
{selectedPostbox ? ( {selectedPostbox ? (
<> <>
@@ -705,7 +706,7 @@ export default function PostboxPage({
<strong>All postboxes</strong> <strong>All postboxes</strong>
</> </>
)} )}
<span className="postbox-total">{total}</span> <CountBadge tone="neutral">{total}</CountBadge>
</div> </div>
<div className="postbox-icon-actions"> <div className="postbox-icon-actions">
<IconButton <IconButton
@@ -723,9 +724,9 @@ export default function PostboxPage({
disabledReason={postboxBusyReason(loadingMessages, busy)} disabledReason={postboxBusyReason(loadingMessages, busy)}
/> />
</div> </div>
</div> </ActionToolbar>
<div className="postbox-message-filters"> <div className="postbox-message-filters">
<div className="postbox-search-row"> <FilterBar surface="control" wrap="never" className="postbox-search-row">
<input <input
type="search" type="search"
value={searchDraft} value={searchDraft}
@@ -764,7 +765,7 @@ export default function PostboxPage({
setSelectedMessage(null); setSelectedMessage(null);
}} }}
/> />
</div> </FilterBar>
<SegmentedControl<MessageStateFilter> <SegmentedControl<MessageStateFilter>
ariaLabel="Message state" ariaLabel="Message state"
width="fill" width="fill"
@@ -791,14 +792,10 @@ export default function PostboxPage({
<div className="postbox-messages"> <div className="postbox-messages">
{loadingMessages ? <p className="postbox-note">Loading messages</p> : null} {loadingMessages ? <p className="postbox-note">Loading messages</p> : null}
{!loadingMessages && !messages.length ? ( {!loadingMessages && !messages.length ? (
<div className="postbox-empty"> <StatePanel size="fill" icon={<MailOpen size={24} />} title="No messages" description="This view has no delivered Postbox messages." />
<MailOpen size={24} />
<strong>No messages</strong>
<p>This view has no delivered Postbox messages.</p>
</div>
) : null} ) : null}
{messages.length ? ( {messages.length ? (
<SelectionList label="Postbox messages"> <SelectionList variant="navigation" label="Postbox messages">
{messages.map((message) => ( {messages.map((message) => (
<SelectionListItem <SelectionListItem
key={message.id} key={message.id}
@@ -855,7 +852,7 @@ export default function PostboxPage({
</section> </section>
<section className="postbox-detail"> <section className="postbox-detail">
<div className="postbox-bar"> <ActionToolbar surface="panel-header" className="postbox-bar">
<div className="postbox-bar-title"> <div className="postbox-bar-title">
<MailOpen size={17} aria-hidden="true" /> <MailOpen size={17} aria-hidden="true" />
<strong>{selectedMessage?.subject || "Message"}</strong> <strong>{selectedMessage?.subject || "Message"}</strong>
@@ -876,7 +873,7 @@ export default function PostboxPage({
<CheckCheck size={16} /> Acknowledge <CheckCheck size={16} /> Acknowledge
</Button> </Button>
</div> </div>
</div> </ActionToolbar>
{selectedMessage ? ( {selectedMessage ? (
<MessageDetail <MessageDetail
message={selectedMessage} message={selectedMessage}
@@ -889,17 +886,9 @@ export default function PostboxPage({
}} }}
/> />
) : unavailableSelection ? ( ) : unavailableSelection ? (
<div className="postbox-empty postbox-unavailable-message"> <StatePanel size="fill" tone="warning" icon={<Archive size={24} />} title="Message unavailable" description={unavailableSelection} className="postbox-unavailable-message" />
<Archive size={24} />
<strong>Message unavailable</strong>
<p>{unavailableSelection}</p>
</div>
) : ( ) : (
<div className="postbox-empty"> <StatePanel size="fill" icon={<Inbox size={24} />} title="Select a message" description="Source, function context, content, and evidence remain attached to the originating postbox." />
<Inbox size={24} />
<strong>Select a message</strong>
<p>Source, function context, content, and evidence remain attached to the originating postbox.</p>
</div>
)} )}
</section> </section>
</div> </div>
+1 -47
View File
@@ -52,7 +52,6 @@
background: var(--panel-soft); background: var(--panel-soft);
} }
.postbox-bar,
.postbox-bar-title, .postbox-bar-title,
.postbox-icon-actions, .postbox-icon-actions,
.postbox-scope-row, .postbox-scope-row,
@@ -68,16 +67,6 @@
align-items: center; align-items: center;
} }
.postbox-bar {
flex: 0 0 auto;
min-height: 54px;
justify-content: space-between;
gap: 10px;
border-bottom: var(--border-line);
background: var(--panel-header);
padding: 8px 12px;
}
.postbox-bar-title { .postbox-bar-title {
min-width: 0; min-width: 0;
gap: 8px; gap: 8px;
@@ -95,18 +84,6 @@
gap: 5px; gap: 5px;
} }
.postbox-total {
min-width: 24px;
height: 22px;
display: inline-grid;
place-items: center;
border-radius: 999px;
background: var(--line);
color: var(--text-strong);
font-size: 12px;
font-weight: 800;
}
.postbox-scope-control { .postbox-scope-control {
flex: 0 0 auto; flex: 0 0 auto;
border-bottom: var(--border-line); border-bottom: var(--border-line);
@@ -152,9 +129,7 @@
} }
.postbox-search-row { .postbox-search-row {
display: flex; width: 100%;
align-items: center;
gap: 5px;
} }
.postbox-search-row input { .postbox-search-row input {
@@ -383,27 +358,6 @@
line-height: 1.4; line-height: 1.4;
} }
.postbox-empty {
min-height: 100%;
display: grid;
place-items: center;
align-content: center;
gap: 7px;
padding: 30px;
color: var(--muted);
text-align: center;
}
.postbox-empty.compact {
min-height: 180px;
padding: 20px;
}
.postbox-empty strong {
color: var(--text-strong);
}
.postbox-empty p,
.postbox-note { .postbox-note {
max-width: 470px; max-width: 470px;
margin: 0; margin: 0;