From 511a88ee7267996cec73fa177d75610a63856266 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 23 Aug 2024 15:05:38 +0800 Subject: [PATCH] feat(WinBox): add ShowModal method (#4132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加 ShowModal 方法 * style: 更新样式名称 * feat: 增加级联参数 * feat: 增加关闭弹窗按钮 * chore: 增加字典 * style: 增加内置样式 * feat: 增加 Stack 方法 * feat: 增加最大化最小化恢复窗口方法 * doc: 更新示例 * feat: 增加 Overflow 参数 * style: 增加不换行样式 * feat: 增加设置标题图标方法 * doc: 更新文档 * doc: 更新示例增加功能按钮 * feat: 更正OnShow 回调 * refactor: 删除冗余变量 * doc: 增加 Modal 示例 * refactor: 增加 modal 样式 * test: 更新 DateTimePicker 单元测试 * test: 增加 Root 单元测试 * test: 更新 Button 单元测试 * Revert "test: 更新 DateTimePicker 单元测试" This reverts commit b6a9875ef6cecd4af690ba148f894e60a6e9d64b. * Revert "test: 更新 Button 单元测试" This reverts commit b82fe2a8f4ca6e2a908f8f542c3b37cfffc2863c. * chore: bump version 8.0.0-beta04 * chore: bump version 8.0.0-beta04 --- exclusion.dic | 1 + .../BootstrapBlazor.Server.csproj | 2 +- .../Components/CustomWinBoxContent.razor | 13 ++++ .../Components/CustomWinBoxContent.razor.cs | 64 +++++++++++++++++ .../Components/Samples/WinBoxes.razor | 69 +++++++++++++++--- .../Components/Samples/WinBoxes.razor.cs | 16 ++++- .../BootstrapBlazor.WinBox.csproj | 2 +- .../Extensions/WinBoxServiceExtensions.cs | 27 +++++++ .../BootstrapBlazor.WinBox/WinBox.razor | 20 +++--- .../BootstrapBlazor.WinBox/WinBox.razor.cs | 40 +++++++++-- .../BootstrapBlazor.WinBox/WinBox.razor.js | 70 +++++++++++++------ .../WinBoxCloseButton.cs | 55 +++++++++++++++ .../BootstrapBlazor.WinBox/WinBoxOption.cs | 10 ++- .../BootstrapBlazor.WinBox/WinBoxService.cs | 52 ++++++++++++-- .../wwwroot/css/winbox.bundle.css | 30 +++++++- .../Components/BootstrapBlazorRootTest.cs | 30 +++++++- 16 files changed, 441 insertions(+), 60 deletions(-) create mode 100644 src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor create mode 100644 src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor.cs create mode 100644 src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxCloseButton.cs diff --git a/exclusion.dic b/exclusion.dic index 8b9fbf09937..0228ea2be3f 100644 --- a/exclusion.dic +++ b/exclusion.dic @@ -93,3 +93,4 @@ webp Pdfi simhei Hmmss +winbox diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index 8bb7f4303a1..a5023905d18 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -66,7 +66,7 @@ - + diff --git a/src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor b/src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor new file mode 100644 index 00000000000..9e8bc9c1eb5 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor @@ -0,0 +1,13 @@ +
+

Custom WinBox Content Create @DateTime.Now

+
+ + diff --git a/src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor.cs b/src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor.cs new file mode 100644 index 00000000000..5393bfffeff --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Components/CustomWinBoxContent.razor.cs @@ -0,0 +1,64 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace BootstrapBlazor.Server.Components.Components; + +/// +/// CustomWinBoxContent 组件 +/// +public partial class CustomWinBoxContent +{ + [Inject, NotNull] + private WinBoxService? WinBoxService { get; set; } + + /// + /// WinBoxOption 实例 + /// + [Parameter, NotNull] + public WinBoxOption? Option { get; set; } + + private Task StackWinBox() => WinBoxService.Stack(); + + private async Task MinWinBox() + { + if (Option != null) + { + await WinBoxService.Minimize(Option); + } + } + + private async Task MaxWinBox() + { + if (Option != null) + { + await WinBoxService.Maximize(Option); + } + } + + private async Task RestoreWinBox() + { + if (Option != null) + { + await WinBoxService.Restore(Option); + } + } + + private async Task SetIconWinBox() + { + if (Option != null) + { + Option.Icon = "./images/Argo-C.png"; + await WinBoxService.SetIcon(Option); + } + } + + private async Task SetTitleWinBox() + { + if (Option != null) + { + Option.Title = $"{DateTime.Now}"; + await WinBoxService.SetTitle(Option); + } + } +} diff --git a/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor b/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor index 8fbf249dc97..e74e6aefbb5 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor @@ -27,36 +27,89 @@ private WinBoxService? WinBoxService { get; set; } - + +

常见问题

- -

通过 WinBoxOption 参数 Background 设置

+

通过 WinBoxOption 参数 Background 设置

new WinBoxOption() { Background = "#000" }
-

通过 WinBoxOption 参数 Class 设置

+

通过 WinBoxOption 参数 Class 设置

new WinBoxOption() { Class = ".bb-win-box" }
.bb-win-box {
     background: #000;
 }
-

通过样式变量实现

+

通过样式变量实现

.winbox {
-    --bb-winbox-bg: var(--bb-primary-color) !important;
+    --bb-winbox-bg: var(--bb-primary-color);
 }
-

通过 WinBoxOption 参数 Border 设置

+

通过 WinBoxOption 参数 Border 设置

new WinBoxOption() { Border = 3 }
+ +

通过 WinBoxOption 参数 Icon 设置

+
new WinBoxOption() { Icon = "./images/Argo-C.png" }
+ +

通过服务方法 WinBoxService 实例方法

+
[Inject]
+[NotNull]
+private WinBoxService? WinBoxService { get; set; }
+
+private async Task SetIcon()
+{
+    // 提前保持弹窗 WinBoxOption 实例
+    option.Icon = "./images/Argo-C.png";
+    await WinBoxService.setIcon(option.Id);
+}
+
+ -

通过 WinBoxOption 参数 Modal 设置

+

通过 WinBoxOption 参数 Modal 设置

new WinBoxOption() { Modal = true }
+ +

通过扩展方法 WinBoxService.ShowModal

+
WinBoxService.ShowModal<Count>("Title")
+
+ + +

通过 WinBoxCloseButton 组件

+
<WinBoxCloseButton />
+ +

通过服务方法 WinBoxService 实例方法

+ +

弹窗内: 使用级联参数 OnCloseAsync WinBoxOption 关闭弹窗

+
[CascadingParameter]
+private Func<WinBoxOption, Task>? OnCloseAsync { get; set; }
+
+[CascadingParameter]
+private WinBoxOption? Option { get; set; }
+
+private async Task Close()
+{
+    if (OnCloseAsync != null && Option != null)
+    {
+        await OnCloseAsync(Option);
+    }
+}
+ +

弹窗外: 使用 WinBoxService 服务实例方法 CloseAsync 关闭弹窗

+
[Inject]
+[NotNull]
+private WinBoxService? WinBoxService { get; set; }
+
+private async Task Close()
+{
+    // 提前保持弹窗 WinBoxOption 实例
+    await WinBoxService.CloseAsync(option.Id);
+}

更多参数信息可参考 WinBoxOption 注释

diff --git a/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor.cs index f79b0025b46..edc510620c4 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/WinBoxes.razor.cs @@ -22,9 +22,16 @@ public partial class WinBoxes Background = "var(--bb-primary-color)" }; - private async Task OpenWinBox() + private Task OpenModalWinBox() { var option = DefaultOptions; + option.Modal = true; + return OpenWinBox(option); + } + + private async Task OpenWinBox(WinBoxOption? option) + { + option ??= DefaultOptions; option.OnCloseAsync = () => { _logger.Log($"WinBoxOption({option.Id}) Trigger OnCloseAsync"); @@ -35,7 +42,7 @@ private async Task OpenWinBox() _logger.Log($"WinBoxOption({option.Id}) Trigger OnCreateAsync"); return Task.CompletedTask; }; - option.OnShownAsync = () => + option.OnShowAsync = () => { _logger.Log($"WinBoxOption({option.Id}) Trigger OnShownAsync"); return Task.CompletedTask; @@ -75,7 +82,10 @@ private async Task OpenWinBox() _logger.Log($"WinBoxOption({option.Id}) Trigger OnMinimizeAsync"); return Task.CompletedTask; }; - await WinBoxService.Show("Test", option: option); + + await WinBoxService.Show("Test", new Dictionary() { + { "Option", option } + }, option); } private AttributeItem[] GetAttributes() => diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/BootstrapBlazor.WinBox.csproj b/src/Extensions/Components/BootstrapBlazor.WinBox/BootstrapBlazor.WinBox.csproj index 5c5534b73a9..58183437f85 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/BootstrapBlazor.WinBox.csproj +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/BootstrapBlazor.WinBox.csproj @@ -1,7 +1,7 @@ - 8.0.0-beta03 + 8.0.0-beta04 diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/Extensions/WinBoxServiceExtensions.cs b/src/Extensions/Components/BootstrapBlazor.WinBox/Extensions/WinBoxServiceExtensions.cs index a7ddcecb7b0..7582ced64aa 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/Extensions/WinBoxServiceExtensions.cs +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/Extensions/WinBoxServiceExtensions.cs @@ -36,4 +36,31 @@ public static async Task Show(this WinBoxService service, string title, Type typ option.ContentTemplate = BootstrapDynamicComponent.CreateComponent(type, parameters).Render(); await service.Show(option); } + + /// + /// 扩展方法 + /// + /// 内容组件 + /// 实例 + /// 指定窗口标题 + /// 指定组件参数集合 + /// 额外参数集合 + public static Task ShowModal(this WinBoxService service, string title, IDictionary? parameters = null, WinBoxOption? option = null) where TComponent : ComponentBase => ShowModal(service, title, typeof(TComponent), parameters, option); + + /// + /// 扩展方法 + /// + /// 实例 + /// 指定窗口标题 + /// 指定内容组件类型 + /// 指定组件参数集合 + /// 额外参数集合 + public static async Task ShowModal(this WinBoxService service, string title, Type type, IDictionary? parameters = null, WinBoxOption? option = null) + { + option ??= new WinBoxOption(); + option.Title = title; + option.Modal = true; + option.ContentTemplate = BootstrapDynamicComponent.CreateComponent(type, parameters).Render(); + await service.Show(option); + } } diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor index b887126f37d..63fd16f617e 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor @@ -2,11 +2,15 @@ @inherits BootstrapModuleComponentBase @attribute [JSModuleAutoLoader("./_content/BootstrapBlazor.WinBox/WinBox.razor.js", JSObjectReference = true)] -@foreach (var item in _cache.Values) -{ - -} + + @foreach (var (key, item) in _cache) + { + + } + diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.cs b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.cs index 3d676f77e6c..3a8b9669bb3 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.cs +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.cs @@ -31,7 +31,7 @@ protected override void OnInitialized() base.OnInitialized(); // 注册 Dialog 弹窗事件 - WinBoxService.Register(this, Show); + WinBoxService.Register(this, Execute); } /// @@ -50,6 +50,33 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } } + private async Task Execute(WinBoxOption? option, string method) + { + if (method == "stack") + { + await InvokeVoidAsync("stack"); + } + else if (option != null) + { + if (method == "show") + { + await Show(option); + } + else if (method == "setIcon") + { + await InvokeVoidAsync("execute", option.Id, method, option.Icon); + } + else if (method == "setTitle") + { + await InvokeVoidAsync("execute", option.Id, method, option.Title); + } + else + { + await InvokeVoidAsync("execute", option.Id, method); + } + } + } + private bool _render; private async Task Show(WinBoxOption option) { @@ -69,6 +96,11 @@ private async Task Show(WinBoxOption option) } } + private async Task CloseAsync(WinBoxOption option) + { + await InvokeVoidAsync("execute", option.Id, "close"); + } + /// /// 弹窗关闭回调方法由 JavaScript 调用 /// @@ -103,11 +135,11 @@ public async Task OnCreate(string id) /// /// [JSInvokable] - public async Task OnShown(string id) + public async Task OnShow(string id) { - if (_cache.TryGetValue(id, out var option) && option.OnShownAsync != null) + if (_cache.TryGetValue(id, out var option) && option.OnShowAsync != null) { - await option.OnShownAsync(); + await option.OnShowAsync(); } } diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.js b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.js index 3e5a42c8011..8df4a6df415 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.js +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBox.razor.js @@ -9,63 +9,87 @@ export async function init(id, invoke, options) { export function show(id, invoke, option) { const el = document.getElementById(id); - const content = el.querySelector('.bb-win-box-content'); + const content = el.querySelector('.bb-winbox-content'); const config = { ...option, title: 'Test', mount: content, onclose: () => { - invoke.invokeMethodAsync("OnClose", option.id); + invoke.invokeMethodAsync("OnClose", config.id); } } - if (option.triggerOnCreate) { + if (config.triggerOnCreate) { + delete config.triggerOnCreate; config.oncreate = () => { - invoke.invokeMethodAsync("OnCreate", option.id); + invoke.invokeMethodAsync("OnCreate", config.id); } } - if (option.triggerOnShown) { - config.onshown = () => { - invoke.invokeMethodAsync("OnShown", option.id); + if (config.triggerOnShow) { + delete config.triggerOnShow; + config.onshow = () => { + invoke.invokeMethodAsync("OnShow", config.id); } } - if (option.triggerOnHide) { + if (config.triggerOnHide) { + delete config.triggerOnHide; config.onhide = () => { - invoke.invokeMethodAsync("OnHide", option.id); + invoke.invokeMethodAsync("OnHide", config.id); } } - if (option.triggerOnFocus) { + if (config.triggerOnFocus) { + delete config.triggerOnFocus; config.onfocus = () => { - invoke.invokeMethodAsync("OnFocus", option.id); + invoke.invokeMethodAsync("OnFocus", config.id); } } - if (option.triggerOnBlur) { + if (config.triggerOnBlur) { + delete config.triggerOnBlur; config.onblur = () => { - invoke.invokeMethodAsync("OnBlur", option.id); + invoke.invokeMethodAsync("OnBlur", config.id); } } - if (option.triggerOnFullscreen) { + if (config.triggerOnFullscreen) { + delete config.triggerOnFullscreen; config.onfullscreen = () => { - invoke.invokeMethodAsync("OnFullscreen", option.id); + invoke.invokeMethodAsync("OnFullscreen", config.id); } } - if (option.triggerOnRestore) { + if (config.triggerOnRestore) { + delete config.triggerOnRestore; config.onrestore = () => { - invoke.invokeMethodAsync("OnRestore", option.id); + invoke.invokeMethodAsync("OnRestore", config.id); } } - if (option.triggerOnMaximize) { + if (config.triggerOnMaximize) { + delete config.triggerOnMaximize; config.onmaximize = () => { - invoke.invokeMethodAsync("OnMaximize", option.id); + invoke.invokeMethodAsync("OnMaximize", config.id); } } - if (option.triggerOnMinimize) { + if (config.triggerOnMinimize) { + delete config.triggerOnMinimize; config.onminimize = () => { - invoke.invokeMethodAsync("OnMinimize", option.id); + invoke.invokeMethodAsync("OnMinimize", config.id); } } - new WinBox(config); + const winbox = new WinBox(config); + Data.set(id, { el, winbox }); } -export function dispose(id) { +export function execute(id, method, args) { + const instance = Data.get(id); + if (instance) { + const { winbox } = instance; + if (winbox) { + winbox[method].call(winbox, args); + } + } +} +export function stack() { + WinBox.stack(); +} + +export function dispose(id) { + Data.remove(id); } diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxCloseButton.cs b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxCloseButton.cs new file mode 100644 index 00000000000..f1c7e5e04d1 --- /dev/null +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxCloseButton.cs @@ -0,0 +1,55 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; + +namespace BootstrapBlazor.Components; + +/// +/// WinBox 组件关闭弹窗按钮组件 +/// +public class WinBoxCloseButton : Button +{ + /// + /// 获得/设置 按钮颜色 + /// + [Parameter] + public override Color Color { get; set; } = Color.Secondary; + + [CascadingParameter] + private Func? OnCloseAsync { get; set; } + + [CascadingParameter] + private WinBoxOption? Option { get; set; } + + [Inject] + [NotNull] + private IStringLocalizer? Localizer { get; set; } + + /// + /// + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + + Icon ??= IconTheme.GetIconByKey(ComponentIcons.DialogCloseButtonIcon); + Text ??= Localizer[nameof(ModalDialog.CloseButtonText)]; + } + + /// + /// + /// + /// + protected override async Task HandlerClick() + { + await base.HandlerClick(); + + if (OnCloseAsync != null && Option != null) + { + await OnCloseAsync(Option); + } + } +} diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxOption.cs b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxOption.cs index 95afd1834cc..1969b89c418 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxOption.cs +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxOption.cs @@ -187,6 +187,12 @@ public class WinBoxOption [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Html { get; set; } + /// + /// Allow the window to move outside the viewport borders on left, right and bottom (default is "false"). + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Overflow { get; set; } + /// /// 获得/设置 子组件模板 默认 null /// @@ -206,10 +212,10 @@ public class WinBoxOption /// 获得/设置 弹窗可见回调方法 默认 null /// [JsonIgnore] - public Func? OnShownAsync { get; set; } + public Func? OnShowAsync { get; set; } [JsonInclude] - private bool TriggerOnShown => OnShownAsync != null; + private bool TriggerOnShow => OnShowAsync != null; /// /// 获得/设置 隐藏弹窗回调方法 默认 null diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxService.cs b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxService.cs index 168a8ea08c7..6d722edde3b 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxService.cs +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/WinBoxService.cs @@ -14,15 +14,16 @@ public class WinBoxService /// /// 获得 回调委托缓存集合 /// - protected List<(ComponentBase Key, Func Callback)> Cache { get; } = []; + protected List<(ComponentBase Key, Func Callback)> Cache { get; } = []; /// /// 异步回调方法 /// /// + /// /// /// - protected async Task Invoke(WinBoxOption option, ComponentBase? component = null) + protected async Task Invoke(WinBoxOption? option, string method, ComponentBase? component = null) { var (Key, Callback) = component != null ? Cache.FirstOrDefault(k => k.Key == component) @@ -31,22 +32,63 @@ protected async Task Invoke(WinBoxOption option, ComponentBase? component = null { throw new InvalidOperationException($"{GetType().Name} not registered. Please add "); } - await Callback.Invoke(option); + await Callback.Invoke(option, method); } /// /// 显示 WinBox 方法 /// /// 弹窗配置信息实体类 + public Task Show(WinBoxOption option) => Invoke(option, "show"); + + /// + /// 关闭 WinBox 方法 + /// + /// 弹窗配置信息实体类 + public Task Close(WinBoxOption option) => Invoke(option, "close"); + + /// + /// 最小化方法 + /// + /// + public Task Minimize(WinBoxOption option) => Invoke(option, "minimize"); + + /// + /// 最大化方法 + /// + /// + public Task Maximize(WinBoxOption option) => Invoke(option, "maximize"); + + /// + /// 最大化方法 + /// + /// + public Task Restore(WinBoxOption option) => Invoke(option, "restore"); + + /// + /// 设置图标方法 + /// + /// + public Task SetIcon(WinBoxOption option) => Invoke(option, "setIcon"); + + /// + /// 设置标题方法 + /// + /// + public Task SetTitle(WinBoxOption option) => Invoke(option, "setTitle"); + + /// + /// 窗口排列方法 + /// /// - public Task Show(WinBoxOption option) => Invoke(option); + public Task Stack() => Invoke(null, "stack"); /// /// 注册弹窗事件 /// /// /// - internal void Register(ComponentBase key, Func callback) + internal void Register(ComponentBase key, Func callback) { Cache.Add((key, callback)); } diff --git a/src/Extensions/Components/BootstrapBlazor.WinBox/wwwroot/css/winbox.bundle.css b/src/Extensions/Components/BootstrapBlazor.WinBox/wwwroot/css/winbox.bundle.css index 21e38ffec4e..6af6e8fb9f8 100644 --- a/src/Extensions/Components/BootstrapBlazor.WinBox/wwwroot/css/winbox.bundle.css +++ b/src/Extensions/Components/BootstrapBlazor.WinBox/wwwroot/css/winbox.bundle.css @@ -1,17 +1,41 @@ @import url('./winbox.min.css'); .winbox { - --bb-winbox-body-padding: .5rem; --bb-winbox-bg: #b5b5c3; + --bb-winbox-bg-dark: #383b3f; + --bb-winbox-body-padding: .5rem; --bb-window-border-radius: var(--bs-border-radius) var(--bs-border-radius) 0 0; + --bb-winbox-footer-border-color: var(--bs-border-color); background: var(--bb-winbox-bg); border-radius: var(--bb-window-border-radius); } - .winbox .bb-win-box-content { + .winbox .bb-winbox-content { padding: var(--bb-winbox-body-padding); + height: 100%; + display: flex; + flex-direction: column; + } + + .winbox .bb-winbox-body { + flex-grow: 1; + } + + .winbox .bb-winbox-footer { + white-space: nowrap; + overflow: hidden; + text-align: end; + border-top: var(--bs-border-width) solid var(--bb-winbox-footer-border-color); + padding: var(--bb-winbox-body-padding); + padding-bottom: 0; + margin: 0 calc(0px - var(--bb-winbox-body-padding)); + } + + .winbox.modal { + display: block; + overflow: hidden; } [data-bs-theme='dark'] .winbox { - --bb-winbox-bg: #383b3f; + background: var(--bb-winbox-bg-dark); } diff --git a/test/UnitTest/Components/BootstrapBlazorRootTest.cs b/test/UnitTest/Components/BootstrapBlazorRootTest.cs index 03931500248..7e375c9be3a 100644 --- a/test/UnitTest/Components/BootstrapBlazorRootTest.cs +++ b/test/UnitTest/Components/BootstrapBlazorRootTest.cs @@ -4,13 +4,39 @@ //using HarmonyLib; +using Microsoft.Extensions.DependencyInjection; + namespace UnitTest.Components; -public class BootstrapBlazorRootTest : BootstrapBlazorTestBase +public class BootstrapBlazorRootTest : TestBase { [Fact] public void Render_Ok() { - var cut = Context.RenderComponent(); + var context = new TestContext(); + context.JSInterop.Mode = JSRuntimeMode.Loose; + + var sc = context.Services; + sc.AddBootstrapBlazor(); + sc.ConfigureJsonLocalizationOptions(op => + { + op.IgnoreLocalizerMissing = false; + }); + sc.AddScoped(); + var cut = context.RenderComponent(); + cut.Contains("
"); + } + + class MockGenerator : IRootComponentGenerator + { + /// + /// + /// + /// + /// + public RenderFragment Generator() => builder => + { + builder.AddContent(0, new MarkupString("
")); + }; } }