Adopt shared WebUI structural primitives

This commit is contained in:
2026-08-18 13:17:31 +02:00
parent 86878ef7a7
commit 83e69565fe
2 changed files with 19 additions and 108 deletions
+18 -19
View File
@@ -4,11 +4,17 @@ import { ActionToolbar,
Button, Button,
DocumentationHelpLink, DocumentationHelpLink,
DismissibleAlert, DismissibleAlert,
FilterBar,
LoadingIndicator, LoadingIndicator,
PageScrollViewport, PageScrollViewport,
SelectionList,
SelectionListItem,
SelectionListItemContent,
StatePanel,
StatusBadge, StatusBadge,
i18nMessage, i18nMessage,
useGuardedNavigate, useGuardedNavigate,
WorkspaceFrame,
type PlatformRouteContext type PlatformRouteContext
} from "@govoplan/core-webui"; } from "@govoplan/core-webui";
import { import {
@@ -73,9 +79,9 @@ export default function CasesPage({ settings }: PlatformRouteContext) {
return ( return (
<main className="cases-page"> <main className="cases-page">
<div className="cases-shell"> <WorkspaceFrame className="cases-shell" label="Cases" interfaceId="cases.catalogue" helpContextId="cases.page.catalogue" helpModuleId="cases">
<ActionToolbar className="cases-toolbar"> <ActionToolbar surface="panel-header" className="cases-toolbar">
<form className="cases-search" onSubmit={submit}> <FilterBar as="form" surface="control" wrap="never" width="default" className="cases-search" onSubmit={submit}>
<Search size={17} aria-hidden="true" /> <Search size={17} aria-hidden="true" />
<input <input
value={query} value={query}
@@ -90,7 +96,7 @@ export default function CasesPage({ settings }: PlatformRouteContext) {
> >
Search Search
</Button> </Button>
</form> </FilterBar>
<label className="cases-status-filter"> <label className="cases-status-filter">
<span>Status</span> <span>Status</span>
<select value={status} onChange={(event) => setStatus(event.target.value)}> <select value={status} onChange={(event) => setStatus(event.target.value)}>
@@ -111,36 +117,29 @@ export default function CasesPage({ settings }: PlatformRouteContext) {
} }
{loading && <LoadingIndicator label="Loading cases" />} {loading && <LoadingIndicator label="Loading cases" />}
{!loading && !error && cases.length === 0 && {!loading && !error && cases.length === 0 &&
<div className="cases-empty">No matching cases.</div> <StatePanel size="compact" description="No matching cases." />
} }
{!loading && cases.length > 0 && {!loading && cases.length > 0 &&
<div className="cases-list" role="list"> <SelectionList variant="navigation" label="Cases">
{cases.map((item) => { {cases.map((item) => {
const statusDefinition = statusLabels.get(item.status_key); const statusDefinition = statusLabels.get(item.status_key);
return ( return (
<button <SelectionListItem
type="button" selected={false}
className="case-list-row"
role="listitem"
key={item.reference.object_id} key={item.reference.object_id}
onClick={() => navigate(`/cases/${encodeURIComponent(item.reference.object_id)}`)}> onClick={() => navigate(`/cases/${encodeURIComponent(item.reference.object_id)}`)}>
<div className="case-list-primary"> <SelectionListItemContent title={item.title} description={`${item.case_number} · ${typeLabels.get(item.case_type_key) ?? humanize(item.case_type_key)} · ${formatDate(item.deadline_at)}`} />
<strong>{item.title}</strong>
<span>{item.case_number}</span>
</div>
<span>{typeLabels.get(item.case_type_key) ?? humanize(item.case_type_key)}</span>
<span>{formatDate(item.deadline_at)}</span>
<StatusBadge <StatusBadge
status={statusDefinition?.terminal ? "inactive" : "active"} status={statusDefinition?.terminal ? "inactive" : "active"}
label={statusDefinition?.label ?? humanize(item.status_key)} label={statusDefinition?.label ?? humanize(item.status_key)}
/> />
</button> </SelectionListItem>
); );
})} })}
</div> </SelectionList>
} }
</PageScrollViewport> </PageScrollViewport>
</div> </WorkspaceFrame>
</main> </main>
); );
} }
+1 -89
View File
@@ -1,19 +1,16 @@
.cases-page, .cases-page,
.cases-shell,
.case-detail-shell { .case-detail-shell {
height: 100%; height: 100%;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
} }
.cases-shell,
.case-detail-shell { .case-detail-shell {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--surface); background: var(--surface);
} }
.cases-toolbar,
.case-detail-toolbar { .case-detail-toolbar {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -24,17 +21,7 @@
background: var(--surface-raised); background: var(--surface-raised);
} }
.cases-search { .cases-search { flex: 1 1 560px; }
display: flex;
align-items: center;
gap: 8px;
width: min(560px, 100%);
}
.cases-search input {
min-width: 120px;
flex: 1;
}
.cases-status-filter { .cases-status-filter {
display: flex; display: flex;
@@ -66,58 +53,6 @@
padding: 16px 18px 24px; padding: 16px 18px 24px;
} }
.cases-list {
overflow: hidden;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface-raised);
}
.case-list-row {
display: grid;
grid-template-columns: minmax(260px, 2fr) minmax(150px, 1fr) minmax(150px, 0.8fr) auto;
align-items: center;
gap: 16px;
width: 100%;
min-height: 66px;
padding: 10px 14px;
border: 0;
border-bottom: 1px solid var(--border);
background: transparent;
color: inherit;
text-align: left;
cursor: pointer;
}
.case-list-row:last-child {
border-bottom: 0;
}
.case-list-row:hover,
.case-list-row:focus-visible {
background: var(--hover-bg);
}
.case-list-primary {
display: flex;
min-width: 0;
flex-direction: column;
gap: 3px;
}
.case-list-primary strong,
.case-list-primary span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.case-list-primary span,
.case-list-row > span:not(.status-badge) {
color: var(--text-soft);
font-size: 0.84rem;
}
.case-detail-content { .case-detail-content {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(280px, 360px); grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
@@ -257,12 +192,6 @@
border-left: 2px solid var(--accent); border-left: 2px solid var(--accent);
} }
.cases-empty {
padding: 36px 0;
color: var(--text-soft);
text-align: center;
}
.case-share-dialog { .case-share-dialog {
width: min(860px, calc(100vw - 32px)); width: min(860px, calc(100vw - 32px));
max-height: min(760px, calc(100vh - 32px)); max-height: min(760px, calc(100vh - 32px));
@@ -398,27 +327,10 @@
} }
@media (max-width: 860px) { @media (max-width: 860px) {
.cases-toolbar {
align-items: stretch;
flex-direction: column;
}
.cases-search {
width: 100%;
}
.cases-count { .cases-count {
margin-left: 0; margin-left: 0;
} }
.case-list-row {
grid-template-columns: minmax(0, 1fr) auto;
}
.case-list-row > span:not(.status-badge) {
display: none;
}
.case-detail-content { .case-detail-content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }