Skip to content

Commit d190b12

Browse files
gunndabadsussexrick
andcommitted
Add Summary card component (#266)
* First draft of support for summary card * Complete Summary Card component implementation --------- Co-authored-by: Rick Mason <[email protected]>
1 parent 1a9a8b9 commit d190b12

32 files changed

+1017
-90
lines changed

docs/components/summary-list.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,30 @@
9696

9797
![Summary list](../images/summary-list-without-actions.png)
9898

99+
## Example - with card
100+
101+
```razor
102+
<govuk-summary-card>
103+
<govuk-summary-card-title>University of Gloucestershire</govuk-summary-card-title>
104+
<govuk-summary-card-actions>
105+
<govuk-summary-card-action href="#" visually-hidden-text="of University of Gloucestershire">Delete choice</govuk-summary-card-action>
106+
<govuk-summary-card-action href="#" visually-hidden-text="from University of Gloucestershire">Withdraw</govuk-summary-card-action>
107+
</govuk-summary-card-actions>
108+
<govuk-summary-list>
109+
<govuk-summary-list-row>
110+
<govuk-summary-list-row-key>Course</govuk-summary-list-row-key>
111+
<govuk-summary-list-row-value>English (3DMD)<br>PGCE with QTS full time</govuk-summary-list-row-value>
112+
</govuk-summary-list-row>
113+
<govuk-summary-list-row>
114+
<govuk-summary-list-row-key>Location</govuk-summary-list-row-key>
115+
<govuk-summary-list-row-value>School name<br>Road, City, SW1 1AA</govuk-summary-list-row-value>
116+
</govuk-summary-list-row>
117+
</govuk-summary-list>
118+
</govuk-summary-card>
119+
```
120+
121+
![Summary list](../images/summary-list-with-card.png)
122+
99123
## API
100124

101125
### `<govuk-summary-list>`
@@ -130,3 +154,31 @@ Must be inside a `<govuk-summary-list-row>` element.
130154

131155
The content is the HTML to use within the generated link.\
132156
Must be inside a `<govuk-summary-list-row-actions>` element.
157+
158+
### `<govuk-summary-card>`
159+
160+
Must contain a `<govuk-summary-list>` as its final child element.
161+
162+
### `<govuk-summary-card-title>`
163+
164+
| Attribute | Type | Description |
165+
| --- | --- | --- |
166+
| `heading-level` | `int` | The heading level. Must be between `1` and `6` (inclusive). The default is `2`. |
167+
168+
The content is the HTML to use for the card's title.\
169+
Must be inside a `<govuk-summary-card>` element.
170+
171+
### `<govuk-summary-card-actions>`
172+
173+
The container element for the card's actions, if any.\
174+
Must be inside a `<govuk-summary-card>` element.
175+
176+
### `<govuk-summary-card-action>`
177+
178+
| Attribute | Type | Description |
179+
| --- | --- | --- |
180+
| (link attributes) | | If specified generates an `href` attribute using the specified values. See [documentation on links](../links.md) for more information. |
181+
| `visually-hidden-text` | `string` | The visually hidden text for the link. |
182+
183+
The content is the HTML to use within the generated link.\
184+
Must be inside a `<govuk-summary-card-actions>` element.
18.7 KB
Loading
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@page
2+
3+
<govuk-summary-card>
4+
<govuk-summary-card-title>University of Gloucestershire</govuk-summary-card-title>
5+
<govuk-summary-card-actions>
6+
<govuk-summary-card-action href="#" visually-hidden-text="of University of Gloucestershire">Delete choice</govuk-summary-card-action>
7+
<govuk-summary-card-action href="#" visually-hidden-text="from University of Gloucestershire">Withdraw</govuk-summary-card-action>
8+
</govuk-summary-card-actions>
9+
<govuk-summary-list>
10+
<govuk-summary-list-row>
11+
<govuk-summary-list-row-key>Course</govuk-summary-list-row-key>
12+
<govuk-summary-list-row-value>English (3DMD)<br>PGCE with QTS full time</govuk-summary-list-row-value>
13+
</govuk-summary-list-row>
14+
<govuk-summary-list-row>
15+
<govuk-summary-list-row-key>Location</govuk-summary-list-row-key>
16+
<govuk-summary-list-row-value>School name<br>Road, City, SW1 1AA</govuk-summary-list-row-value>
17+
</govuk-summary-list-row>
18+
</govuk-summary-list>
19+
</govuk-summary-card>

src/GovUk.Frontend.AspNetCore.DocSamplesScreenshotter/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ await WriteScreenshots(new[]
7676
("Radios/RadiosWithError", "radios-with-error.png"),
7777
("Select/Select", "select.png"),
7878
("SummaryList/SummaryListWithActions", "summary-list-with-actions.png"),
79+
("SummaryList/SummaryListWithCard", "summary-list-with-card.png"),
7980
("SummaryList/SummaryListWithoutActions", "summary-list-without-actions.png"),
8081
("PhaseBanner/PhaseBanner", "phase-banner.png"),
8182
("Tabs/Tabs", "tabs.png"),
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using Microsoft.AspNetCore.Html;
2+
using Microsoft.AspNetCore.Mvc.Rendering;
3+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
4+
5+
namespace GovUk.Frontend.AspNetCore.HtmlGeneration
6+
{
7+
internal partial class ComponentGenerator
8+
{
9+
internal const string SummaryCardElement = "div";
10+
internal const int SummaryCardDefaultHeadingLevel = 2;
11+
internal const int SummaryCardMinHeadingLevel = 1;
12+
internal const int SummaryCardMaxHeadingLevel = 6;
13+
14+
public TagBuilder GenerateSummaryCard(
15+
SummaryCardTitle? title,
16+
SummaryListActions? actions,
17+
IHtmlContent summaryList,
18+
AttributeDictionary? attributes)
19+
{
20+
Guard.ArgumentValidNotNull(
21+
nameof(summaryList),
22+
$"Summary card is not valid; {nameof(summaryList)} cannot be null.",
23+
summaryList,
24+
summaryList != null);
25+
26+
var tagBuilder = new TagBuilder(SummaryCardElement);
27+
tagBuilder.MergeOptionalAttributes(attributes);
28+
tagBuilder.MergeCssClass("govuk-summary-card");
29+
30+
var headerTagBuilder = new TagBuilder("div");
31+
headerTagBuilder.MergeCssClass("govuk-summary-card__title-wrapper");
32+
tagBuilder.InnerHtml.AppendHtml(headerTagBuilder);
33+
34+
if (title is not null)
35+
{
36+
var titleTagBuilder = new TagBuilder($"h{title.HeadingLevel ?? SummaryCardDefaultHeadingLevel}");
37+
titleTagBuilder.MergeOptionalAttributes(title.Attributes);
38+
titleTagBuilder.MergeCssClass("govuk-summary-card__title");
39+
titleTagBuilder.InnerHtml.AppendHtml(title.Content);
40+
41+
headerTagBuilder.InnerHtml.AppendHtml(titleTagBuilder);
42+
}
43+
44+
if (actions?.Items?.Count > 0)
45+
{
46+
var actionsWrapper = new TagBuilder(actions.Items.Count == 1 ? "div" : "ul");
47+
actionsWrapper.MergeOptionalAttributes(actions.Attributes);
48+
actionsWrapper.MergeCssClass("govuk-summary-card__actions");
49+
50+
if (actions.Items!.Count == 1)
51+
{
52+
actionsWrapper.InnerHtml.AppendHtml(GenerateLink(actions.Items![0], actionIndex: 0));
53+
}
54+
else
55+
{
56+
var actionIndex = 0;
57+
foreach (var action in actions.Items)
58+
{
59+
var li = new TagBuilder("li");
60+
li.MergeCssClass("govuk-summary-card__action");
61+
li.InnerHtml.AppendHtml(GenerateLink(action, actionIndex++));
62+
63+
actionsWrapper.InnerHtml.AppendHtml(li);
64+
}
65+
}
66+
67+
headerTagBuilder.InnerHtml.AppendHtml(actionsWrapper);
68+
}
69+
70+
var contentTagBuilder = new TagBuilder("div");
71+
contentTagBuilder.MergeCssClass("govuk-summary-card__content");
72+
tagBuilder.InnerHtml.AppendHtml(contentTagBuilder);
73+
contentTagBuilder.InnerHtml.AppendHtml(summaryList);
74+
75+
return tagBuilder;
76+
}
77+
78+
static TagBuilder GenerateLink(SummaryListAction action, int actionIndex)
79+
{
80+
Guard.ArgumentValidNotNull(
81+
nameof(SummaryCard.Actions),
82+
$"Action {actionIndex} is not valid; {nameof(SummaryListAction.Content)} cannot be null.",
83+
action.Content,
84+
action.Content != null);
85+
86+
var anchor = new TagBuilder(SummaryListRowActionElement);
87+
anchor.MergeOptionalAttributes(action.Attributes);
88+
anchor.MergeCssClass("govuk-link");
89+
anchor.InnerHtml.AppendHtml(action.Content);
90+
91+
if (action.VisuallyHiddenText != null)
92+
{
93+
var vht = new TagBuilder("span");
94+
vht.MergeCssClass("govuk-visually-hidden");
95+
vht.InnerHtml.Append(action.VisuallyHiddenText);
96+
anchor.InnerHtml.AppendHtml(vht);
97+
}
98+
99+
return anchor;
100+
}
101+
}
102+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ internal partial class ComponentGenerator
1515
internal const string SummaryListRowValueElement = "dd";
1616

1717
public TagBuilder GenerateSummaryList(
18-
AttributeDictionary? attributes,
19-
IEnumerable<SummaryListRow> rows)
18+
IEnumerable<SummaryListRow> rows,
19+
AttributeDictionary? attributes)
2020
{
2121
Guard.ArgumentNotNull(nameof(rows), rows);
2222

@@ -116,11 +116,11 @@ public TagBuilder GenerateSummaryList(
116116

117117
return tagBuilder;
118118

119-
static TagBuilder GenerateLink(SummaryListRowAction action, int rowIndex)
119+
static TagBuilder GenerateLink(SummaryListAction action, int rowIndex)
120120
{
121121
Guard.ArgumentValidNotNull(
122122
nameof(rows),
123-
$"Row {rowIndex} is not valid; {nameof(SummaryListRowAction.Content)} cannot be null.",
123+
$"Row {rowIndex} is not valid; {nameof(SummaryListAction.Content)} cannot be null.",
124124
action.Content,
125125
action.Content != null);
126126

src/GovUk.Frontend.AspNetCore/HtmlGeneration/SummaryListRow.cs renamed to src/GovUk.Frontend.AspNetCore/HtmlGeneration/SummaryList.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,34 @@
44

55
namespace GovUk.Frontend.AspNetCore.HtmlGeneration
66
{
7+
internal class SummaryCard
8+
{
9+
public SummaryCardTitle? Title { get; set; }
10+
public SummaryListActions? Actions { get; set; }
11+
}
12+
13+
internal class SummaryCardTitle
14+
{
15+
public IHtmlContent? Content { get; set; }
16+
public AttributeDictionary? Attributes { get; set; }
17+
public int? HeadingLevel { get; set; }
18+
}
19+
720
internal class SummaryListRow
821
{
922
public SummaryListRowKey? Key { get; set; }
1023
public SummaryListRowValue? Value { get; set; }
1124
public AttributeDictionary? Attributes { get; set; }
12-
public SummaryListRowActions? Actions { get; set; }
25+
public SummaryListActions? Actions { get; set; }
1326
}
1427

15-
internal class SummaryListRowActions
28+
internal class SummaryListActions
1629
{
17-
public IReadOnlyList<SummaryListRowAction>? Items { get; set; }
30+
public IReadOnlyList<SummaryListAction>? Items { get; set; }
1831
public AttributeDictionary? Attributes { get; set; }
1932
}
2033

21-
internal class SummaryListRowAction
34+
internal class SummaryListAction
2235
{
2336
public string? VisuallyHiddenText { get; set; }
2437
public IHtmlContent? Content { get; set; }

src/GovUk.Frontend.AspNetCore/IGovUkHtmlGenerator.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ TagBuilder GenerateSelect(
168168

169169
TagBuilder GenerateSkipLink(string href, IHtmlContent content, AttributeDictionary attributes);
170170

171-
TagBuilder GenerateSummaryList(AttributeDictionary attributes, IEnumerable<SummaryListRow> rows);
171+
TagBuilder GenerateSummaryCard(SummaryCardTitle title, SummaryListActions actions, IHtmlContent summaryList, AttributeDictionary attributes);
172+
173+
TagBuilder GenerateSummaryList(IEnumerable<SummaryListRow> rows, AttributeDictionary attributes);
172174

173175
TagBuilder GenerateTabs(
174176
string id,

src/GovUk.Frontend.AspNetCore/TagHelperContextExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using GovUk.Frontend.AspNetCore.TagHelpers;
24
using Microsoft.AspNetCore.Razor.TagHelpers;
35

46
namespace GovUk.Frontend.AspNetCore
@@ -42,6 +44,20 @@ public static IDisposable SetScopedContextItem(
4244
return new RestoreItemsOnDispose(context, key, previousValue);
4345
}
4446

47+
public static bool TryGetContextItem<TItem>(this TagHelperContext context, [NotNullWhen(true)] out TItem? item)
48+
{
49+
if (context.Items.TryGetValue(typeof(TItem), out var itemObj))
50+
{
51+
item = (TItem)itemObj;
52+
return true;
53+
}
54+
else
55+
{
56+
item = default;
57+
return false;
58+
}
59+
}
60+
4561
internal class RestoreItemsOnDispose : IDisposable
4662
{
4763
private readonly TagHelperContext _context;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ namespace GovUk.Frontend.AspNetCore.TagHelpers
8383
[HtmlTargetElement(PaginationPreviousTagHelper.TagName, Attributes = "asp-route")]
8484
[HtmlTargetElement(PaginationPreviousTagHelper.TagName, Attributes = "asp-all-route-data")]
8585
[HtmlTargetElement(PaginationPreviousTagHelper.TagName, Attributes = "asp-route-*")]
86+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-action")]
87+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-controller")]
88+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-area")]
89+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-page")]
90+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-page-handler")]
91+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-fragment")]
92+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-host")]
93+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-protocol")]
94+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-route")]
95+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-all-route-data")]
96+
[HtmlTargetElement(SummaryCardActionTagHelper.TagName, Attributes = "asp-route-*")]
8697
[HtmlTargetElement(SummaryListRowActionTagHelper.TagName, Attributes = "asp-action")]
8798
[HtmlTargetElement(SummaryListRowActionTagHelper.TagName, Attributes = "asp-controller")]
8899
[HtmlTargetElement(SummaryListRowActionTagHelper.TagName, Attributes = "asp-area")]

0 commit comments

Comments
 (0)