From 86be95c41fda4d35b0f51211946461edd68ffe6e Mon Sep 17 00:00:00 2001 From: Brian Surowiec Date: Tue, 9 Apr 2024 01:04:59 -0400 Subject: [PATCH] Add new merge default classes tag helper --- CHANGELOG.md | 1 + README.md | 34 ++++- src/MergeClassesTagHelper.cs | 54 ++++++++ test/LinkAriaCurrentStateTagHelperTests.cs | 24 ++-- test/MergeDefaultClassTagHelperTests.cs | 149 +++++++++++++++++++++ test/ValidationStatusTagHelperTests.cs | 6 +- 6 files changed, 247 insertions(+), 21 deletions(-) create mode 100644 src/MergeClassesTagHelper.cs create mode 100644 test/MergeDefaultClassTagHelperTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a8374..dc873b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Dropped support for .NET 6 - Dropped support for .NET 7 - Added support for .NET 8 +- Added `MergeDefaultClassTagHelper` which will merge the `default-class` attribute with the `class` attribute ## [0.5.0](https://github.com/xt0rted/tailwindcss-tag-helpers/compare/v0.4.0...v0.5.0) - 2023-02-24 diff --git a/README.md b/README.md index fe33d8b..dd23bf3 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,8 @@ The naming of these attributes aligns with the comments found in the Tailwind UI The matching method can be either `Full` (default) which ensures the link path and current path are the same, or `Base` which ensures the link path starts with, or is the same as, the current path. -> **Note**: Query string values are not used for either method of matching. +> [!NOTE] +> Query string values are not used for either method of matching. ```html + +``` + +This tag helper target element is `*` so it can be used with any element, including web components and other tag helpers. + +```html + +``` + +#### Attributes + +Name | Value | Description +:-- | :-- | :-- +`default-class` | `string` | The classes to merge into the class attribute. + ### ValidationStatusTagHelper The validation status tag helper will check the validation status of the model value passed to the `asp-for` attribute and apply one of two sets of css classes. diff --git a/src/MergeClassesTagHelper.cs b/src/MergeClassesTagHelper.cs new file mode 100644 index 0000000..41933d6 --- /dev/null +++ b/src/MergeClassesTagHelper.cs @@ -0,0 +1,54 @@ +namespace Tailwind.Css.TagHelpers; + +using System.Text.Encodings.Web; + +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Options; + +[HtmlTargetElement("*", Attributes = ForAttributeName)] +public class MergeDefaultClassTagHelper : TagHelper +{ + protected const string ForAttributeName = "merge-classes"; + protected const string DefaultClassAttributeName = "default-class"; + + private readonly TagOptions _settings; + + public MergeDefaultClassTagHelper(IOptions settings) + => _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); + + /// + /// The classes to merge into the main class list. + /// + [HtmlAttributeName(DefaultClassAttributeName)] + public string? DefaultClass { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + ArgumentNullException.ThrowIfNull(output); + + var classList = Utilities.SplitClassList(DefaultClass); + + if (_settings.IncludeComments) + { + output.PreElement.AppendHtmlLine(""); + } + + if (classList?.Length > 0) + { + foreach (var className in classList) + { + output.AddClass(className, HtmlEncoder.Default); + } + } + } +} diff --git a/test/LinkAriaCurrentStateTagHelperTests.cs b/test/LinkAriaCurrentStateTagHelperTests.cs index a50fc42..e00d807 100644 --- a/test/LinkAriaCurrentStateTagHelperTests.cs +++ b/test/LinkAriaCurrentStateTagHelperTests.cs @@ -11,11 +11,9 @@ public void Should_not_throw_when_no_link_context_set() { // Given var context = MakeTagHelperContext( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); var output = MakeTagHelperOutput( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); var logger = A.Dummy>(); var options = Options.Create(new TagOptions()); @@ -40,11 +38,9 @@ public void Should_set_attribute_when_enabled(LinkAriaCurrentState state, string { // Given var context = MakeTagHelperContext( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); var output = MakeTagHelperOutput( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); AddLinkContext(context, isMatch: true); @@ -74,11 +70,9 @@ public void Should_not_emit_comments_when_setting_is_disabled() { // Given var context = MakeTagHelperContext( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); var output = MakeTagHelperOutput( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); var logger = A.Dummy>(); var options = Options.Create( @@ -100,11 +94,9 @@ public void Should_emit_comments_when_setting_is_enabled() { // Given var context = MakeTagHelperContext( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); var output = MakeTagHelperOutput( - tagName: "a", - new TagHelperAttributeList()); + tagName: "a"); var logger = A.Dummy>(); var options = Options.Create( diff --git a/test/MergeDefaultClassTagHelperTests.cs b/test/MergeDefaultClassTagHelperTests.cs new file mode 100644 index 0000000..5e29574 --- /dev/null +++ b/test/MergeDefaultClassTagHelperTests.cs @@ -0,0 +1,149 @@ +namespace Tailwind.Css.TagHelpers; + +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Options; + +public class MergeDefaultClassTagHelperTests : TagHelperTestBase +{ + [Fact] + public void Should_set_to_defaults_when_class_is_empty() + { + // Given + var context = MakeTagHelperContext( + tagName: "div"); + var output = MakeTagHelperOutput( + tagName: "div"); + + var options = Options.Create(new TagOptions()); + var helper = new MergeDefaultClassTagHelper(options) + { + DefaultClass = "bg-white text-black", + }; + + // When + helper.Process(context, output); + + // Then + output.Attributes["class"].ShouldNotBeNull(); + + var classList = output.Attributes["class"].Value as HtmlString; + classList.ShouldNotBeNull(); + classList.Value.ShouldBe("bg-white text-black"); + } + + [Fact] + public void Should_merge_defaults_into_existing_list() + { + // Given + var context = MakeTagHelperContext( + tagName: "div", + new TagHelperAttributeList + { + { "class", "flex flex-col" }, + }); + var output = MakeTagHelperOutput( + tagName: "div", + new TagHelperAttributeList + { + { "class", "flex flex-col" }, + }); + + var options = Options.Create(new TagOptions()); + var helper = new MergeDefaultClassTagHelper(options) + { + DefaultClass = "bg-white text-black", + }; + + // When + helper.Process(context, output); + + // Then + output.Attributes["class"].ShouldNotBeNull(); + + var classList = output.Attributes["class"].Value as HtmlString; + classList.ShouldNotBeNull(); + classList.Value.ShouldBe("flex flex-col bg-white text-black"); + } + + [Theory] + [InlineData(null)] + [InlineData("flex flex-col")] + public void Should_not_emit_comments_when_setting_is_disabled(string? defaultClasses) + { + // Given + var attributeList = new TagHelperAttributeList(); + + if (defaultClasses is not null) + { + attributeList.Add("class", defaultClasses); + } + + var context = MakeTagHelperContext( + tagName: "div", + attributeList); + var output = MakeTagHelperOutput( + tagName: "div", + attributeList); + + var options = Options.Create( + new TagOptions + { + IncludeComments = false, + }); + var helper = new MergeDefaultClassTagHelper(options) + { + DefaultClass = "bg-white underline", + }; + + // When + helper.Process(context, output); + + // Then + output.PreElement.GetContent().ShouldBeEmpty(); + } + + [Theory] + [InlineData(null)] + [InlineData("flex flex-col")] + public void Should_emit_comments_when_setting_is_enabled(string? defaultClasses) + { + // Given + var attributeList = new TagHelperAttributeList(); + + if (defaultClasses is not null) + { + attributeList.Add("class", defaultClasses); + } + + var context = MakeTagHelperContext( + tagName: "div", + attributeList); + var output = MakeTagHelperOutput( + tagName: "div", + attributeList); + + var options = Options.Create( + new TagOptions + { + IncludeComments = true, + }); + var helper = new MergeDefaultClassTagHelper(options) + { + DefaultClass = "bg-white text-black", + }; + + // When + helper.Process(context, output); + + // Then + output.PreElement.GetContent().ShouldBe( + $""" + + + """); + } +} diff --git a/test/ValidationStatusTagHelperTests.cs b/test/ValidationStatusTagHelperTests.cs index 552e035..551077f 100644 --- a/test/ValidationStatusTagHelperTests.cs +++ b/test/ValidationStatusTagHelperTests.cs @@ -13,11 +13,9 @@ public void Should_not_throw_when_for_is_null() { // Given var context = MakeTagHelperContext( - tagName: "input", - new TagHelperAttributeList()); + tagName: "input"); var output = MakeTagHelperOutput( - tagName: "input", - new TagHelperAttributeList()); + tagName: "input"); var options = Options.Create(new TagOptions()); var helper = new ValidationStatusTagHelper(options)