replace Bazored.Toast with custom Toast component

This commit is contained in:
Eduardo
2026-08-04 23:00:07 -03:00
committed by Acentech Dev
parent 23bc36fd65
commit 72874567c6
27 changed files with 525 additions and 101 deletions

View File

@@ -0,0 +1,63 @@
// <copyright file="IToastService.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Web.Shared.Components.Toast;
using System;
using System.Collections.Generic;
/// <summary>
/// Service for showing toast notifications.
/// </summary>
public interface IToastService
{
/// <summary>
/// Occurs when the list of toasts has changed (added, closed, cleared).
/// </summary>
event Action? StateChanged;
/// <summary>
/// Gets the currently shown toasts.
/// </summary>
IReadOnlyList<ToastInstance> Toasts { get; }
/// <summary>
/// Shows a success toast.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="heading">The optional heading.</param>
void ShowSuccess(string message, string? heading = null);
/// <summary>
/// Shows an info toast.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="heading">The optional heading.</param>
void ShowInfo(string message, string? heading = null);
/// <summary>
/// Shows a warning toast.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="heading">The optional heading.</param>
void ShowWarning(string message, string? heading = null);
/// <summary>
/// Shows an error toast.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="heading">The optional heading.</param>
void ShowError(string message, string? heading = null);
/// <summary>
/// Closes the specified toast (triggers its closing animation).
/// </summary>
/// <param name="toast">The toast to close.</param>
void Close(ToastInstance toast);
/// <summary>
/// Closes all currently shown toasts.
/// </summary>
void Clear();
}

View File

@@ -0,0 +1,66 @@
@using MUnique.OpenMU.Web.Shared.Components.Toast
@using MUnique.OpenMU.Web.Shared.Services
@inject ToastService ToastService
@implements IDisposable
@if (this.ToastService.Toasts.Count > 0)
{
<div class="toast-container position-fixed top-0 end-0 p-3" aria-live="polite" aria-atomic="true">
@foreach (var toast in this.ToastService.Toasts)
{
var (iconClass, accentClass) = this.GetStyling(toast.Level);
<div @key="toast.Key" class="toast @accentClass @(toast.IsClosing ? "closing" : "show")" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body d-flex align-items-start">
<span class="oi @iconClass toast__icon" aria-hidden="true"></span>
<div class="ms-2 w-100">
@if (toast.Heading is { } heading)
{
<strong class="d-block">@heading</strong>
}
<div>@toast.Message</div>
</div>
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close" @onclick="() => this.CloseAsync(toast)"></button>
</div>
</div>
}
</div>
}
@code {
/// <inheritdoc />
protected override void OnInitialized()
{
this.ToastService.StateChanged += this.OnStateChanged;
}
/// <inheritdoc />
public void Dispose()
{
this.ToastService.StateChanged -= this.OnStateChanged;
}
private void OnStateChanged()
{
_ = this.InvokeAsync(this.StateHasChanged);
}
private Task CloseAsync(ToastInstance toast)
{
this.ToastService.Close(toast);
return Task.CompletedTask;
}
private (string IconClass, string AccentClass) GetStyling(ToastLevel level)
{
return level switch
{
ToastLevel.Success => ("oi-circle-check", "text-success"),
ToastLevel.Info => ("oi-info", "text-primary"),
ToastLevel.Warning => ("oi-warning", "text-warning"),
ToastLevel.Error => ("oi-bolt", "text-danger"),
_ => ("oi-info", "text-primary"),
};
}
}

View File

@@ -0,0 +1,34 @@
.toast-container {
z-index: 1080;
}
.toast {
min-width: 18rem;
border-left: 4px solid currentColor;
background-color: var(--bs-body-bg);
box-shadow: var(--bs-box-shadow-lg);
animation: toast-slide-in 0.2s ease-out;
opacity: 1;
transition: opacity 0.3s ease;
}
.toast.closing {
opacity: 0;
}
.toast__icon {
font-size: 1.1rem;
line-height: 1;
align-self: center;
}
@keyframes toast-slide-in {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

View File

@@ -0,0 +1,52 @@
// <copyright file="ToastInstance.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Web.Shared.Components.Toast;
using System;
/// <summary>
/// Represents a single toast message shown in the <see cref="ToastContainer"/>.
/// </summary>
public sealed class ToastInstance
{
/// <summary>
/// Initializes a new instance of the <see cref="ToastInstance"/> class.
/// </summary>
/// <param name="level">The level.</param>
/// <param name="message">The message.</param>
/// <param name="heading">The optional heading.</param>
internal ToastInstance(ToastLevel level, string message, string? heading)
{
this.Key = Guid.NewGuid();
this.Level = level;
this.Message = message;
this.Heading = heading;
}
/// <summary>
/// Gets a stable key identifying this toast, used as a render key.
/// </summary>
public Guid Key { get; }
/// <summary>
/// Gets the level.
/// </summary>
public ToastLevel Level { get; }
/// <summary>
/// Gets the message.
/// </summary>
public string Message { get; }
/// <summary>
/// Gets the optional heading.
/// </summary>
public string? Heading { get; }
/// <summary>
/// Gets or sets a value indicating whether the toast is performing its closing animation.
/// </summary>
internal bool IsClosing { get; set; }
}

View File

@@ -0,0 +1,31 @@
// <copyright file="ToastLevel.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Web.Shared.Components.Toast;
/// <summary>
/// The level of a toast message.
/// </summary>
public enum ToastLevel
{
/// <summary>
/// Informational message.
/// </summary>
Info,
/// <summary>
/// Success message.
/// </summary>
Success,
/// <summary>
/// Warning message.
/// </summary>
Warning,
/// <summary>
/// Error message.
/// </summary>
Error,
}