diff --git a/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md b/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md index 970d35b851..043f811d49 100644 --- a/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md +++ b/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md @@ -1,6 +1,6 @@ # ASP.NET Core MVC / Razor Pages UI: JavaScript Notify API -Notify API is used to show toast style, auto disappearing UI notifications to the end user. It is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default. +Notify API is used to show toast style, auto disappearing UI notifications to the end user. ## Quick Example @@ -26,20 +26,23 @@ There are four types of pre-defined notifications; * `abp.notify.warn(...)` * `abp.notify.error(...)` -All of the methods above gets the following parameters; +All of the methods above accept the following parameters: * `message`: A message (`string`) to show to the user. * `title`: An optional title (`string`). -* `options`: Additional options to be passed to the underlying library, to the Toastr by default. - -## Toastr Configuration - -The notification API is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default. You can see its own configuration options. - -**Example: Show toast messages on the top right of the page** - -````js -toastr.options.positionClass = 'toast-top-right'; -```` - -> ABP sets this option to `toast-bottom-right` by default. You can override it just as shown above. \ No newline at end of file +* `options`: Additional options to customize the notification. Available options: + * `life`: Display duration in milliseconds (default: `5000`) + * `sticky`: Keep toast visible until manually closed (default: `false`) + * `closable`: Show close button (default: `true`) + * `tapToDismiss`: Click anywhere on toast to dismiss (default: `false`) + * `containerKey`: Key for multiple container support (optional) + * `iconClass`: Custom icon class (optional) + * `position`: Position configuration (optional) + * `top`: Distance from top (default: `'auto'`) + * `right`: Distance from right (default: `'30px'`) + * `bottom`: Distance from bottom (default: `'30px'`) + * `left`: Distance from left (default: `'auto'`) + +## Global Configuration + +`AbpToastService.setDefaultOptions` method can be used to set default options for all notifications. This method should be called before any notification is shown. diff --git a/docs/en/images/js-notify-success.png b/docs/en/images/js-notify-success.png index d5300f3468..8366cb9d5a 100644 Binary files a/docs/en/images/js-notify-success.png and b/docs/en/images/js-notify-success.png differ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrScriptBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrScriptBundleContributor.cs deleted file mode 100644 index 226b076b7a..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrScriptBundleContributor.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; -using Volo.Abp.Modularity; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; - -[DependsOn(typeof(JQueryScriptContributor))] -public class ToastrScriptBundleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/toastr/toastr.min.js"); - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrStyleBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrStyleBundleContributor.cs deleted file mode 100644 index d00f9e3768..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrStyleBundleContributor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; - -public class ToastrStyleBundleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/toastr/toastr.min.css"); - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs index 5d8c15ba40..4bb1d80fef 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs @@ -12,7 +12,6 @@ using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; using Volo.Abp.AspNetCore.Mvc.UI.Packages.SweetAlert2; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; @@ -26,7 +25,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; typeof(Select2ScriptContributor), typeof(DatatablesNetBs5ScriptContributor), typeof(Sweetalert2ScriptContributor), - typeof(ToastrScriptBundleContributor), typeof(MalihuCustomScrollbarPluginScriptBundleContributor), typeof(LuxonScriptContributor), typeof(TimeagoScriptContributor), @@ -48,7 +46,7 @@ public class SharedThemeGlobalScriptContributor : BundleContributor "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/authentication-state/authentication-state-listener.js" }); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs index f959d73f05..4f436a02b0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs @@ -7,7 +7,6 @@ using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs5; using Volo.Abp.AspNetCore.Mvc.UI.Packages.FontAwesome; using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; @@ -16,7 +15,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; typeof(CoreStyleContributor), typeof(BootstrapStyleContributor), typeof(FontAwesomeStyleContributor), - typeof(ToastrStyleBundleContributor), typeof(Select2StyleContributor), typeof(MalihuCustomScrollbarPluginStyleBundleContributor), typeof(DatatablesNetBs5StyleContributor), @@ -30,7 +28,8 @@ public class SharedThemeGlobalStyleContributor : BundleContributor context.Files.AddRange(new BundleFile[] { "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css" + "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css", }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css new file mode 100644 index 0000000000..9840c6fce2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css @@ -0,0 +1,139 @@ +.abp-toast-container { + position: fixed; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + min-width: 350px; + min-height: 80px; + z-index: 1900; + right: 30px; + bottom: 30px; +} + +.abp-toast { + display: grid; + grid-template-columns: 35px 1fr; + gap: 5px; + margin: 5px 0; + padding: 7px; + width: 350px; + user-select: none; + z-index: 9999; + color: #fff; + border-radius: 8px; + font-size: 14px; + box-shadow: 0 0 20px 0 rgba(76, 87, 125, 0.02); + animation: toastIn 0.3s ease-in-out; +} + +.abp-toast-success { + border: 2px solid #4fbf67; + background-color: #4fbf67; +} + +.abp-toast-error { + border: 2px solid #c00d49; + background-color: #c00d49; +} + +.abp-toast-info { + border: 2px solid #438aa7; + background-color: #438aa7; +} + +.abp-toast-warning { + border: 2px solid #ff9f38; + background-color: #ff9f38; +} + +.abp-toast-icon { + display: flex; + align-items: center; + justify-content: center; +} + +.abp-toast-icon .icon { + font-size: 32px; +} + +.abp-toast-content { + position: relative; + display: flex; + align-self: center; + flex-direction: column; + word-break: break-word; + padding-bottom: 2px; +} + +.abp-toast-close-button { + position: absolute; + top: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + margin: 0; + padding: 0px 5px 0 0; + width: 25px; + height: 100%; + border: none; + border-radius: 50%; + background: transparent; + color: inherit; + cursor: pointer; +} + +.abp-toast-close-button:focus { + outline: none; +} + +.abp-toast-title { + margin: 0; + padding: 0; + font-size: 1rem; + font-weight: 600; +} + +.abp-toast-message { + margin: 0; + padding: 0; + max-width: 240px; +} + +@keyframes toastIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes toastOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +.toast-removing { + animation: toastOut 0.3s ease-in-out forwards; +} + +@media only screen and (max-width: 768px) { + .abp-toast-container { + min-width: 100%; + right: 0; + } + + .abp-toast { + width: 95%; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js new file mode 100644 index 0000000000..115335e585 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js @@ -0,0 +1,216 @@ +function AbpToastService(globalOptions) { + // Static default configuration for all instances + if (!AbpToastService.defaultOptions) { + AbpToastService.defaultOptions = { + closable: true, + sticky: false, + life: 5000, + tapToDismiss: false, + containerKey: undefined, + iconClass: undefined, + position: { + top: 'auto', + right: '30px', + bottom: '30px', + left: 'auto' + } + }; + } + + // Find existing container or create new one + const containerId = globalOptions?.containerKey ? + `toast-container-${globalOptions.containerKey}` : + 'toast-container'; + + this.container = document.getElementById(containerId); + if (!this.container) { + this.container = document.createElement('div'); + this.container.id = containerId; + this.container.className = 'abp-toast-container'; + document.body.appendChild(this.container); + } + + this.toasts = []; + this.lastId = 0; + + // Merge user provided global options with defaults + this.globalOptions = this.extend({}, AbpToastService.defaultOptions, globalOptions); + + this.updateContainerPosition(); +} + +// Deep merge objects +AbpToastService.prototype.extend = function(target, ...sources) { + sources.forEach(source => { + for (const key in source) { + if (source[key] && typeof source[key] === 'object') { + target[key] = this.extend(target[key] || {}, source[key]); + } else { + target[key] = source[key]; + } + } + }); + return target; +}; + +// Update toast container position based on global options +AbpToastService.prototype.updateContainerPosition = function() { + const { position } = this.globalOptions; + Object.assign(this.container.style, position); +}; + +// Update global options +AbpToastService.prototype.setGlobalOptions = function(options) { + this.globalOptions = this.extend({}, this.globalOptions, options); + this.updateContainerPosition(); +}; + +// Get icon class based on severity +AbpToastService.prototype.getIconClass = function(severity, options) { + // Use custom icon class if provided + if (options.iconClass) { + return options.iconClass; + } + + const icons = { + success: 'bi-check', + info: 'bi-info-circle', + warning: 'bi-exclamation-triangle', + error: 'bi-shield-exclamation' + }; + return icons[severity] || 'bi-exclamation-triangle'; +}; + +// Create toast DOM element +AbpToastService.prototype.createToastElement = function(message, title, severity, options) { + const toast = document.createElement('div'); + toast.className = `abp-toast abp-toast-${severity}`; + + const closeButton = options.closable !== false ? + `` : ''; + + const titleHtml = title ? `