replace Bazored.Toast with custom Toast component
This commit is contained in:
63
src/Web/Shared/Components/Toast/IToastService.cs
Normal file
63
src/Web/Shared/Components/Toast/IToastService.cs
Normal 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();
|
||||
}
|
||||
66
src/Web/Shared/Components/Toast/ToastContainer.razor
Normal file
66
src/Web/Shared/Components/Toast/ToastContainer.razor
Normal 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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
34
src/Web/Shared/Components/Toast/ToastContainer.razor.css
Normal file
34
src/Web/Shared/Components/Toast/ToastContainer.razor.css
Normal 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;
|
||||
}
|
||||
}
|
||||
52
src/Web/Shared/Components/Toast/ToastInstance.cs
Normal file
52
src/Web/Shared/Components/Toast/ToastInstance.cs
Normal 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; }
|
||||
}
|
||||
31
src/Web/Shared/Components/Toast/ToastLevel.cs
Normal file
31
src/Web/Shared/Components/Toast/ToastLevel.cs
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user