Release govoplan-templates v0.1.22: bound rendering and improve dialog layout

This commit is contained in:
2026-09-08 01:32:53 +02:00
parent 69416faca9
commit 19cbdd303b
9 changed files with 172 additions and 42 deletions
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@govoplan/templates-webui",
"version": "0.1.21",
"version": "0.1.22",
"private": true,
"type": "module",
"main": "src/index.ts",
@@ -14,10 +14,11 @@
"./styles/templates.css": "./src/styles/templates.css"
},
"scripts": {
"test:dialog-layout": "node scripts/test-dialog-layout.mjs",
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"@govoplan/core-webui": "^0.1.18",
"@govoplan/core-webui": "^0.1.45",
"lucide-react": "^1.23.0",
"react": ">=19.2.7 <20",
"react-dom": ">=19.2.7 <20",
+9
View File
@@ -0,0 +1,9 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const page = readFileSync(new URL("../src/features/templates/TemplatesPage.tsx", import.meta.url), "utf8");
const styles = readFileSync(new URL("../src/styles/templates.css", import.meta.url), "utf8");
assert.match(page, /<Dialog open=\{createOpen\}[^]*?<FormGrid columns=\{2\} collapseAt="standard">/);
assert.doesNotMatch(page, /templates-dialog-form/);
assert.doesNotMatch(styles, /templates-dialog-form/);
console.log("Templates dialog uses the shared shrinking form layout.");
+15 -12
View File
@@ -9,13 +9,14 @@ import {
X
} from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { DialogSection, ActionToolbar,
import { FormGrid, ActionToolbar,
ApiError,
ActionBlockerHint,
Button,
ConfirmDialog,
ContentSection,
Dialog,
DialogSection,
DocumentationHelpLink,
DismissibleAlert,
FilterBar,
@@ -302,6 +303,14 @@ export default function TemplatesPage({ settings, auth }: Props) {
return (
<WorkspaceFrame as="main" height="viewport" surface="plain" className="templates-page" label="Template workspace">
<WorkspaceActionBar
scope="workspace"
variant="collection"
refreshable
reloadAction={{ onReload: () => void reload(selectedId), loading: loading || busy }}
contextActions={<strong>Template library</strong>}
createAction={<IconButton label="Add template" icon={<Plus size={17} />} variant="primary" disabled={!canWrite} disabledReason={!canWrite ? TEMPLATES_I18N.writeReason : undefined} onClick={() => requestDiscard(() => setCreateOpen(true))} />}
/>
<WorkspaceLayout
variant="split"
primarySize="compact"
@@ -312,14 +321,6 @@ export default function TemplatesPage({ settings, auth }: Props) {
contentLabel="Template workspace"
contentClassName="templates-workspace"
primary={<>
<WorkspaceActionBar
scope="collection-pane"
variant="collection"
refreshable
reloadAction={{ onReload: () => void reload(selectedId), loading: loading || busy }}
contextActions={<strong>Template library</strong>}
createAction={<IconButton label="Add template" icon={<Plus size={17} />} variant="primary" disabled={!canWrite} disabledReason={!canWrite ? TEMPLATES_I18N.writeReason : undefined} onClick={() => requestDiscard(() => setCreateOpen(true))} />}
/>
<FilterBar surface="panel"><input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="Search templates" /></FilterBar>
<SelectionList variant="navigation" label="Templates">
{visibleItems.map((item) => (
@@ -418,9 +419,11 @@ export default function TemplatesPage({ settings, auth }: Props) {
</WorkspaceLayout>
<Dialog open={createOpen} title="Add template" onClose={closeCreate} closeDisabled={busy} footer={<><Button onClick={closeCreate} disabled={busy} disabledReason={busy ? TEMPLATES_I18N.busy : undefined}>Cancel</Button><Button variant="primary" disabled={!createName.trim() || busy} disabledReason={busy ? TEMPLATES_I18N.busy : !createName.trim() ? TEMPLATES_I18N.incomplete : undefined} onClick={() => void create()}>Create</Button></>}>
<DialogSection className="templates-dialog-form">
<FormField label="Name" documentation={TEMPLATE_FIELDS_DOCUMENTATION}><input autoFocus value={createName} onChange={(event) => setCreateName(event.target.value)} /></FormField>
<FormField label="Type" documentation={TEMPLATE_FIELDS_DOCUMENTATION}><select value={createType} onChange={(event) => setCreateType(event.target.value as TemplateType)}>{TEMPLATE_TYPES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</select></FormField>
<DialogSection>
<FormGrid columns={2} collapseAt="standard">
<FormField label="Name" documentation={TEMPLATE_FIELDS_DOCUMENTATION}><input autoFocus value={createName} onChange={(event) => setCreateName(event.target.value)} /></FormField>
<FormField label="Type" documentation={TEMPLATE_FIELDS_DOCUMENTATION}><select value={createType} onChange={(event) => setCreateType(event.target.value as TemplateType)}>{TEMPLATE_TYPES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</select></FormField>
</FormGrid>
</DialogSection>
</Dialog>
<ConfirmDialog open={publishOpen} title="i18n:govoplan-templates.publish_title" message="i18n:govoplan-templates.publish_message" confirmLabel="Publish" busy={busy} onCancel={() => setPublishOpen(false)} onConfirm={() => void publish()} />
+2 -3
View File
@@ -12,7 +12,7 @@
.templates-content { height: 100%; min-width: 0; min-height: 0; overflow: auto; padding: 14px; }
.templates-definition-fields { display: grid; grid-template-columns: minmax(220px, 1.4fr) repeat(3, minmax(130px, .7fr)); gap: 12px; margin-bottom: 14px; }
.templates-definition-fields .form-field:nth-child(5) { grid-column: span 2; }
.templates-definition-fields input, .templates-definition-fields select, .templates-dialog-form input, .templates-dialog-form select, .templates-layout-fields input, .templates-layout-fields select, .templates-preview-controls select { width: 100%; }
.templates-definition-fields input, .templates-definition-fields select, .templates-layout-fields input, .templates-layout-fields select, .templates-preview-controls select { width: 100%; }
.templates-section-heading small { color: var(--muted); font-weight: 400; }
.templates-section-heading .btn { display: inline-flex; align-items: center; gap: 6px; }
@@ -39,7 +39,6 @@
.templates-history-list small { margin-top: 3px; color: var(--muted); font-size: 11px; }
.templates-history-list .btn { display: inline-flex; align-items: center; gap: 6px; }
.templates-history-badges { display: flex; align-items: center; gap: 6px; }
.templates-dialog-form { display: grid; grid-template-columns: minmax(220px, 1fr) minmax(180px, .7fr); gap: 12px; min-width: min(560px, 80vw); }
@media (max-width: 1100px) {
.templates-definition-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@@ -54,5 +53,5 @@
.templates-workspace-toolbar { align-items: flex-start; flex-wrap: wrap; }
.templates-toolbar-actions { flex-wrap: wrap; }
.templates-content { height: auto; overflow: visible; }
.templates-definition-fields, .templates-dialog-form, .templates-render-result dl { grid-template-columns: 1fr; }
.templates-definition-fields, .templates-render-result dl { grid-template-columns: 1fr; }
}