Skip to content

Commit

Permalink
Add new merge default classes tag helper
Browse files Browse the repository at this point in the history
  • Loading branch information
xt0rted committed Apr 9, 2024
1 parent bf458a4 commit 86be95c
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<a
Expand Down Expand Up @@ -147,6 +148,37 @@ Name | Value | Description
`default-class` | `string` | The css classes to apply if the link doesn't match the current url.
`match` | `Full` (default) or `Base` | The method to use when matching the link to the current url.

### MergeDefaultClassTagHelper

The merge default class tag helper is applied by adding the `merge-classes` attribute and will merge the `default-class` attribute with the `class` attribute.
This can be helpful when you aren't using another tag helper that works with the class lists, but still need access to the default classes of an element in your JavaScript.

```html
<div
merge-classes
class="flex flex-col"
default-class="bg-white text-black"
>
</div>
```

This tag helper target element is `*` so it can be used with any element, including web components and other tag helpers.

```html
<heroicon-solid
icon="AtSymbol"
merge-classes
class="h-5 w-5"
default-class="text-gray-400"
/>
```

#### 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.
Expand Down
54 changes: 54 additions & 0 deletions src/MergeClassesTagHelper.cs
Original file line number Diff line number Diff line change
@@ -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<TagOptions> settings)
=> _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));

/// <summary>
/// The classes to merge into the main class list.
/// </summary>
[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("<!--");

output.PreElement.Append(" Base: ");
output.PreElement.AppendLine(output.Attributes.GetValue("class"));

output.PreElement.Append(" Default: ");
output.PreElement.AppendLine(DefaultClass ?? "");

output.PreElement.AppendHtmlLine("-->");
}

if (classList?.Length > 0)
{
foreach (var className in classList)
{
output.AddClass(className, HtmlEncoder.Default);
}
}
}
}
24 changes: 8 additions & 16 deletions test/LinkAriaCurrentStateTagHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ILogger<LinkAriaCurrentStateTagHelper>>();
var options = Options.Create(new TagOptions());
Expand All @@ -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);

Expand Down Expand Up @@ -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<ILogger<LinkAriaCurrentStateTagHelper>>();
var options = Options.Create(
Expand All @@ -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<ILogger<LinkAriaCurrentStateTagHelper>>();
var options = Options.Create(
Expand Down
149 changes: 149 additions & 0 deletions test/MergeDefaultClassTagHelperTests.cs
Original file line number Diff line number Diff line change
@@ -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(
$"""
<!--
Base: {defaultClasses}
Default: bg-white text-black
-->
""");
}
}
6 changes: 2 additions & 4 deletions test/ValidationStatusTagHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 86be95c

Please sign in to comment.