From eeddfc5673077f3e2f0a5dcea7e47186ca75bc5b Mon Sep 17 00:00:00 2001 From: Roland Banks Date: Thu, 12 Nov 2026 12:44:32 +0700 Subject: [PATCH] Form validation and helper colours from CSS variables PDForm's label, validation indicator, helper icons, field error message and footer error count read --pd-form-* variables, each defaulting to the colour drawn before. A label can show a coloured edge, and carries pd-form-title-required or pd-form-title-invalid to tell a field still waiting for a value from a wrong one; the indicator carries pd-form-status. The Bootstrap text-info and text-danger classes, which no theme could override, are gone from PDFormBody, PDFormHeader and PDFormFooter. Under Bootstrap 5.3's dark mode the editor frame takes Bootstrap's dark border colour. The PDForm 6 demo page switches groups of the variables on and off, and the documentation lists them. Co-Authored-By: Claude Opus 5.5 --- .../Pages/PDFormDocumentation.razor | 21 ++++- .../Pages/PDFormPage6.razor | 91 ++++++++++++++++++ .../Pages/PDFormPage6.razor.cs | 93 +++++++++++++++++++ .../Pages/PDFormPage6.razor.css | 35 +++++++ .../Shared/NavMenu.razor | 5 + .../Components/PDFormBodyTests.Colours.cs | 73 +++++++++++++++ .../Components/PDFormBodyTests.cs | 2 +- .../Components/PDFormFooterTests.Rendering.cs | 1 + .../Components/PDFormHeaderTests.cs | 1 + .../ThemeVariableStylesheetTests.cs | 15 ++- PanoramicData.Blazor/PDFormBody.razor | 12 +-- PanoramicData.Blazor/PDFormBody.razor.cs | 13 +++ PanoramicData.Blazor/PDFormBody.razor.css | 47 +++++++--- .../PDFormFieldEditor.razor.css | 5 + PanoramicData.Blazor/PDFormFooter.razor | 2 +- PanoramicData.Blazor/PDFormFooter.razor.css | 15 ++- PanoramicData.Blazor/PDFormHeader.razor | 4 +- PanoramicData.Blazor/PDFormHeader.razor.css | 5 + .../PanoramicData.Blazor.csproj | 2 +- 19 files changed, 411 insertions(+), 31 deletions(-) create mode 100644 PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor create mode 100644 PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.cs create mode 100644 PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.css create mode 100644 PanoramicData.Blazor.Test/Components/PDFormBodyTests.Colours.cs diff --git a/PanoramicData.Blazor.Demo/Pages/PDFormDocumentation.razor b/PanoramicData.Blazor.Demo/Pages/PDFormDocumentation.razor index 17273d56c..9827bde2f 100644 --- a/PanoramicData.Blazor.Demo/Pages/PDFormDocumentation.razor +++ b/PanoramicData.Blazor.Demo/Pages/PDFormDocumentation.razor @@ -214,13 +214,28 @@

Colours

- The frame around each field's editor comes from a CSS variable, so a host can match it to its own control - borders on :root or on a theme class, for example on a dark theme. + The form's frames, validation state and helper icons take their colours from CSS variables, so a host can + match them to its own theme on :root or on a theme class. Each default is the colour the form + drew before. PDForm 6 in the demo turns groups of them on and off.

- + + + + + + + + + + + + + + +
VariableDefaultDescription
--pd-form-editor-border-color#ced4daThe border around a field's editor, to the right of its label
--pd-form-editor-border-color#ced4da, and Bootstrap's border colour under its dark modeThe border around a field's editor, to the right of its label
--pd-form-title-error-bg, --pd-form-title-error-colorBootstrap's danger alert coloursThe label of a field with errors, whether still waiting for a value or holding a wrong one
--pd-form-title-edge-width0The width of an edge down the left of such a label; none unless set
--pd-form-title-required-edge-colortransparentThe edge while a field is still waiting for a value (label class pd-form-title-required)
--pd-form-title-invalid-edge-colortransparentThe edge once a field holds a wrong value (label class pd-form-title-invalid)
--pd-form-status-valid-bg, --pd-form-status-valid-colorBootstrap's success alert coloursThe validation indicator at the end of a valid field (class pd-form-status)
--pd-form-status-required-bg, --pd-form-status-required-colorBootstrap's warning alert coloursThe indicator, an asterisk, while a field is still waiting for a value
--pd-form-status-invalid-bg, --pd-form-status-invalid-colorBootstrap's danger alert coloursThe indicator once a field holds a wrong value
--pd-form-helper-colorBootstrap's info colourThe help link, helper and copy icons beside a label, and the header's help icon
--pd-form-error-colorBootstrap's danger colourThe message under a field with a wrong value
--pd-form-footer-error-color, --pd-form-footer-error-icon-colorBootstrap's danger colourThe footer's error count and its icon
--pd-form-footer-error-bgtransparentBehind the footer's error count
--pd-form-footer-error-border-width, --pd-form-footer-error-border-color0 0 1px 0, the text colourIts border, a line underneath by default
--pd-form-footer-error-edge-colorthe border colourIts left border, for a coloured edge
--pd-form-footer-error-padding, --pd-form-footer-error-radius0.25rem 0, 0Its padding and corners
diff --git a/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor b/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor new file mode 100644 index 000000000..dc003bbc3 --- /dev/null +++ b/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor @@ -0,0 +1,91 @@ +@page "/pdform6" + +

PDForm

+ + + +

+ The form's validation and helper colours come from --pd-form-* CSS variables. Each switch below sets + one group of them on the box around the form. With every switch off the form uses Bootstrap's alert and info + colours, as it always has. +

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
diff --git a/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.cs b/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.cs new file mode 100644 index 000000000..e5873f63b --- /dev/null +++ b/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.cs @@ -0,0 +1,93 @@ +using PanoramicData.Blazor.Services; +using System.ComponentModel.DataAnnotations; + +namespace PanoramicData.Blazor.Demo.Pages; + +public partial class PDFormPage6 +{ + private PDForm? _form; + private bool _dark; + private bool _neutralLabels; + private bool _labelEdges; + private bool _quietStatus; + private bool _mutedHelpers; + private bool _footerBox; + + private readonly DelegatedDataProviderService _dataProvider = new() + { + CreateAsync = (_, _) => Task.FromResult(new OperationResponse { Success = true }) + }; + + private readonly FormFieldHelper _folderHelper = new() + { + IconCssClass = "fas fa-fw fa-folder-open", + ToolTip = "Choose a folder", + Click = _ => new FormFieldResult { NewValue = "/Reports" } + }; + + private string WrapperCssClass => string.Join(' ', new[] + { + _neutralLabels ? "neutral-labels" : null, + _labelEdges ? "label-edges" : null, + _quietStatus ? "quiet-status" : null, + _mutedHelpers ? "muted-helpers" : null, + _footerBox ? "footer-box" : null + }.Where(x => x is not null)); + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await ResetFormAsync().ConfigureAwait(true); + } + } + + private async Task ResetFormAsync() + { + if (_form is not null) + { + await _form.EditItemAsync(new FormColoursModel(), FormModes.Create).ConfigureAwait(true); + } + } + + private void AllOn() => SetAll(true); + + private void AllOff() => SetAll(false); + + private void SetAll(bool value) + { + _neutralLabels = value; + _labelEdges = value; + _quietStatus = value; + _mutedHelpers = value; + _footerBox = value; + } + + /// + /// A schedule-like item with required fields, a valid one and an invalid one, to show every validation state. + /// + public class FormColoursModel + { + /// Gets or sets the name, which is required. + [Required] + public string Name { get; set; } = string.Empty; + + /// Gets or sets the description, which starts valid. + public string Description { get; set; } = "Weekly summary"; + + /// Gets or sets the input folder, which is required. + [Required] + public string InputFolder { get; set; } = string.Empty; + + /// Gets or sets the output folder, which is required. + [Required] + public string OutputFolder { get; set; } = string.Empty; + + /// Gets or sets the address to notify, which starts invalid. + [EmailAddress] + public string NotifyEmail { get; set; } = "not-an-address"; + + /// Gets or sets free-text notes. + public string Notes { get; set; } = "Copy me"; + } +} diff --git a/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.css b/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.css new file mode 100644 index 000000000..f7f259af3 --- /dev/null +++ b/PanoramicData.Blazor.Demo/Pages/PDFormPage6.razor.css @@ -0,0 +1,35 @@ +/* Each switch on the page sets one group of the form's colour variables; they inherit into the form */ +.form-colours-demo.neutral-labels { + --pd-form-title-error-bg: var(--bs-tertiary-bg); + --pd-form-title-error-color: var(--bs-body-color); +} + +.form-colours-demo.label-edges { + --pd-form-title-edge-width: 4px; + --pd-form-title-required-edge-color: var(--bs-warning); + --pd-form-title-invalid-edge-color: var(--bs-danger); +} + +.form-colours-demo.quiet-status { + --pd-form-status-valid-bg: var(--bs-tertiary-bg); + --pd-form-status-valid-color: var(--bs-secondary-color); + --pd-form-status-required-bg: var(--bs-tertiary-bg); + --pd-form-status-required-color: var(--bs-warning-text-emphasis); + --pd-form-status-invalid-bg: var(--bs-tertiary-bg); + --pd-form-status-invalid-color: var(--bs-danger-text-emphasis); +} + +.form-colours-demo.muted-helpers { + --pd-form-helper-color: var(--bs-secondary-color); +} + +.form-colours-demo.footer-box { + --pd-form-footer-error-color: var(--bs-body-color); + --pd-form-footer-error-icon-color: var(--bs-danger); + --pd-form-footer-error-bg: var(--bs-body-bg); + --pd-form-footer-error-border-width: 1px 1px 1px 4px; + --pd-form-footer-error-border-color: var(--bs-border-color); + --pd-form-footer-error-edge-color: var(--bs-danger); + --pd-form-footer-error-padding: 0.375rem 0.75rem; + --pd-form-footer-error-radius: 0.375rem; +} diff --git a/PanoramicData.Blazor.Demo/Shared/NavMenu.razor b/PanoramicData.Blazor.Demo/Shared/NavMenu.razor index f075cfcab..ab2a2991a 100644 --- a/PanoramicData.Blazor.Demo/Shared/NavMenu.razor +++ b/PanoramicData.Blazor.Demo/Shared/NavMenu.razor @@ -157,6 +157,11 @@ PDForm 5 +