Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-51: Moving boostrap stuff to UIKit. #64

Merged
merged 11 commits into from
Apr 21, 2022
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc.Localization;
using OrchardCore.DisplayManagement;

namespace Lombiq.HelpfulExtensions.Models;
namespace Lombiq.HelpfulExtensions.Extensions.Bootstrap.Models;

public class BootstrapAccordionItem
{
Expand Down
14 changes: 14 additions & 0 deletions Extensions/Bootstrap/Models/BootstrapSplitButton.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace Lombiq.HelpfulExtensions.Extensions.Bootstrap.Models;

public class BootstrapSplitButton
{
public string Type { get; set; }
public string Text { get; set; }
public string WrapperClasses { get; set; }
public string ButtonClasses { get; set; }
public string ToggleClasses { get; set; }
public string DropdownClasses { get; set; }
public IEnumerable<(string Url, string Text)> Options { get; set; }
}
20 changes: 20 additions & 0 deletions Extensions/Bootstrap/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Lombiq.HelpfulExtensions.Extensions.Bootstrap.TagHelpers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Modules;
using System;

namespace Lombiq.HelpfulExtensions.Extensions.Bootstrap;

[Feature(FeatureIds.Bootstrap)]
public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services) =>
services.AddTagHelpers<BootstrapSplitButtonTagHelper>();

public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
// No need for anything here yet.
}
}
57 changes: 57 additions & 0 deletions Extensions/Bootstrap/TagHelpers/BootstrapSplitButtonTagHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
using OrchardCore.DisplayManagement;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Lombiq.HelpfulExtensions.Extensions.Bootstrap.TagHelpers;

[HtmlTargetElement("bootstrap-split-button", Attributes = "options")]
public class BootstrapSplitButtonTagHelper : TagHelper
{
private readonly IDisplayHelper _displayHelper;
private readonly IShapeFactory _shapeFactory;

[HtmlAttributeName("type")]
public string Type { get; set; }

[HtmlAttributeName("text")]
public string Text { get; set; }

[HtmlAttributeName("class")]
public string WrapperClasses { get; set; }

[HtmlAttributeName("button-classes")]
public string ButtonClasses { get; set; }

[HtmlAttributeName("toggle-classes")]
public string ToggleClasses { get; set; }

[HtmlAttributeName("dropdown-classes")]
public string DropdownClasses { get; set; }

[HtmlAttributeName("options")]
public IEnumerable<(string Url, string Text)> Options { get; set; }

public BootstrapSplitButtonTagHelper(IDisplayHelper displayHelper, IShapeFactory factory)
{
_displayHelper = displayHelper;
_shapeFactory = factory;
}

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
IShape shape = await _shapeFactory.New.BootstrapSplitButton(
Type: Type,
Text: Text,
WrapperClasses: WrapperClasses,
ButtonClasses: ButtonClasses,
ToggleClasses: ToggleClasses,
DropdownClasses: DropdownClasses,
Options: Options);
var content = await _displayHelper.ShapeExecuteAsync(shape);

output.TagName = null;
output.TagMode = TagMode.StartTagAndEndTag;
output.PostContent.SetHtmlContent(content);
}
}
1 change: 1 addition & 0 deletions FeatureIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public static class FeatureIds
public const string Widgets = FeatureIdPrefix + nameof(Widgets);
public const string Emails = FeatureIdPrefix + nameof(Emails);
public const string Security = FeatureIdPrefix + nameof(Security);
public const string Bootstrap = FeatureIdPrefix + nameof(Bootstrap);
}
7 changes: 7 additions & 0 deletions Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,10 @@
"OrchardCore.Email",
}
)]

[assembly: Feature(
Id = Bootstrap,
Name = "Lombiq Helpful Extensions - Bootstrap Controls",
Category = "Content",
Description = "Adds reusable shapes for various Boostrap controls. These aren't widgets but smaller units you can reuse in your templates."
)]
2 changes: 1 addition & 1 deletion Views/BootstrapAccordion.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@using Lombiq.HelpfulExtensions.Models
@using Lombiq.HelpfulExtensions.Extensions.Bootstrap.Models
@using OrchardCore.Mvc.Utilities

@{
Expand Down
67 changes: 67 additions & 0 deletions Views/BootstrapSplitButton.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@using Lombiq.HelpfulExtensions.Extensions.Bootstrap.Models
@using OrchardCore.DisplayManagement
@using OrchardCore.DisplayManagement.Implementation
@{
if (Model is not IShape shape || shape.As<BootstrapSplitButton>() is not { } viewModel) { return; }

var type = string.IsNullOrWhiteSpace(viewModel.Type) ? "primary" : viewModel.Type;
var classes = $"btn btn-{type}";
var options = viewModel.Options.AsList();
var disabled = !options.Any();
var shadowOffset = type == "primary" ? 0 : 8;
}

<div class="btn-group bootstrapSplitButton @viewModel.WrapperClasses">
<button type="button"
class="bootstrapSplitButton__main @classes @viewModel.ButtonClasses"
disabled="@disabled">
@(string.IsNullOrWhiteSpace(viewModel.Text) ? options.FirstOrDefault().Text : viewModel.Text)
</button>
<button type="button"
class="dropdown-toggle dropdown-toggle-split bootstrapSplitButton__dropdownButton @classes @viewModel.ToggleClasses"
disabled="@disabled"
aria-expanded="false">
<span class="visually-hidden">@T["Toggle Dropdown"]</span>
</button>
<div class="w-100 h-100 fixed-bottom bootstrapSplitButton__screen"></div>
<ul class="bootstrapSplitButton__dropdownMenu dropdown-menu @viewModel.DropdownClasses">
@foreach (var option in options)
{
<li class="bootstrapSplitButton__dropdownMenuItem">
@if (option.Text?.Trim().StartsWith("---") == true)
{
<hr class="dropdown-divider">
}
else
{
<a href="@option.Url" class="dropdown-item">@option.Text</a>
}
</li>
}
</ul>
</div>

<script asp-name="BootstrapSplitButtonScript" at="Foot">
Array.from(document.querySelectorAll('.bootstrapSplitButton'))
.forEach((block) => {
const toggler = block.querySelector('.bootstrapSplitButton__dropdownButton');
const menu = block.querySelector('.bootstrapSplitButton__dropdownMenu');

const screen = block.querySelector('.bootstrapSplitButton__screen');
screen.style.zIndex = 'auto';
screen.hidden = true;
screen.addEventListener('click', () => toggler.click());

block.querySelector('.bootstrapSplitButton__main')
.addEventListener('click', () => block.querySelector('a.dropdown-item').click());

toggler
.addEventListener('click', (event) => {
const expanded = event.target.getAttribute('aria-expanded') === 'false';
event.target.setAttribute('aria-expanded', expanded);
screen.hidden = !expanded;
menu.classList[expanded ? 'add' : 'remove']('show');
menu.style.marginTop = (toggler.offsetHeight + @shadowOffset) + 'px';
});
});
</script>