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 ? `
${title}
` : ''; + + toast.innerHTML = ` +
+ +
+
+ ${closeButton} + ${titleHtml} +

${message}

+
`; + + // Add event listeners + const closeButtonElement = toast.querySelector('.abp-toast-close-button'); + if (closeButtonElement) { + closeButtonElement.addEventListener('click', () => this.remove(toast)); + } + + if (options.tapToDismiss) { + toast.addEventListener('click', () => this.remove(toast)); + } + + return toast; +}; + +// Show a toast with given options +AbpToastService.prototype.show = function(message, title, severity = 'neutral', options = {}) { + const mergedOptions = this.extend({}, this.globalOptions, options); + const id = ++this.lastId; + const toast = this.createToastElement(message, title, severity, mergedOptions); + + // Set data attributes for non-object options + Object.entries(mergedOptions) + .filter(([_, value]) => typeof value !== 'object') + .forEach(([key, value]) => toast.dataset[key] = value); + + toast.dataset.id = id; + this.container.appendChild(toast); + this.toasts.push(toast); + + // Auto remove if not sticky + if (!mergedOptions.sticky) { + setTimeout(() => this.remove(toast), mergedOptions.life); + } + + return id; +}; + +// Remove a toast with animation +AbpToastService.prototype.remove = function(toastElement) { + toastElement.classList.add('toast-removing'); + setTimeout(() => { + if (toastElement.parentNode === this.container) { + this.container.removeChild(toastElement); + } + this.toasts = this.toasts.filter(t => t !== toastElement); + }, 300); // Match animation duration +}; + +// Convenience methods for different severities +AbpToastService.prototype.success = function(message, title, options) { + return this.show(message, title, 'success', options); +}; + +AbpToastService.prototype.error = function(message, title, options) { + return this.show(message, title, 'error', options); +}; + +AbpToastService.prototype.info = function(message, title, options) { + return this.show(message, title, 'info', options); +}; + +AbpToastService.prototype.warning = function(message, title, options) { + return this.show(message, title, 'warning', options); +}; + +// Clear all toasts +AbpToastService.prototype.clear = function(containerKey) { + if (containerKey) { + this.toasts = this.toasts.filter(toast => { + const shouldRemove = toast.dataset.containerKey === containerKey; + if (shouldRemove) { + this.remove(toast); + } + return !shouldRemove; + }); + } else { + this.toasts.forEach(toast => this.remove(toast)); + } +}; + +// Static method to set default options for all instances +AbpToastService.setDefaultOptions = function(options) { + AbpToastService.defaultOptions = this.prototype.extend({}, AbpToastService.defaultOptions, options); +}; + +var abp = abp || {}; +(function () { + var toast; + + function getToast() { + if (!toast) { + toast = new AbpToastService(); + } + return toast; + } + + abp.notify.success = function (message, title, options) { + getToast().success(message, title, options); + }; + + abp.notify.info = function (message, title, options) { + getToast().info(message, title, options); + }; + + abp.notify.warn = function (message, title, options) { + getToast().warning(message, title, options); + }; + + abp.notify.error = function (message, title, options) { + getToast().error(message, title, options); + }; +})(); \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js deleted file mode 100644 index 37a7c40166..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js +++ /dev/null @@ -1,34 +0,0 @@ -var abp = abp || {}; -(function () { - - if (!toastr) { - return; - } - - /* DEFAULTS *************************************************/ - - toastr.options.positionClass = 'toast-bottom-right'; - - /* NOTIFICATION *********************************************/ - - var showNotification = function (type, message, title, options) { - toastr[type](message, title, options); - }; - - abp.notify.success = function (message, title, options) { - showNotification('success', message, title, options); - }; - - abp.notify.info = function (message, title, options) { - showNotification('info', message, title, options); - }; - - abp.notify.warn = function (message, title, options) { - showNotification('warning', message, title, options); - }; - - abp.notify.error = function (message, title, options) { - showNotification('error', message, title, options); - }; - -})(); \ No newline at end of file diff --git a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json index bbd90971f9..fc77c3a467 100644 --- a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json +++ b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json @@ -24,8 +24,7 @@ "@abp/moment": "~9.1.0-rc.1", "@abp/select2": "~9.1.0-rc.1", "@abp/sweetalert2": "~9.1.0-rc.1", - "@abp/timeago": "~9.1.0-rc.1", - "@abp/toastr": "~9.1.0-rc.1" + "@abp/timeago": "~9.1.0-rc.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io",