Skip to content

Commit 4150da3

Browse files
Added modal support for size & full screen #6
1 parent 78d0658 commit 4150da3

File tree

2 files changed

+207
-5
lines changed

2 files changed

+207
-5
lines changed

src/AspNetCore.Utilities.Bootstrap5TagHelpers.Tests/Modal/ModalTagHelperTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Contexts;
22
using ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Modal;
33
using Microsoft.AspNetCore.Razor.TagHelpers;
4+
using System.Drawing;
45

56
namespace ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Tests.Modal;
67

@@ -186,4 +187,47 @@ public async Task Should_Render_Scrollable_Class_When_Flagged()
186187
//Assert
187188
Assert.Equal(expectedContent, output.Content.GetContent());
188189
}
190+
191+
[Theory]
192+
[InlineData(ModalSize.Small, "modal-dialog modal-sm")]
193+
[InlineData(ModalSize.Default, "modal-dialog")]
194+
[InlineData(ModalSize.Large, "modal-dialog modal-lg")]
195+
[InlineData(ModalSize.ExtraLarge, "modal-dialog modal-xl")]
196+
public async Task Should_Render_ProperModalDialogClass_ForSize(ModalSize size, string expectedClass)
197+
{
198+
//Arrange
199+
TagHelperContext context = MakeTagHelperContext();
200+
TagHelperOutput output = MakeTagHelperOutput(" ");
201+
var expectedContent = $"<div class=\"{expectedClass}\"><div class=\"modal-content\"></div></div>";
202+
203+
//Act
204+
var helper = new ModalTagHelper { Size = size};
205+
await helper.ProcessAsync(context, output);
206+
207+
//Assert
208+
Assert.Equal(expectedContent, output.Content.GetContent());
209+
}
210+
211+
[Theory]
212+
[InlineData(ModalFullscreenMode.Never, "modal-dialog")]
213+
[InlineData(ModalFullscreenMode.Always, "modal-dialog modal-fullscreen")]
214+
[InlineData(ModalFullscreenMode.BelowSmall, "modal-dialog modal-fullscreen-sm-down")]
215+
[InlineData(ModalFullscreenMode.BelowMedium, "modal-dialog modal-fullscreen-md-down")]
216+
[InlineData(ModalFullscreenMode.BelowLarge, "modal-dialog modal-fullscreen-lg-down")]
217+
[InlineData(ModalFullscreenMode.BelowXLarge, "modal-dialog modal-fullscreen-xl-down")]
218+
[InlineData(ModalFullscreenMode.BelowXXLarge, "modal-dialog modal-fullscreen-xxl-down")]
219+
public async Task Should_Render_ProperModalDialogClass_ForFullscreen(ModalFullscreenMode mode, string expectedClass)
220+
{
221+
//Arrange
222+
TagHelperContext context = MakeTagHelperContext();
223+
TagHelperOutput output = MakeTagHelperOutput(" ");
224+
var expectedContent = $"<div class=\"{expectedClass}\"><div class=\"modal-content\"></div></div>";
225+
226+
//Act
227+
var helper = new ModalTagHelper { FullscreenMode = mode};
228+
await helper.ProcessAsync(context, output);
229+
230+
//Assert
231+
Assert.Equal(expectedContent, output.Content.GetContent());
232+
}
189233
}

src/AspNetCore.Utilities.Bootstrap5TagHelpers/Modal/ModalTagHelper.cs

Lines changed: 163 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,170 @@
22
using Microsoft.AspNetCore.Mvc.Rendering;
33
using Microsoft.AspNetCore.Mvc.TagHelpers;
44
using Microsoft.AspNetCore.Razor.TagHelpers;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Text.Encodings.Web;
67
using System.Threading.Tasks;
78

89
namespace ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Modal;
910

11+
/// <summary>
12+
/// A collection of options for modal dialog sizing
13+
/// </summary>
14+
public enum ModalSize
15+
{
16+
/// <summary>
17+
/// Will render with .modal-sm
18+
/// </summary>
19+
Small = 0,
20+
21+
/// <summary>
22+
/// Will render without any additional class
23+
/// </summary>
24+
Default = 1,
25+
26+
/// <summary>
27+
/// Will render with .modal-lg
28+
/// </summary>
29+
Large = 2,
30+
31+
/// <summary>
32+
/// Will render with .modal-xl
33+
/// </summary>
34+
ExtraLarge = 3
35+
}
36+
37+
/// <summary>
38+
/// A collection of options for setting the full-screen mode of a modal
39+
/// </summary>
40+
public enum ModalFullscreenMode
41+
{
42+
/// <summary>
43+
/// The default behavior, it will NEVER be full screen
44+
/// </summary>
45+
Never = 0,
46+
/// <summary>
47+
/// The dialog will ALWAYS be full scree
48+
/// </summary>
49+
Always = 1,
50+
/// <summary>
51+
/// The dialog will only be full screen below the small breakpoint
52+
/// </summary>
53+
BelowSmall = 2,
54+
/// <summary>
55+
/// The dialog will only be full screen below the medium breakpoint
56+
/// </summary>
57+
BelowMedium = 3,
58+
/// <summary>
59+
/// The dialog will only be full screen below the large breakpoint
60+
/// </summary>
61+
BelowLarge = 4,
62+
/// <summary>
63+
/// The dialog will only be full screen below the extra large breakpoint
64+
/// </summary>
65+
BelowXLarge = 5,
66+
/// <summary>
67+
/// The dialog will only be full screen below the extra extra large breakpoint
68+
/// </summary>
69+
BelowXXLarge = 6
70+
}
71+
72+
/// <summary>
73+
/// Extension methods for helping with conversion of enum to class
74+
/// </summary>
75+
public static class ModalEnumExtensions
76+
{
77+
/// <summary>
78+
/// Converts to the proper CSS class
79+
/// </summary>
80+
/// <param name="modalSize">The targeted size of the modal</param>
81+
/// <returns></returns>
82+
public static string ToClass(this ModalSize modalSize)
83+
{
84+
switch (modalSize)
85+
{
86+
case ModalSize.Small:
87+
return "modal-sm";
88+
case ModalSize.Large:
89+
return "modal-lg";
90+
case ModalSize.ExtraLarge:
91+
return "modal-xl";
92+
default:
93+
return string.Empty;
94+
}
95+
}
96+
97+
/// <summary>
98+
/// Converts to proper css class
99+
/// </summary>
100+
/// <param name="mode">The targeted mode for full screen dispay</param>
101+
/// <returns></returns>
102+
public static string ToClass(this ModalFullscreenMode mode)
103+
{
104+
switch (mode)
105+
{
106+
case ModalFullscreenMode.Always:
107+
return "modal-fullscreen";
108+
case ModalFullscreenMode.BelowSmall:
109+
return "modal-fullscreen-sm-down";
110+
case ModalFullscreenMode.BelowMedium:
111+
return "modal-fullscreen-md-down";
112+
case ModalFullscreenMode.BelowLarge:
113+
return "modal-fullscreen-lg-down";
114+
case ModalFullscreenMode.BelowXLarge:
115+
return "modal-fullscreen-xl-down";
116+
case ModalFullscreenMode.BelowXXLarge:
117+
return "modal-fullscreen-xxl-down";
118+
default:
119+
return string.Empty;
120+
}
121+
}
122+
}
123+
124+
10125
/// <summary>
11126
/// A high-level wrapper Tag Helper for rendering a bootstrap Modal
12127
/// </summary>
13128
[RestrictChildren("modal-body", "modal-header", "modal-footer")]
14129
public class ModalTagHelper : TagHelper
15130
{
131+
/// <summary>
132+
/// Determines the optional size of the modal dialog
133+
/// </summary>
134+
public ModalSize Size { get; set; } = ModalSize.Default;
135+
136+
/// <summary>
137+
/// Determines the optional full screen mode of the dialog.
138+
/// </summary>
139+
public ModalFullscreenMode FullscreenMode { get; set; } = ModalFullscreenMode.Never;
140+
16141
/// <summary>
17142
/// If set to true the background will not be clickable to dismiss the dialog
18143
/// </summary>
19144
public bool StaticBackdrop { get; set; } = false;
20145

21146
/// <summary>
22-
/// If set to true the modal will have the added class of modal-dialog-centered
147+
/// If set to true the modal will have the added class of modal-dialog-centered
23148
/// </summary>
24149
public bool VerticallyCentered { get; set; } = false;
25150

26151
/// <summary>
27-
/// If set to true the modal will have the added class of modal-dialog-scrollable
152+
/// If set to true the modal will have the added class of modal-dialog-scrollable
28153
/// </summary>
29154
public bool Scrollable { get; set; } = false;
30155

156+
/// <summary>
157+
/// Ensure that if we have a context item that we reset. This is needed when you have multiple tag helpers on the same
158+
/// page if the information is not shared
159+
/// </summary>
160+
/// <param name="context"></param>
161+
[ExcludeFromCodeCoverage]
31162
public override void Init(TagHelperContext context)
32163
{
33164
//Reset
34-
if(context.Items.ContainsKey(typeof(ModalContext)))
165+
if (context.Items.ContainsKey(typeof(ModalContext)))
166+
{
35167
context.Items.Remove(typeof(ModalContext));
168+
}
36169
}
37170

38171
/// <summary>
@@ -46,7 +179,9 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
46179
//Obtain the id value to add to the context
47180
var id = "";
48181
if (output.Attributes.ContainsName("id"))
182+
{
49183
id = output.Attributes["id"].Value.ToString();
184+
}
50185

51186
//Add the id to the context
52187
var modalContext = new ModalContext { Id = id };
@@ -61,19 +196,42 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
61196
//Add classes to the existing tag, merging with custom ones added
62197
output.AddClass("modal", HtmlEncoder.Default);
63198
output.AddClass("fade", HtmlEncoder.Default);
64-
65-
if(!string.IsNullOrEmpty(id))
199+
200+
if (!string.IsNullOrEmpty(id))
201+
{
66202
output.Attributes.Add("aria-labelledby", $"{id}Label");
203+
}
204+
67205
output.Attributes.Add("aria-hidden", "true");
68206
if (StaticBackdrop)
207+
{
69208
output.Attributes.Add("data-bs-backdrop", "static");
209+
}
210+
70211
output.Attributes.Add("tabindex", "-1");
71212
var dialogWrapper = new TagBuilder("div");
72213
dialogWrapper.AddCssClass("modal-dialog");
73214
if (VerticallyCentered)
215+
{
74216
dialogWrapper.AddCssClass("modal-dialog-centered");
217+
}
218+
75219
if (Scrollable)
220+
{
76221
dialogWrapper.AddCssClass("modal-dialog-scrollable");
222+
}
223+
224+
var sizeClass = Size.ToClass();
225+
if (!string.IsNullOrEmpty(sizeClass))
226+
{
227+
dialogWrapper.AddCssClass(sizeClass);
228+
}
229+
230+
var fullscreenClass = FullscreenMode.ToClass();
231+
if (!string.IsNullOrEmpty(fullscreenClass))
232+
{
233+
dialogWrapper.AddCssClass(fullscreenClass);
234+
}
77235
var dialogContent = new TagBuilder("div");
78236
dialogContent.AddCssClass("modal-content");
79237
dialogContent.InnerHtml.AppendHtml(body);

0 commit comments

Comments
 (0)