Adopt shared WebUI layout primitives
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { ArrowDown, ArrowUp, Eye, Languages, Plus, Trash2 } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
import { FormGrid,
|
||||
Button,
|
||||
ConfirmDialog,
|
||||
Dialog,
|
||||
@@ -198,7 +198,7 @@ export default function FormDefinitionDialog({
|
||||
<div className="form-definition-editor">
|
||||
<div className="form-definition-help"><DocumentationHelpLink reference={FORMS_FIELD_DOCUMENTATION} /></div>
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
<div className="form-definition-grid">
|
||||
<FormGrid columns={2} gap="small" collapseAt="narrow">
|
||||
<Field label="Title">
|
||||
<input value={draft.title} disabled={busy} onChange={(event) => setDraft({ ...draft, title: event.target.value })} />
|
||||
</Field>
|
||||
@@ -260,7 +260,7 @@ export default function FormDefinitionDialog({
|
||||
placeholder="Optional instructions announced before the Form"
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</FormGrid>
|
||||
|
||||
<div className="form-field-editor-heading">
|
||||
<h3>Fields</h3>
|
||||
@@ -333,19 +333,19 @@ export default function FormDefinitionDialog({
|
||||
function ConstraintFields({ field, disabled, onChange }: { field: FormFieldDefinition; disabled: boolean; onChange: (value: Record<string, unknown>) => void }) {
|
||||
if (["text", "multiline_text", "email"].includes(field.value_type)) {
|
||||
return (
|
||||
<div className="form-field-constraints">
|
||||
<FormGrid columns={3} gap="compact" collapseAt="narrow" className="form-field-constraints">
|
||||
<Field label="Minimum length"><input type="number" min={0} value={constraintValue(field.constraints.min_length)} disabled={disabled} onChange={(event) => onChange(patchConstraint(field.constraints, "min_length", event.target.value))} /></Field>
|
||||
<Field label="Maximum length"><input type="number" min={0} value={constraintValue(field.constraints.max_length)} disabled={disabled} onChange={(event) => onChange(patchConstraint(field.constraints, "max_length", event.target.value))} /></Field>
|
||||
<Field label="Pattern"><input value={String(field.constraints.pattern ?? "")} disabled={disabled} onChange={(event) => onChange(patchTextConstraint(field.constraints, "pattern", event.target.value))} /></Field>
|
||||
</div>
|
||||
</FormGrid>
|
||||
);
|
||||
}
|
||||
if (["integer", "number"].includes(field.value_type)) {
|
||||
return (
|
||||
<div className="form-field-constraints">
|
||||
<FormGrid columns={3} gap="compact" collapseAt="narrow" className="form-field-constraints">
|
||||
<Field label="Minimum"><input type="number" value={constraintValue(field.constraints.minimum)} disabled={disabled} onChange={(event) => onChange(patchConstraint(field.constraints, "minimum", event.target.value))} /></Field>
|
||||
<Field label="Maximum"><input type="number" value={constraintValue(field.constraints.maximum)} disabled={disabled} onChange={(event) => onChange(patchConstraint(field.constraints, "maximum", event.target.value))} /></Field>
|
||||
</div>
|
||||
</FormGrid>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -367,7 +367,7 @@ function ConditionFields({
|
||||
const predicate = condition?.kind === "predicate" ? condition : null;
|
||||
const candidates = fields.filter((item) => item.key !== currentKey && item.key.trim());
|
||||
return (
|
||||
<div className="form-field-condition">
|
||||
<FormGrid columns={3} gap="compact" collapseAt="narrow" className="form-field-condition">
|
||||
<Field label="Visible when">
|
||||
<select
|
||||
value={predicate?.field_key ?? ""}
|
||||
@@ -406,7 +406,7 @@ function ConditionFields({
|
||||
</Field>
|
||||
}
|
||||
</>}
|
||||
</div>
|
||||
</FormGrid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ function DefinitionPreview({ definition, previous }: { definition: FormDefinitio
|
||||
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>
|
||||
<div className="form-preview-grid">
|
||||
<FormGrid columns={2} collapseAt="narrow" className="form-preview-grid">
|
||||
<div>
|
||||
<strong>{definition.title || "Untitled Form"}</strong>
|
||||
{(definition.pages?.length ? definition.pages : [defaultPage(definition.fields)]).map((page) =>
|
||||
@@ -517,7 +517,7 @@ function DefinitionPreview({ definition, previous }: { definition: FormDefinitio
|
||||
)}
|
||||
</div>
|
||||
<div><strong>Changes</strong><ul>{changed.map((item) => <li key={item}>{item}</li>)}</ul></div>
|
||||
</div>
|
||||
</FormGrid>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Download, Pencil, Plus, RefreshCw, Search, Upload } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState, type FormEvent } from "react";
|
||||
import {
|
||||
import { ActionToolbar,
|
||||
ActionBlockerHint,
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -145,7 +145,7 @@ export default function FormsPage({ settings, auth }: PlatformRouteContext) {
|
||||
return (
|
||||
<main className="forms-page">
|
||||
<div className="forms-shell">
|
||||
<div className="forms-toolbar">
|
||||
<ActionToolbar className="forms-toolbar">
|
||||
<form 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" />
|
||||
@@ -166,7 +166,7 @@ export default function FormsPage({ settings, auth }: PlatformRouteContext) {
|
||||
<Button variant="primary" disabled={!canWrite} disabledReason={!canWrite ? FORMS_I18N.writeReason : undefined} onClick={() => setEditing("new")}><Plus size={16} aria-hidden="true" />New definition</Button>
|
||||
<span className="forms-count">{total}</span>
|
||||
<DocumentationHelpLink reference={FORMS_DOCUMENTATION} />
|
||||
</div>
|
||||
</ActionToolbar>
|
||||
<PageScrollViewport className="forms-list-viewport">
|
||||
{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} />}
|
||||
|
||||
@@ -145,12 +145,6 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.form-definition-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.form-definition-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
@@ -229,13 +223,6 @@
|
||||
grid-column: 2 / -1;
|
||||
}
|
||||
|
||||
.form-field-constraints,
|
||||
.form-field-condition {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 9px;
|
||||
}
|
||||
|
||||
.form-composition-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -302,9 +289,7 @@
|
||||
}
|
||||
|
||||
.form-preview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-preview-grid > div {
|
||||
@@ -374,14 +359,10 @@
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.form-definition-grid,
|
||||
.form-field-constraints,
|
||||
.form-field-condition,
|
||||
.form-page-editor-heading,
|
||||
.form-section-editor,
|
||||
.form-localization-editor,
|
||||
.form-localization-fields,
|
||||
.form-preview-grid {
|
||||
.form-localization-fields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user