diff --git a/docs/components/character-count.md b/docs/components/character-count.md index bd97660b..377610ba 100644 --- a/docs/components/character-count.md +++ b/docs/components/character-count.md @@ -33,6 +33,7 @@ Check out the [max words validator](../validation/maxwords.md) for adding server | `form-group-*` | | Additional attributes to add to the generated form-group wrapper element. | | `id` | `string` | The `id` attribute for the generated `textarea` element. If not specified then a value is generated from the `name` attribute. | | `ignore-modelstate-errors` | `bool` | Whether ModelState errors on the ModelExpression specified by the `asp-for` attribute should be ignored when generating an error message. The default is `false`. | +| `label-class` | `string` | Additional classes for the generated `label` element. | | `max-length` | `int?` | The maximum number of characters the generated `textarea` may contain. Required unless the `max-words` attribute is specified. | | `max-words` | `int?` | The maximum number of words the generated `textarea` may contain. Required unless the `max-length` attribute is specified. | | `name` | `string` | The `name` attribute for the generated `textarea` element. Required unless the `asp-for` attribute is specified. | diff --git a/docs/components/file-upload.md b/docs/components/file-upload.md index 7bfc9d03..377b1923 100644 --- a/docs/components/file-upload.md +++ b/docs/components/file-upload.md @@ -35,6 +35,7 @@ | `id` | `string` | The `id` attribute for the generated `input` element. If not specified then a value is generated from the `name` attribute. | | `ignore-modelstate-errors` | `bool` | Whether ModelState errors on the ModelExpression specified by the `asp-for` attribute should be ignored when generating an error message. The default is `false`. | | `input-*` | | Additional attributes to add to the generated `input` element. | +| `label-class` | `string` | Additional classes for the generated `label` element. | | `name` | `string` | The `name` attribute for the generated `input` element. Required unless the `asp-for` attribute is specified. | ### `` diff --git a/docs/components/select.md b/docs/components/select.md index ab4d79c8..3b44ebf5 100644 --- a/docs/components/select.md +++ b/docs/components/select.md @@ -27,6 +27,7 @@ | `disabled` | `bool` | Whether the element should be disabled. The default is `false`. | | `id` | `string` | The `id` attribute for the generated `select` element. If not specified then a value is generated from the `name` attribute. | | `ignore-modelstate-errors` | `bool` | Whether ModelState errors on the ModelExpression specified by the `asp-for` attribute should be ignored when generating an error message. The default is `false`. | +| `label-class` | `string` | Additional classes for the generated `label` element. | | `name` | `string` | The `name` attribute for the generated `select` element. Required unless the `asp-for` attribute is specified. | | `select-*` | | Additional attributes to add to the generated `select` element. | diff --git a/docs/components/text-input.md b/docs/components/text-input.md index 100dd6b2..cac871c8 100644 --- a/docs/components/text-input.md +++ b/docs/components/text-input.md @@ -53,6 +53,7 @@ The content is the HTML to use within the generated component. | `id` | `string` | The `id` attribute for the generated `input` element. If not specified then a value is generated from the `name` attribute. | | `ignore-modelstate-errors` | `bool` | Whether ModelState errors on the ModelExpression specified by the `asp-for` attribute should be ignored when generating an error message. The default is `false`. | | `input-*` | | Additional attributes to add to the generated `input` element. | +| `label-class` | `string` | Additional classes for the generated `label` element. | | `name` | `string` | The `name` attribute for the generated `input` element. Required unless the `asp-for` attribute is specified. | | `inputmode` | `string` | The `inputmode` attribute for the generated `input` element. | | `pattern` | `string` | The `pattern` attribute for the generated `input` element. | diff --git a/docs/components/textarea.md b/docs/components/textarea.md index a6e21c67..18c1ad23 100644 --- a/docs/components/textarea.md +++ b/docs/components/textarea.md @@ -29,6 +29,7 @@ | `disabled` | `bool` | Whether the textarea should be disabled. The default is `false`. | | `id` | `string` | The `id` attribute for the generated `textarea` element. If not specified then a value is generated from the `name` attribute. | | `ignore-modelstate-errors` | `bool` | Whether ModelState errors on the ModelExpression specified by the `asp-for` attribute should be ignored when generating an error message. The default is `false`. | +| `label-class` | `string` | Additional classes for the generated `label` element. | | `name` | `string` | The `name` attribute for the generated `textarea` element. Required unless the `asp-for` attribute is specified. | | `rows` | `int` | The `rows` attribute for the generated `textarea` element. The default is `5`. | | `spellcheck` | `bool?` | The `spellcheck` attribute for the generated `textarea` element. The default is `null`. | diff --git a/src/GovUk.Frontend.AspNetCore/AttributeDictionaryExtensions.cs b/src/GovUk.Frontend.AspNetCore/AttributeDictionaryExtensions.cs index af90c72f..a090fdf1 100644 --- a/src/GovUk.Frontend.AspNetCore/AttributeDictionaryExtensions.cs +++ b/src/GovUk.Frontend.AspNetCore/AttributeDictionaryExtensions.cs @@ -16,7 +16,7 @@ public static class AttributeDictionaryExtensions /// /// The to add the CSS class name to. /// The CSS class name to add. - public static void MergeCssClass(this AttributeDictionary attributeDictionary, string value) + public static void MergeCssClass(this AttributeDictionary attributeDictionary, string? value) { Guard.ArgumentNotNull(nameof(attributeDictionary), attributeDictionary); diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/CharacterCountTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/CharacterCountTagHelper.cs index 6043d6bd..15ac1566 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/CharacterCountTagHelper.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/CharacterCountTagHelper.cs @@ -26,6 +26,7 @@ public class CharacterCountTagHelper : FormGroupTagHelperBase private const string DisabledAttributeName = "disabled"; private const string FormGroupAttributesPrefix = "form-group-"; private const string IdAttributeName = "id"; + private const string LabelClassAttributeName = "label-class"; private const string MaxLengthAttributeName = "max-length"; private const string MaxWordsLengthAttributeName = "max-words"; private const string NameAttributeName = "name"; @@ -86,6 +87,12 @@ internal CharacterCountTagHelper(IGovUkHtmlGenerator? htmlGenerator = null, IMod [HtmlAttributeName(IdAttributeName)] public string? Id { get; set; } + /// + /// Additional classes for the generated label element. + /// + [HtmlAttributeName(LabelClassAttributeName)] + public string? LabelClass { get; set; } + /// /// The maximum number of characters the generated textarea may contain. /// @@ -217,7 +224,7 @@ private protected override IHtmlContent GenerateFormGroupContent( { var contentBuilder = new HtmlContentBuilder(); - var label = GenerateLabel(formGroupContext); + var label = GenerateLabel(formGroupContext, LabelClass); contentBuilder.AppendHtml(label); var hint = GenerateHint(tagHelperContext, formGroupContext); diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/FileUploadTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/FileUploadTagHelper.cs index 8d6cf715..299ff9d9 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/FileUploadTagHelper.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/FileUploadTagHelper.cs @@ -20,6 +20,7 @@ public class FileUploadTagHelper : FormGroupTagHelperBase private const string AttributesPrefix = "input-"; private const string IdAttributeName = "id"; + private const string LabelClassAttributeName = "label-class"; private const string NameAttributeName = "name"; /// @@ -52,6 +53,12 @@ internal FileUploadTagHelper(IGovUkHtmlGenerator? htmlGenerator = null, IModelHe [HtmlAttributeName(DictionaryAttributePrefix = AttributesPrefix)] public IDictionary InputAttributes { get; set; } = new Dictionary(); + /// + /// Additional classes for the generated label element. + /// + [HtmlAttributeName(LabelClassAttributeName)] + public string? LabelClass { get; set; } + /// /// The name attribute for the generated input element. /// @@ -72,7 +79,7 @@ private protected override IHtmlContent GenerateFormGroupContent( { var contentBuilder = new HtmlContentBuilder(); - var label = GenerateLabel(formGroupContext); + var label = GenerateLabel(formGroupContext, LabelClass); contentBuilder.AppendHtml(label); var hint = GenerateHint(tagHelperContext, formGroupContext); diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupTagHelperBase.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupTagHelperBase.cs index 58907940..31034d3f 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupTagHelperBase.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupTagHelperBase.cs @@ -193,7 +193,7 @@ void AddErrorToFormErrorContext() } } - internal IHtmlContent GenerateLabel(FormGroupContext formGroupContext) + internal IHtmlContent GenerateLabel(FormGroupContext formGroupContext, string? labelClass) { // We need some content for the label; if AspFor is null then label content must have been specified if (AspFor == null && formGroupContext.Label?.Content == null) @@ -206,7 +206,9 @@ internal IHtmlContent GenerateLabel(FormGroupContext formGroupContext) var isPageHeading = formGroupContext.Label?.IsPageHeading ?? ComponentGenerator.LabelDefaultIsPageHeading; var content = formGroupContext.Label?.Content; - var attributes = formGroupContext.Label?.Attributes; + + var attributes = formGroupContext.Label?.Attributes?.ToAttributeDictionary() ?? new(); + attributes.MergeCssClass(labelClass); var resolvedContent = content ?? new HtmlString(HtmlEncoder.Default.Encode(ModelHelper.GetDisplayName(ViewContext!, AspFor!.ModelExplorer, AspFor.Name) ?? string.Empty)); diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/SelectTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/SelectTagHelper.cs index 95cbf530..909d0f1d 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/SelectTagHelper.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/SelectTagHelper.cs @@ -23,6 +23,7 @@ public class SelectTagHelper : FormGroupTagHelperBase private const string DescribedByAttributeName = "described-by"; private const string DisabledAttributeName = "disabled"; private const string IdAttributeName = "id"; + private const string LabelClassAttributeName = "label-class"; private const string NameAttributeName = "name"; /// @@ -65,6 +66,12 @@ internal SelectTagHelper(IGovUkHtmlGenerator? htmlGenerator = null, IModelHelper [HtmlAttributeName(IdAttributeName)] public string? Id { get; set; } + /// + /// Additional classes for the generated label element. + /// + [HtmlAttributeName(LabelClassAttributeName)] + public string? LabelClass { get; set; } + /// /// The name attribute for the generated select element. /// @@ -93,7 +100,7 @@ private protected override IHtmlContent GenerateFormGroupContent( var contentBuilder = new HtmlContentBuilder(); - var label = GenerateLabel(formGroupContext); + var label = GenerateLabel(formGroupContext, LabelClass); contentBuilder.AppendHtml(label); var hint = GenerateHint(tagHelperContext, formGroupContext); diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/TextAreaTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/TextAreaTagHelper.cs index 2f6a7612..e91a2576 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/TextAreaTagHelper.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/TextAreaTagHelper.cs @@ -23,6 +23,7 @@ public class TextAreaTagHelper : FormGroupTagHelperBase private const string AutocompleteAttributeName = "autocomplete"; private const string DisabledAttributeName = "disabled"; private const string IdAttributeName = "id"; + private const string LabelClassAttributeName = "label-class"; private const string NameAttributeName = "name"; private const string RowsAttributeName = "rows"; private const string SpellcheckAttributeName = "spellcheck"; @@ -64,6 +65,12 @@ internal TextAreaTagHelper(IGovUkHtmlGenerator? htmlGenerator = null, IModelHelp [HtmlAttributeName(IdAttributeName)] public string? Id { get; set; } + /// + /// Additional classes for the generated label element. + /// + [HtmlAttributeName(LabelClassAttributeName)] + public string? LabelClass { get; set; } + /// /// The name attribute for the generated textarea element. /// @@ -105,7 +112,7 @@ private protected override IHtmlContent GenerateFormGroupContent( { var contentBuilder = new HtmlContentBuilder(); - var label = GenerateLabel(formGroupContext); + var label = GenerateLabel(formGroupContext, LabelClass); contentBuilder.AppendHtml(label); var hint = GenerateHint(tagHelperContext, formGroupContext); diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/TextInputTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/TextInputTagHelper.cs index be6c86ee..91753894 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/TextInputTagHelper.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/TextInputTagHelper.cs @@ -26,6 +26,7 @@ public class TextInputTagHelper : FormGroupTagHelperBase private const string DisabledAttributeName = "disabled"; private const string IdAttributeName = "id"; private const string InputModeAttributeName = "inputmode"; + private const string LabelClassAttributeName = "label-class"; private const string NameAttributeName = "name"; private const string PatternAttributeName = "pattern"; private const string SpellcheckAttributeName = "spellcheck"; @@ -46,8 +47,8 @@ public TextInputTagHelper() internal TextInputTagHelper(IGovUkHtmlGenerator? htmlGenerator = null, IModelHelper? modelHelper = null) : base( - htmlGenerator ?? new ComponentGenerator(), - modelHelper ?? new DefaultModelHelper()) + htmlGenerator ?? new ComponentGenerator(), + modelHelper ?? new DefaultModelHelper()) { } @@ -88,6 +89,12 @@ internal TextInputTagHelper(IGovUkHtmlGenerator? htmlGenerator = null, IModelHel [HtmlAttributeName(DictionaryAttributePrefix = AttributesPrefix)] public IDictionary? InputAttributes { get; set; } = new Dictionary(); + /// + /// Additional classes for the generated label element. + /// + [HtmlAttributeName(LabelClassAttributeName)] + public string? LabelClass { get; set; } + /// /// The name attribute for the generated input element. /// @@ -160,7 +167,7 @@ private protected override IHtmlContent GenerateFormGroupContent( var contentBuilder = new HtmlContentBuilder(); - var label = GenerateLabel(formGroupContext); + var label = GenerateLabel(formGroupContext, LabelClass); contentBuilder.AppendHtml(label); var hint = GenerateHint(tagHelperContext, formGroupContext); diff --git a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/CharacterCountTagHelperTests.cs b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/CharacterCountTagHelperTests.cs index 0ebdc11f..6afd513c 100644 --- a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/CharacterCountTagHelperTests.cs +++ b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/CharacterCountTagHelperTests.cs @@ -51,7 +51,8 @@ public async Task ProcessAsync_WithMaxWords_GeneratesExpectedOutput() Id = "my-id", Name = "my-name", MaxWords = 10, - Threshold = 90 + Threshold = 90, + LabelClass = "additional-label-class" }; // Act @@ -61,7 +62,7 @@ public async Task ProcessAsync_WithMaxWords_GeneratesExpectedOutput() var expectedHtml = @"
- +
The hint
diff --git a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FileUploadTagHelperTests.cs b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FileUploadTagHelperTests.cs index a3b0ac91..c6ba9471 100644 --- a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FileUploadTagHelperTests.cs +++ b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FileUploadTagHelperTests.cs @@ -45,7 +45,8 @@ public async Task ProcessAsync_GeneratesExpectedOutput() { Id = "my-id", DescribedBy = "describedby", - Name = "my-id" + Name = "my-id", + LabelClass = "additional-label-class" }; // Act @@ -54,7 +55,7 @@ public async Task ProcessAsync_GeneratesExpectedOutput() // Assert var expectedHtml = @"
- +
The hint
"; diff --git a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FormGroupTagHelperBaseTests.cs b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FormGroupTagHelperBaseTests.cs index 66e63dd7..527fc4fa 100644 --- a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FormGroupTagHelperBaseTests.cs +++ b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/FormGroupTagHelperBaseTests.cs @@ -511,7 +511,7 @@ public void GenerateLabel_NoLabelContentOnContextOrAspFor_ThrowsInvalidOperation }; // Act - var ex = Record.Exception(() => tagHelper.GenerateLabel(formGroupContext)); + var ex = Record.Exception(() => tagHelper.GenerateLabel(formGroupContext, labelClass: null)); // Assert Assert.IsType(ex); @@ -533,7 +533,7 @@ public void GenerateLabel_LabelContentOnContext_ReturnsContentFromContext() }; // Act - var label = tagHelper.GenerateLabel(formGroupContext); + var label = tagHelper.GenerateLabel(formGroupContext, labelClass: null); // Assert Assert.NotNull(label); @@ -562,7 +562,7 @@ public void GenerateLabel_NoLabelContentOnContext_ReturnsContentFromAspFor() }; // Act - var label = tagHelper.GenerateLabel(formGroupContext); + var label = tagHelper.GenerateLabel(formGroupContext, labelClass: null); // Assert Assert.NotNull(label); @@ -592,7 +592,7 @@ public void GenerateLabel_LabelContentOnContextAndAspFor_ReturnsContentFromConte }; // Act - var label = tagHelper.GenerateLabel(formGroupContext); + var label = tagHelper.GenerateLabel(formGroupContext, labelClass: null); // Assert Assert.NotNull(label); @@ -751,7 +751,7 @@ private protected override IHtmlContent GenerateFormGroupContent( { var contentBuilder = new HtmlContentBuilder(); - var label = GenerateLabel(formGroupContext); + var label = GenerateLabel(formGroupContext, labelClass: null); contentBuilder.AppendHtml(label); var hint = GenerateHint(tagHelperContext, formGroupContext); diff --git a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/SelectTagHelperTests.cs b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/SelectTagHelperTests.cs index 95e14804..3389584c 100644 --- a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/SelectTagHelperTests.cs +++ b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/SelectTagHelperTests.cs @@ -70,7 +70,8 @@ public async Task ProcessAsync_GeneratesExpectedOutput() { Id = "my-id", DescribedBy = "describedby", - Name = "my-name" + Name = "my-name", + LabelClass = "additional-label-class" }; // Act @@ -79,7 +80,7 @@ public async Task ProcessAsync_GeneratesExpectedOutput() // Assert var expectedHtml = @"
- +
The hint
"; diff --git a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/TextInputTagHelperTests.cs b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/TextInputTagHelperTests.cs index 6c49510b..49de0efc 100644 --- a/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/TextInputTagHelperTests.cs +++ b/test/GovUk.Frontend.AspNetCore.Tests/TagHelpers/TextInputTagHelperTests.cs @@ -50,7 +50,8 @@ public async Task ProcessAsync_GeneratesExpectedOutput() InputMode = "numeric", Pattern = "[0-9]*", Type = "number", - Value = "42" + Value = "42", + LabelClass = "additional-label-class" }; // Act @@ -59,7 +60,7 @@ public async Task ProcessAsync_GeneratesExpectedOutput() // Assert var expectedHtml = @"
- +
The hint