Adopt shared WebUI structural primitives

This commit is contained in:
2026-08-18 13:17:31 +02:00
parent abf35d75f3
commit 50e0172f65
5 changed files with 27 additions and 73 deletions
@@ -1,6 +1,7 @@
import { ArrowDown, ArrowUp, Eye, Languages, Plus, Trash2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { FormGrid,
ActionToolbar,
Button,
ConfirmDialog,
Dialog,
@@ -262,12 +263,12 @@ export default function FormDefinitionDialog({
</Field>
</FormGrid>
<div className="form-field-editor-heading">
<ActionToolbar surface="section-header" className="form-field-editor-heading">
<h3>Fields</h3>
<Button onClick={() => setDraft(addField(draft))} disabled={busy}>
<Plus size={16} aria-hidden="true" />Add field
</Button>
</div>
</ActionToolbar>
<div className="form-field-editor-list">
{draft.fields.map((field, index) =>
<div className="form-field-editor-row" key={`${index}:${field.key}`}>
@@ -417,12 +418,12 @@ function PageEditor({ draft, disabled, onChange }: { draft: FormDefinition; disa
}
return (
<section className="form-composition-section">
<div className="form-field-editor-heading">
<ActionToolbar surface="section-header" className="form-field-editor-heading">
<h3>Pages and sections</h3>
{pages.length === 0
? <Button disabled={disabled} onClick={() => updatePages([defaultPage(draft.fields)])}><Plus size={16} aria-hidden="true" />Enable pages</Button>
: <Button disabled={disabled} onClick={() => updatePages([...pages, emptyPage(pages.length + 1)])}><Plus size={16} aria-hidden="true" />Add page</Button>}
</div>
</ActionToolbar>
{pages.length === 0 && <p className="form-section-note">Fields render in their declared order on one page.</p>}
{pages.map((page, pageIndex) =>
<div className="form-page-editor" key={`${pageIndex}:${page.key}`}>
@@ -470,10 +471,10 @@ function LocalizationEditor({ draft, disabled, onChange }: { draft: FormDefiniti
}
return (
<section className="form-composition-section">
<div className="form-field-editor-heading">
<ActionToolbar surface="section-header" className="form-field-editor-heading">
<h3><Languages size={17} aria-hidden="true" />Localizations</h3>
<Button disabled={disabled} onClick={() => update([...localizations, emptyLocalization()])}><Plus size={16} aria-hidden="true" />Add locale</Button>
</div>
</ActionToolbar>
{localizations.length === 0 && <p className="form-section-note">The canonical labels are used for every locale.</p>}
{localizations.map((localization, index) =>
<div className="form-localization-editor" key={`${index}:${localization.locale}`}>
@@ -505,7 +506,7 @@ function DefinitionPreview({ definition, previous }: { definition: FormDefinitio
const changed = previous ? definitionChanges(previous, definition) : ["New definition"];
return (
<section className="form-composition-section form-definition-preview">
<div className="form-field-editor-heading"><h3><Eye size={17} aria-hidden="true" />Preview and revision changes</h3></div>
<ActionToolbar surface="section-header" className="form-field-editor-heading"><h3><Eye size={17} aria-hidden="true" />Preview and revision changes</h3></ActionToolbar>
<FormGrid columns={2} collapseAt="narrow" className="form-preview-grid">
<div>
<strong>{definition.title || "Untitled Form"}</strong>
+9 -6
View File
@@ -8,12 +8,15 @@ import { ActionToolbar,
DismissibleAlert,
IconButton,
FormField,
FilterBar,
LoadingIndicator,
PageScrollViewport,
StatePanel,
StatusBadge,
hasScope,
useUnsavedChanges,
useUnsavedDraftGuard,
WorkspaceFrame,
type PlatformRouteContext
} from "@govoplan/core-webui";
import {
@@ -144,13 +147,13 @@ export default function FormsPage({ settings, auth }: PlatformRouteContext) {
return (
<main className="forms-page">
<div className="forms-shell">
<ActionToolbar className="forms-toolbar">
<form onSubmit={search} className="forms-search">
<WorkspaceFrame className="forms-shell" label="Form definitions" interfaceId="forms.catalogue" helpContextId="forms.page.catalogue" helpModuleId="forms">
<ActionToolbar surface="panel-header" className="forms-toolbar">
<FilterBar as="form" surface="control" wrap="never" width="default" onSubmit={search} className="forms-search">
<Search size={17} aria-hidden="true" />
<input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Search definitions" aria-label="Search Form definitions" />
<Button type="submit">Search</Button>
</form>
</FilterBar>
<label>
<span>State</span>
<select value={state} onChange={(event) => setState(event.target.value)}>
@@ -171,7 +174,7 @@ export default function FormsPage({ settings, auth }: PlatformRouteContext) {
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
{!canWrite && <ActionBlockerHint tone="info" reason={{ summary: "No Form management permission", details: FORMS_I18N.writeReason, requiredAction: FORMS_I18N.permissionAction, actor: FORMS_I18N.permissionActor, target: FORMS_I18N.permissionDestination }} labels={{ requiredAction: FORMS_I18N.requiredAction, actor: FORMS_I18N.actor, target: FORMS_I18N.destination }} documentation={FORMS_DOCUMENTATION} />}
{loading && <LoadingIndicator label="Loading Form definitions" />}
{!loading && !error && items.length === 0 && <div className="forms-empty">No matching definitions.</div>}
{!loading && !error && items.length === 0 && <StatePanel size="compact" description="No matching definitions." />}
{!loading && items.length > 0 &&
<div className="forms-list" role="list">
{items.map((item) => {
@@ -192,7 +195,7 @@ export default function FormsPage({ settings, auth }: PlatformRouteContext) {
</div>
}
</PageScrollViewport>
</div>
</WorkspaceFrame>
{editing &&
<FormDefinitionDialog
open
+2 -55
View File
@@ -1,39 +1,14 @@
.forms-page,
.forms-shell {
.forms-page {
height: 100%;
min-height: 0;
overflow: hidden;
}
.forms-shell {
display: flex;
flex-direction: column;
background: var(--surface);
}
.forms-toolbar {
display: flex;
align-items: center;
gap: 10px;
min-height: 58px;
padding: 10px 18px;
border-bottom: 1px solid var(--border);
background: var(--surface-raised);
}
.forms-search {
display: flex;
align-items: center;
gap: 8px;
width: min(520px, 100%);
flex: 1 1 520px;
margin-right: auto;
}
.forms-search input {
min-width: 120px;
flex: 1;
}
.forms-toolbar > label {
display: flex;
align-items: center;
@@ -122,12 +97,6 @@
color: var(--text-soft);
}
.forms-empty {
padding: 36px 0;
color: var(--text-soft);
text-align: center;
}
.form-definition-dialog {
width: min(1120px, calc(100vw - 32px));
height: min(860px, calc(100vh - 32px));
@@ -171,19 +140,6 @@
font-weight: 600;
}
.form-field-editor-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.form-field-editor-heading h3 {
margin: 0;
font-size: 0.98rem;
letter-spacing: 0;
}
.form-field-editor-list {
border-top: 1px solid var(--border);
}
@@ -313,15 +269,6 @@
}
@media (max-width: 900px) {
.forms-toolbar {
align-items: stretch;
flex-wrap: wrap;
}
.forms-search {
width: 100%;
}
.forms-row {
grid-template-columns: minmax(0, 1fr) auto auto;
}