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,
DocumentationHelpLink,
DismissibleAlert,
FilterBar,
LoadingIndicator,
PageScrollViewport,
SelectionList,
SelectionListItem,
SelectionListItemContent,
StatePanel,
StatusBadge,
i18nMessage,
useGuardedNavigate,
WorkspaceFrame,
type PlatformRouteContext
} from "@govoplan/core-webui";
import {
@@ -73,9 +79,9 @@ export default function CasesPage({ settings }: PlatformRouteContext) {
return (
<main className="cases-page">
<div className="cases-shell">
<ActionToolbar className="cases-toolbar">
<form className="cases-search" onSubmit={submit}>
<WorkspaceFrame className="cases-shell" label="Cases" interfaceId="cases.catalogue" helpContextId="cases.page.catalogue" helpModuleId="cases">
<ActionToolbar surface="panel-header" className="cases-toolbar">
<FilterBar as="form" surface="control" wrap="never" width="default" className="cases-search" onSubmit={submit}>
<Search size={17} aria-hidden="true" />
<input
value={query}
@@ -90,7 +96,7 @@ export default function CasesPage({ settings }: PlatformRouteContext) {
>
Search
</Button>
</form>
</FilterBar>
<label className="cases-status-filter">
<span>Status</span>
<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 && !error && cases.length === 0 &&
<div className="cases-empty">No matching cases.</div>
<StatePanel size="compact" description="No matching cases." />
}
{!loading && cases.length > 0 &&
<div className="cases-list" role="list">
<SelectionList variant="navigation" label="Cases">
{cases.map((item) => {
const statusDefinition = statusLabels.get(item.status_key);
return (
<button
type="button"
className="case-list-row"
role="listitem"
<SelectionListItem
selected={false}
key={item.reference.object_id}
onClick={() => navigate(`/cases/${encodeURIComponent(item.reference.object_id)}`)}>
<div className="case-list-primary">
<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>
<SelectionListItemContent title={item.title} description={`${item.case_number} · ${typeLabels.get(item.case_type_key) ?? humanize(item.case_type_key)} · ${formatDate(item.deadline_at)}`} />
<StatusBadge
status={statusDefinition?.terminal ? "inactive" : "active"}
label={statusDefinition?.label ?? humanize(item.status_key)}
/>
</button>
</SelectionListItem>
);
})}
</div>
</SelectionList>
}
</PageScrollViewport>
</div>
</WorkspaceFrame>
</main>
);
}
+1 -89
View File
@@ -1,19 +1,16 @@
.cases-page,
.cases-shell,
.case-detail-shell {
height: 100%;
min-height: 0;
overflow: hidden;
}
.cases-shell,
.case-detail-shell {
display: flex;
flex-direction: column;
background: var(--surface);
}
.cases-toolbar,
.case-detail-toolbar {
display: flex;
align-items: center;
@@ -24,17 +21,7 @@
background: var(--surface-raised);
}
.cases-search {
display: flex;
align-items: center;
gap: 8px;
width: min(560px, 100%);
}
.cases-search input {
min-width: 120px;
flex: 1;
}
.cases-search { flex: 1 1 560px; }
.cases-status-filter {
display: flex;
@@ -66,58 +53,6 @@
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 {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
@@ -257,12 +192,6 @@
border-left: 2px solid var(--accent);
}
.cases-empty {
padding: 36px 0;
color: var(--text-soft);
text-align: center;
}
.case-share-dialog {
width: min(860px, calc(100vw - 32px));
max-height: min(760px, calc(100vh - 32px));
@@ -398,27 +327,10 @@
}
@media (max-width: 860px) {
.cases-toolbar {
align-items: stretch;
flex-direction: column;
}
.cases-search {
width: 100%;
}
.cases-count {
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 {
grid-template-columns: 1fr;
}