Adopt shared WebUI structural primitives

This commit is contained in:
2026-08-18 13:17:31 +02:00
parent 0e03f6a70c
commit 75c9488f66
2 changed files with 14 additions and 56 deletions
+12 -9
View File
@@ -15,13 +15,16 @@ import { ActionToolbar,
Button, Button,
DocumentationHelpLink, DocumentationHelpLink,
DismissibleAlert, DismissibleAlert,
FilterBar,
IconButton, IconButton,
LoadingIndicator, LoadingIndicator,
PageScrollViewport, PageScrollViewport,
StatePanel,
StatusBadge, StatusBadge,
hasScope, hasScope,
i18nMessage, i18nMessage,
usePlatformLanguage, usePlatformLanguage,
WorkspaceFrame,
type PlatformRouteContext type PlatformRouteContext
} from "@govoplan/core-webui"; } from "@govoplan/core-webui";
import { import {
@@ -182,12 +185,12 @@ export default function CommitteePage({ settings, auth }: PlatformRouteContext)
return ( return (
<main className="committee-page"> <main className="committee-page">
<div className="committee-shell"> <WorkspaceFrame className="committee-shell" label="Committee workspace" interfaceId="committee.workspace" helpContextId="committee.page.workspace" helpModuleId="committee">
<ActionToolbar className="committee-toolbar"> <ActionToolbar surface="panel-header" className="committee-toolbar">
<form onSubmit={(event) => { event.preventDefault(); void refresh(); }} className="committee-search"> <FilterBar as="form" surface="control" wrap="never" width="compact" onSubmit={(event) => { event.preventDefault(); void refresh(); }} className="committee-search">
<Search size={16} aria-hidden="true" /> <Search size={16} aria-hidden="true" />
<input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Search bodies" aria-label="Search committee bodies" /> <input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Search bodies" aria-label="Search committee bodies" />
</form> </FilterBar>
<Button variant="ghost" onClick={() => void refresh()} disabled={loading} disabledReason={loading ? COMMITTEE_INTERFACE_I18N.loading : undefined}> <Button variant="ghost" onClick={() => void refresh()} disabled={loading} disabledReason={loading ? COMMITTEE_INTERFACE_I18N.loading : undefined}>
<RefreshCw size={16} aria-hidden="true" /> <RefreshCw size={16} aria-hidden="true" />
Refresh Refresh
@@ -262,7 +265,7 @@ export default function CommitteePage({ settings, auth }: PlatformRouteContext)
<section className="committee-detail" aria-label="Meeting workspace"> <section className="committee-detail" aria-label="Meeting workspace">
{!selectedMeeting ? ( {!selectedMeeting ? (
<div className="committee-empty">Select or create a meeting.</div> <StatePanel size="fill" title="Meetings" description="Select or create a meeting." />
) : ( ) : (
<> <>
<div className="committee-detail-heading"> <div className="committee-detail-heading">
@@ -310,7 +313,7 @@ export default function CommitteePage({ settings, auth }: PlatformRouteContext)
/> />
</div> </div>
))} ))}
{agendaItems.length === 0 ? <div className="committee-empty compact">No agenda items.</div> : null} {agendaItems.length === 0 ? <StatePanel size="inline" description="No agenda items." /> : null}
</div> </div>
</WorkspaceSection> </WorkspaceSection>
@@ -373,7 +376,7 @@ export default function CommitteePage({ settings, auth }: PlatformRouteContext)
)} )}
</section> </section>
</div> </div>
</div> </WorkspaceFrame>
{editor ? ( {editor ? (
<CommitteeRecordDialog <CommitteeRecordDialog
@@ -410,7 +413,7 @@ function RecordList({ records, selectedId, onSelect, secondary }: {
onSelect: (id: string) => void; onSelect: (id: string) => void;
secondary?: (record: CommitteeRecord) => string; secondary?: (record: CommitteeRecord) => string;
}) { }) {
if (records.length === 0) return <div className="committee-empty compact">No records.</div>; if (records.length === 0) return <StatePanel size="inline" description="No records." />;
return <div className="committee-record-list">{records.map((record) => ( return <div className="committee-record-list">{records.map((record) => (
<button key={record.object_id} type="button" className={record.object_id === selectedId ? "is-selected" : ""} onClick={() => onSelect(record.object_id)}> <button key={record.object_id} type="button" className={record.object_id === selectedId ? "is-selected" : ""} onClick={() => onSelect(record.object_id)}>
<strong>{record.title}</strong> <strong>{record.title}</strong>
@@ -430,7 +433,7 @@ function RecordRows({ records, editDisabledReason, onEdit, detail, secondaryActi
detail: (record: CommitteeRecord) => string; detail: (record: CommitteeRecord) => string;
secondaryAction?: (record: CommitteeRecord) => ReactNode; secondaryAction?: (record: CommitteeRecord) => ReactNode;
}) { }) {
if (records.length === 0) return <div className="committee-empty compact">No records.</div>; if (records.length === 0) return <StatePanel size="inline" description="No records." />;
return <div className="committee-record-rows">{records.map((record) => ( return <div className="committee-record-rows">{records.map((record) => (
<div key={record.object_id}> <div key={record.object_id}>
<span><strong>{record.title}</strong><small>{detail(record)}</small></span> <span><strong>{record.title}</strong><small>{detail(record)}</small></span>
+2 -47
View File
@@ -1,39 +1,14 @@
.committee-page, .committee-page {
.committee-shell {
height: 100%; height: 100%;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
} }
.committee-shell {
display: flex;
flex-direction: column;
background: var(--surface);
}
.committee-toolbar {
display: flex;
align-items: center;
gap: 9px;
min-height: 58px;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
background: var(--surface-raised);
}
.committee-search { .committee-search {
display: flex; flex: 1 1 460px;
align-items: center;
gap: 8px;
width: min(460px, 100%);
margin-right: auto; margin-right: auto;
} }
.committee-search input {
min-width: 140px;
flex: 1;
}
.committee-alert { .committee-alert {
margin: 10px 16px 0; margin: 10px 16px 0;
} }
@@ -231,17 +206,6 @@
gap: 4px !important; gap: 4px !important;
} }
.committee-empty {
display: grid;
min-height: 180px;
place-items: center;
color: var(--text-soft);
}
.committee-empty.compact {
min-height: 72px;
}
.committee-record-dialog { .committee-record-dialog {
width: min(820px, calc(100vw - 32px)); width: min(820px, calc(100vw - 32px));
max-height: min(820px, calc(100vh - 32px)); max-height: min(820px, calc(100vh - 32px));
@@ -292,15 +256,6 @@
} }
@media (max-width: 760px) { @media (max-width: 760px) {
.committee-toolbar {
align-items: stretch;
flex-wrap: wrap;
}
.committee-search {
width: 100%;
}
.committee-workspace { .committee-workspace {
grid-template-columns: minmax(150px, 0.8fr) minmax(0, 1.8fr); grid-template-columns: minmax(150px, 0.8fr) minmax(0, 1.8fr);
grid-template-rows: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr));