diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MessageBox/BitMessageBoxService.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MessageBox/BitMessageBoxService.cs index 7b923dda89..558630fbf1 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/MessageBox/BitMessageBoxService.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/MessageBox/BitMessageBoxService.cs @@ -1,23 +1,84 @@ namespace Bit.BlazorUI; /// -/// A wrapper serive around the to enhance showing message boxes. +/// A wrapper service around the and to enhance showing message boxes. +/// It works with either of these services when available and prefers the when both are available. /// -public class BitMessageBoxService(BitModalService modalService) +/// +/// A message box only appears if the modal container of the chosen service is mounted in the layout: +/// for the and +/// for the . Because the two services are usually both registered but a layout may +/// mount only one container, the selection (see ) is driven by which container is actually +/// mounted rather than by which service is registered, so the message box is not silently swallowed. +/// +public class BitMessageBoxService(BitProModalService? proModalService = null, BitModalService? modalService = null) { /// - /// Shows a inside a using the . + /// Shows a inside a using the , + /// otherwise inside a using the . /// + /// + /// Both services are commonly registered together (registering the Extras services adds both), yet whether a + /// message box actually renders depends on which modal container is mounted, not on which service is registered: + /// a modal shown through a service whose container is absent is silently not rendered. To avoid that, the service + /// to use is chosen from container availability (), + /// in this order: + /// + /// the when its is mounted (preferred when both are); + /// otherwise the when its is mounted; + /// otherwise the only registered service — or, when both are registered but neither container is mounted, the + /// preferred (a misconfiguration that renders nothing regardless: mount a container). + /// + /// Container availability reflects live state and is reliable at the moment of a user-triggered show, since the + /// layout (and thus the container) has already rendered by then. + /// + /// Neither nor is registered. public async Task Show(string title, string body) { - BitModalReference modalRef = default!; - Dictionary parameters = new() + if (proModalService is null && modalService is null) { - { nameof(BitMessageBox.Title), title }, - { nameof(BitMessageBox.Body), body }, - { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, () => modalRef.Close()) } - }; + throw new InvalidOperationException("Neither BitProModalService nor BitModalService is available. Register at least one of them to use BitMessageBoxService."); + } - modalRef = await modalService.Show(parameters); + // Choose the service whose container is actually mounted (preferring the pro one), because a modal shown + // through a service without a mounted container is silently not rendered. Only when neither container is + // mounted do we fall back to the preferred registered service. + bool useProModal; + if (proModalService is null) + { + useProModal = false; + } + else if (modalService is null) + { + useProModal = true; + } + else if (modalService.IsContainerAvailable && !proModalService.IsContainerAvailable) + { + // Only the classic container is mounted: route to it so the message box actually renders. + useProModal = false; + } + else + { + // The pro container is mounted, or neither is (nothing renders either way): keep the preferred pro service. + useProModal = true; + } + + // The parameters are built from the modal reference the service hands back, so the OnClose callback + // closes this very modal without a window where the reference isn't assigned yet. + if (useProModal) + { + await proModalService!.Show(modalRef => BuildParameters(title, body, modalRef.Close)); + } + else + { + await modalService!.Show(modalRef => BuildParameters(title, body, modalRef.Close)); + } } + + private Dictionary BuildParameters(string title, string body, Func onClose) => new() + { + { nameof(BitMessageBox.Title), title }, + { nameof(BitMessageBox.Body), body }, + { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, onClose) } + }; } diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalService.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalService.cs index c4306ca6f6..3e5396c9b9 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalService.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalService.cs @@ -1,8 +1,14 @@ -namespace Bit.BlazorUI; +namespace Bit.BlazorUI; /// /// A service to show any content inside a centralized using . /// +/// +/// A must be mounted in the layout for shown modals to render: a non-persistent modal +/// shown while no container is mounted is silently not rendered (see the base type remarks). Use +/// to check whether a container is +/// currently mounted before showing a modal. +/// public class BitProModalService : BitModalServiceBase { protected override BitProModalReference CreateReference(bool persistent) diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalService.cs b/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalService.cs index 4ec34c67e7..acab52b703 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalService.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalService.cs @@ -1,8 +1,14 @@ -namespace Bit.BlazorUI; +namespace Bit.BlazorUI; /// /// A core service to show any content inside a centralized using . /// +/// +/// A must be mounted in the layout for shown modals to render: a non-persistent modal +/// shown while no container is mounted is silently not rendered (see the base type remarks). Use +/// to check whether a container is +/// currently mounted before showing a modal. +/// public class BitModalService : BitModalServiceBase { protected override BitModalReference CreateReference(bool persistent) diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalServiceBase.cs b/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalServiceBase.cs index 4ee8b29001..4cdee1071c 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalServiceBase.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalServiceBase.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace Bit.BlazorUI; @@ -79,6 +79,24 @@ public void RemoveContainer(BitModalContainerBase conta } } + /// + /// Whether a modal container is currently mounted for this service, i.e. whether a Show call right now + /// would actually render its modal. + /// + /// + /// This reflects LIVE state, not registration: the service being registered in DI does not imply a container + /// exists. A container attaches during its own initialization (after its first render) via + /// and detaches on dispose via , so this can be false very early in a render + /// cycle before any container has initialized, and it can flip back to false across a container remount. + /// It is therefore reliable at the moment of a user-triggered Show (the layout has long since rendered), + /// but should not be treated as a permanent guarantee. + ///
+ /// The main use is a caller that can show through more than one modal service (for example a wrapper that prefers + /// one service but can fall back to another) and wants to pick a service whose container is actually mounted, + /// because a non-persistent modal shown while this is false is silently not rendered (see the type remarks). + ///
+ public bool IsContainerAvailable => _container is not null; + /// /// Closes an already opened modal using its reference. /// @@ -147,7 +165,7 @@ public Task Refresh(TReference modalRef) public Task Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( bool persistent = false) where T : IComponent { - return Show(null, null, persistent); + return Show((Dictionary?)null, null, persistent); } /// @@ -174,7 +192,7 @@ public Task Refresh(TReference modalRef) public Task Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( TParameters modalParameters) where T : IComponent { - return Show(null, modalParameters, false); + return Show((Dictionary?)null, modalParameters, false); } /// @@ -183,22 +201,43 @@ public Task Refresh(TReference modalRef) public Task Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( TParameters? modalParameters, bool persistent = false) where T : IComponent { - return Show(null, modalParameters, persistent); + return Show((Dictionary?)null, modalParameters, persistent); } /// /// Shows a new modal with a custom component as its content with custom parameters for the custom component and the modal. /// - public async Task Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( + public Task Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( Dictionary? parameters, TParameters? modalParameters, bool persistent = false) where T : IComponent { + return Show(_ => parameters, modalParameters, persistent); + } + + /// + /// Shows a new modal, building the content component's parameters from a factory that receives the modal reference. + /// Use this overload when a parameter needs the reference itself (e.g. an OnClose callback that closes this + /// very modal): the reference is handed to the factory before the content is rendered, so the callback can never + /// observe an unassigned reference the way it can when the reference is only captured after Show returns. + /// + public async Task Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( + Func?> parametersFactory, + TParameters? modalParameters = null, + bool persistent = false) where T : IComponent + { + ArgumentNullException.ThrowIfNull(parametersFactory); + var componentType = typeof(T); var modalReference = CreateReference(persistent); modalReference.SetParameters(modalParameters); + // Build the content parameters with the reference already in hand so a parameter such as an OnClose + // callback can capture modalReference.Close directly, closing the window a caller would otherwise face + // when wiring the close callback only after Show returns. + var parameters = parametersFactory(modalReference); + var content = new RenderFragment(builder => { var i = 0; diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor index a39946b5bd..d95cd93cab 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor @@ -25,28 +25,55 @@ +
The basic usage of the BitMessageBox, rendered inline inside a BitCard.
+
+
Show the BitMessageBox inside a BitModal component and close it using the OnClose event.
+
Show
- + +
Show the BitMessageBox inside a BitProModal component and close it using the OnClose event.
+
+ Show + + + +
+ + +
Show the BitMessageBox as the content of a modal shown by the BitModalService.
+
Show MessageBox
- + +
Show the BitMessageBox as the content of a pro modal shown by the BitProModalService.
+
+ Show MessageBox +
+ + +
+ Show the BitMessageBox with a simple call to the Show method of the BitMessageBoxService. + This service uses the BitProModalService or the BitModalService (whichever is available) to show the message box, + preferring the BitProModalService when both are available. +
+
Show MessageBox
- -
Customize the appearance of BitActionButton using styles and CSS classes.
+ +
Customize the appearance of BitMessageBox using styles and CSS classes.

@@ -63,7 +90,7 @@
- +
Use BitMessageBox in right-to-left (RTL).


@@ -73,4 +100,4 @@
- \ No newline at end of file + diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.cs index fe7b10e9c4..49fe4e347c 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.cs @@ -133,18 +133,28 @@ public partial class BitMessageBoxDemo private bool isModalOpen; + private bool isProModalOpen; [AutoInject] private BitModalService modalService { get; set; } = default!; private async Task ShowMessageBox() { - BitModalReference modalRef = default!; - Dictionary parameters = new() + await modalService.Show(modalRef => new() { { nameof(BitMessageBox.Title), "This is a title" }, { nameof(BitMessageBox.Body), "This is a body." }, - { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, () => modalRef.Close()) } - }; - modalRef = await modalService.Show(parameters); + { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, modalRef.Close) } + }); + } + + [AutoInject] private BitProModalService proModalService { get; set; } = default!; + private async Task ShowProMessageBox() + { + await proModalService.Show(modalRef => new() + { + { nameof(BitMessageBox.Title), "This is a title" }, + { nameof(BitMessageBox.Body), "This is a body." }, + { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, modalRef.Close) } + }); } [AutoInject] private BitMessageBoxService messageBoxService { get; set; } = default!; diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.samples.cs index 8fe6ec33e1..909a1164ff 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MessageBox/BitMessageBoxDemo.razor.samples.cs @@ -16,33 +16,59 @@ public partial class BitMessageBoxDemo private bool isModalOpen;"; private readonly string example3RazorCode = @" + isProModalOpen = true"">Show + + isProModalOpen = false"" Title=""This is the Title"" Body=""This is the Body!"" /> +"; + private readonly string example3CsharpCode = @" +private bool isProModalOpen;"; + + private readonly string example4RazorCode = @" Show MessageBox "; - private readonly string example3CsharpCode = @" + private readonly string example4CsharpCode = @" [AutoInject] private BitModalService modalService { get; set; } = default!; private async Task ShowMessageBox() { - BitModalReference modalRef = default!; - Dictionary parameters = new() + await modalService.Show(modalRef => new() { { nameof(BitMessageBox.Title), ""This is a title"" }, { nameof(BitMessageBox.Body), ""This is a body."" }, - { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, () => modalRef.Close()) } - }; - modalRef = await modalService.Show(parameters); + { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, modalRef.Close) } + }); }"; - private readonly string example4RazorCode = @" -Show MessageBox"; - private readonly string example4CsharpCode = @" + private readonly string example5RazorCode = @" +Show MessageBox + +"; + private readonly string example5CsharpCode = @" +[AutoInject] private BitProModalService proModalService { get; set; } = default!; +private async Task ShowProMessageBox() +{ + await proModalService.Show(modalRef => new() + { + { nameof(BitMessageBox.Title), ""This is a title"" }, + { nameof(BitMessageBox.Body), ""This is a body."" }, + { nameof(BitMessageBox.OnClose), EventCallback.Factory.Create(this, modalRef.Close) } + }); +}"; + + private readonly string example6RazorCode = @" +Show MessageBox + +@* The service uses the BitProModalService when available, otherwise the BitModalService. *@ +@* Mount the matching container: BitProModalContainer for the pro modal, BitModalContainer otherwise. *@ +"; + private readonly string example6CsharpCode = @" [AutoInject] private BitMessageBoxService messageBoxService { get; set; } = default!; private async Task ShowMessageBoxService() { await messageBoxService.Show(""TITLE"", ""BODY""); }"; - private readonly string example5RazorCode = @" + private readonly string example7RazorCode = @"