Skip to content

Commit 621cb33

Browse files
committed
Port Panel to IComponentGenerator
1 parent 9238eb0 commit 621cb33

File tree

17 files changed

+168
-170
lines changed

17 files changed

+168
-170
lines changed

docs/components/panel.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
```razor
88
<govuk-panel heading-level="2">
9-
<govuk-panel-title>Application complete</govuk-panel-title>
10-
<govuk-panel-body>
9+
<panel-title>Application complete</panel-title>
10+
<panel-body>
1111
Your reference number<br><strong>HDJ2123F</strong>
12-
</govuk-panel-body>
12+
</panel-body>
1313
</govuk-panel>
1414
```
1515

@@ -19,17 +19,17 @@
1919

2020
### `<govuk-panel>`
2121

22-
| Attribute | Type | Description |
23-
| --- | --- | --- |
24-
| `heading-level` | `int` | The heading level. Must be between `1` and `6` (inclusive). The default is `1`. |
22+
| Attribute | Type | Description |
23+
| --- | --- |-------------------------------------------------------------------------------------------------|
24+
| `heading-level` | `int` | The heading level. Must be between `1` and `6` (inclusive). If not specified, `1` will be used. |
2525

26-
### `<govuk-panel-title>`
26+
### `<panel-title>`
2727

2828
*Required*\
2929
The content is the HTML to use within the panel title.\
3030
Must be inside a `<govuk-panel>` element.
3131

32-
### `<govuk-panel-body>`
32+
### `<panel-body>`
3333

3434
The content is the HTML to use within the panel content.\
3535
Must be inside a `<govuk-panel>` element.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
@page
22

33
<govuk-panel heading-level="2">
4-
<govuk-panel-title>Application complete</govuk-panel-title>
5-
<govuk-panel-body>
4+
<panel-title>Application complete</panel-title>
5+
<panel-body>
66
Your reference number<br><strong>HDJ2123F</strong>
7-
</govuk-panel-body>
7+
</panel-body>
88
</govuk-panel>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
3+
namespace GovUk.Frontend.AspNetCore.ComponentGeneration;
4+
5+
public partial class DefaultComponentGenerator
6+
{
7+
internal const int PanelDefaultHeadingLevel = 1;
8+
internal const string PanelElement = "div";
9+
internal const int PanelMinHeadingLevel = 1;
10+
internal const int PanelMaxHeadingLevel = 6;
11+
12+
/// <inheritdoc/>
13+
public virtual HtmlTagBuilder GeneratePanel(PanelOptions options)
14+
{
15+
ArgumentNullException.ThrowIfNull(options);
16+
options.Validate();
17+
18+
return new HtmlTagBuilder(PanelElement)
19+
.WithCssClasses("govuk-panel", "govuk-panel--confirmation")
20+
.WithCssClasses(ExplodeClasses(options.Classes?.ToHtmlString()))
21+
.WithAttributes(options.Attributes)
22+
.WithAppendedHtml(new HtmlTagBuilder($"h{options.HeadingLevel ?? PanelDefaultHeadingLevel}")
23+
.WithCssClass("govuk-panel__title")
24+
.WithAttributes(options.TitleAttributes)
25+
.WithAppendedHtml(GetEncodedTextOrHtml(options.TitleText, options.TitleHtml)!))
26+
.When(
27+
options.Text.NormalizeEmptyString() is not null || options.Html.NormalizeEmptyString() is not null,
28+
b => b.WithAppendedHtml(new HtmlTagBuilder("div")
29+
.WithCssClass("govuk-panel__body")
30+
.WithAttributes(options.BodyAttributes)
31+
.WithAppendedHtml(GetEncodedTextOrHtml(options.Text, options.Html)!)));
32+
}
33+
}

src/GovUk.Frontend.AspNetCore/ComponentGeneration/IComponentGenerator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ public interface IComponentGenerator
8383
/// <returns>An <see cref="HtmlTagBuilder"/> with the component's HTML.</returns>
8484
HtmlTagBuilder GeneratePagination(PaginationOptions options);
8585

86+
/// <summary>
87+
/// Generates a panel component.
88+
/// </summary>
89+
/// <returns>An <see cref="HtmlTagBuilder"/> with the component's HTML.</returns>
90+
HtmlTagBuilder GeneratePanel(PanelOptions options);
91+
8692
/// <summary>
8793
/// Generates a phase banner component.
8894
/// </summary>

src/GovUk.Frontend.AspNetCore/ComponentGeneration/PanelOptions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,18 @@ public class PanelOptions
1313
public IHtmlContent? Html { get; set; }
1414
public IHtmlContent? Classes { get; set; }
1515
public EncodedAttributesDictionary? Attributes { get; set; }
16+
17+
[NonStandardParameter]
18+
internal EncodedAttributesDictionary? TitleAttributes { get; set; }
19+
20+
[NonStandardParameter]
21+
internal EncodedAttributesDictionary? BodyAttributes { get; set; }
22+
23+
internal void Validate()
24+
{
25+
if (TitleHtml.NormalizeEmptyString() is null && TitleText.NormalizeEmptyString() is null)
26+
{
27+
throw new InvalidOptionsException(GetType(), $"{nameof(TitleHtml)} or {nameof(TitleText)} must be specified.");
28+
}
29+
}
1630
}

src/GovUk.Frontend.AspNetCore/HtmlGeneration/ComponentGenerator.Panel.cs

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/GovUk.Frontend.AspNetCore/IGovUkHtmlGenerator.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,6 @@ TagBuilder GenerateNotificationBanner(
100100
IHtmlContent content,
101101
AttributeDictionary attributes);
102102

103-
TagBuilder GeneratePanel(
104-
int headingLevel,
105-
IHtmlContent titleContent,
106-
IHtmlContent bodyContent,
107-
AttributeDictionary attributes);
108-
109103
TagBuilder GenerateRadios(
110104
string idPrefix,
111105
string name,

src/GovUk.Frontend.AspNetCore/TagHelpers/PanelBodyTagHelper.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Threading.Tasks;
2+
using GovUk.Frontend.AspNetCore.ComponentGeneration;
23
using Microsoft.AspNetCore.Razor.TagHelpers;
34

45
namespace GovUk.Frontend.AspNetCore.TagHelpers;
@@ -7,23 +8,25 @@ namespace GovUk.Frontend.AspNetCore.TagHelpers;
78
/// Represents the body in a GDS panel component.
89
/// </summary>
910
[HtmlTargetElement(TagName, ParentTag = PanelTagHelper.TagName)]
11+
[HtmlTargetElement(ShortTagName, ParentTag = PanelTagHelper.TagName)]
1012
public class PanelBodyTagHelper : TagHelper
1113
{
1214
internal const string TagName = "govuk-panel-body";
15+
internal const string ShortTagName = "panel-body";
1316

1417
/// <inheritdoc/>
1518
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
1619
{
1720
var panelContext = context.GetContextItem<PanelContext>();
1821

19-
var childContent = await output.GetChildContentAsync();
22+
var childContent = (await output.GetChildContentAsync()).Snapshot();
2023

2124
if (output.Content.IsModified)
2225
{
2326
childContent = output.Content;
2427
}
2528

26-
panelContext.SetBody(childContent.Snapshot());
29+
panelContext.SetBody(childContent, new EncodedAttributesDictionary(output.Attributes), output.TagName);
2730

2831
output.SuppressOutput();
2932
}
Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,55 @@
1+
using System;
2+
using GovUk.Frontend.AspNetCore.ComponentGeneration;
13
using Microsoft.AspNetCore.Html;
24

35
namespace GovUk.Frontend.AspNetCore.TagHelpers;
46

57
internal class PanelContext
68
{
7-
public IHtmlContent? Body { get; private set; }
8-
public IHtmlContent? Title { get; private set; }
9+
private string? _bodyTagName;
910

10-
public void SetBody(IHtmlContent content)
11+
public (IHtmlContent Content, EncodedAttributesDictionary Attributes)? Body { get; private set; }
12+
public (IHtmlContent Content, EncodedAttributesDictionary Attributes)? Title { get; private set; }
13+
14+
public void SetBody(IHtmlContent content, EncodedAttributesDictionary attributes, string tagName)
1115
{
12-
Guard.ArgumentNotNull(nameof(content), content);
16+
ArgumentNullException.ThrowIfNull(content);
17+
ArgumentNullException.ThrowIfNull(attributes);
18+
ArgumentNullException.ThrowIfNull(tagName);
1319

1420
if (Body != null)
1521
{
16-
throw ExceptionHelper.OnlyOneElementIsPermittedIn(PanelBodyTagHelper.TagName, PanelTagHelper.TagName);
22+
throw ExceptionHelper.OnlyOneElementIsPermittedIn(tagName, PanelTagHelper.TagName);
1723
}
1824

19-
Body = content;
25+
Body = (content, attributes);
26+
_bodyTagName = tagName;
2027
}
2128

22-
public void SetTitle(IHtmlContent content)
29+
public void SetTitle(IHtmlContent content, EncodedAttributesDictionary attributes, string tagName)
2330
{
24-
Guard.ArgumentNotNull(nameof(content), content);
31+
ArgumentNullException.ThrowIfNull(content);
32+
ArgumentNullException.ThrowIfNull(attributes);
33+
ArgumentNullException.ThrowIfNull(tagName);
2534

2635
if (Title != null)
2736
{
28-
throw ExceptionHelper.OnlyOneElementIsPermittedIn(PanelTitleTagHelper.TagName, PanelTagHelper.TagName);
37+
throw ExceptionHelper.OnlyOneElementIsPermittedIn(tagName, PanelTagHelper.TagName);
2938
}
3039

3140
if (Body != null)
3241
{
33-
throw ExceptionHelper.ChildElementMustBeSpecifiedBefore(PanelTitleTagHelper.TagName, PanelBodyTagHelper.TagName);
42+
throw ExceptionHelper.ChildElementMustBeSpecifiedBefore(tagName, _bodyTagName!);
3443
}
3544

36-
Title = content;
45+
Title = (content, attributes);
3746
}
3847

3948
public void ThrowIfNotComplete()
4049
{
4150
if (Title == null)
4251
{
43-
throw ExceptionHelper.AChildElementMustBeProvided(PanelTitleTagHelper.TagName);
52+
throw ExceptionHelper.AChildElementMustBeProvided([PanelTitleTagHelper.ShortTagName, PanelTitleTagHelper.TagName]);
4453
}
4554
}
4655
}
Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Threading.Tasks;
3-
using GovUk.Frontend.AspNetCore.HtmlGeneration;
4-
using Microsoft.AspNetCore.Mvc.TagHelpers;
3+
using GovUk.Frontend.AspNetCore.ComponentGeneration;
54
using Microsoft.AspNetCore.Razor.TagHelpers;
65

76
namespace GovUk.Frontend.AspNetCore.TagHelpers;
@@ -10,48 +9,45 @@ namespace GovUk.Frontend.AspNetCore.TagHelpers;
109
/// Generates a GDS panel component.
1110
/// </summary>
1211
[HtmlTargetElement(TagName)]
13-
[OutputElementHint(ComponentGenerator.PanelElement)]
14-
[RestrictChildren(PanelTitleTagHelper.TagName, PanelBodyTagHelper.TagName)]
12+
[OutputElementHint(DefaultComponentGenerator.PanelElement)]
13+
[RestrictChildren(PanelTitleTagHelper.TagName, PanelTitleTagHelper.ShortTagName, PanelBodyTagHelper.TagName, PanelBodyTagHelper.ShortTagName)]
1514
public class PanelTagHelper : TagHelper
1615
{
1716
internal const string TagName = "govuk-panel";
1817

1918
private const string HeadingLevelAttributeName = "heading-level";
2019

21-
private readonly IGovUkHtmlGenerator _htmlGenerator;
22-
private int _headingLevel = ComponentGenerator.PanelDefaultHeadingLevel;
20+
private readonly IComponentGenerator _componentGenerator;
21+
private int? _headingLevel;
2322

2423
/// <summary>
25-
/// Creates a new <see cref="PanelTagHelper"/>.
24+
/// Creates a new <see cref="BackLinkTagHelper"/>.
2625
/// </summary>
27-
public PanelTagHelper()
28-
: this(null)
26+
public PanelTagHelper(IComponentGenerator componentGenerator)
2927
{
30-
}
31-
32-
internal PanelTagHelper(IGovUkHtmlGenerator? htmlGenerator = null)
33-
{
34-
_htmlGenerator = htmlGenerator ?? new ComponentGenerator();
28+
ArgumentNullException.ThrowIfNull(componentGenerator);
29+
_componentGenerator = componentGenerator;
3530
}
3631

3732
/// <summary>
3833
/// The heading level.
3934
/// </summary>
4035
/// <remarks>
41-
/// Must be between <c>1</c> and <c>6</c> (inclusive). The default is <c>1</c>.
36+
/// Must be between <c>1</c> and <c>6</c> (inclusive). If not specified, <c>1</c> will be used.
4237
/// </remarks>
4338
[HtmlAttributeName(HeadingLevelAttributeName)]
44-
public int HeadingLevel
39+
public int? HeadingLevel
4540
{
4641
get => _headingLevel;
4742
set
4843
{
49-
if (value < ComponentGenerator.PanelMinHeadingLevel ||
50-
value > ComponentGenerator.PanelMaxHeadingLevel)
44+
if (value is not null && (
45+
value < DefaultComponentGenerator.PanelMinHeadingLevel ||
46+
value > DefaultComponentGenerator.PanelMaxHeadingLevel))
5147
{
5248
throw new ArgumentOutOfRangeException(
5349
nameof(value),
54-
$"{nameof(HeadingLevel)} must be between {ComponentGenerator.PanelMinHeadingLevel} and {ComponentGenerator.PanelMaxHeadingLevel}.");
50+
$"{nameof(HeadingLevel)} must be between {DefaultComponentGenerator.PanelMinHeadingLevel} and {DefaultComponentGenerator.PanelMaxHeadingLevel}.");
5551
}
5652

5753
_headingLevel = value;
@@ -70,17 +66,20 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
7066

7167
panelContext.ThrowIfNotComplete();
7268

73-
var tagBuilder = _htmlGenerator.GeneratePanel(
74-
HeadingLevel,
75-
panelContext.Title,
76-
panelContext.Body,
77-
output.Attributes.ToAttributeDictionary());
69+
var attributes = new EncodedAttributesDictionary(output.Attributes);
70+
attributes.Remove("class", out var classes);
7871

79-
output.TagName = tagBuilder.TagName;
80-
output.TagMode = TagMode.StartTagAndEndTag;
72+
var component = _componentGenerator.GeneratePanel(new PanelOptions
73+
{
74+
TitleHtml = panelContext.Title?.Content,
75+
TitleAttributes = panelContext.Title?.Attributes,
76+
HeadingLevel = HeadingLevel,
77+
Html = panelContext.Body?.Content,
78+
BodyAttributes = panelContext.Body?.Attributes,
79+
Classes = classes,
80+
Attributes = attributes
81+
});
8182

82-
output.Attributes.Clear();
83-
output.MergeAttributes(tagBuilder);
84-
output.Content.SetHtmlContent(tagBuilder.InnerHtml);
83+
component.WriteTo(output);
8584
}
8685
}

0 commit comments

Comments
 (0)