diff --git a/js/src/stackedNotification.js b/js/src/stackedNotification.js new file mode 100644 index 000000000000..7490abb088a4 --- /dev/null +++ b/js/src/stackedNotification.js @@ -0,0 +1,50 @@ +export class StackedNotificationManager { + constructor() { + this.notifications = []; + this.container = this._createStackedNotificationContainer(); + } + + _createStackedNotificationContainer() { + const container = document.createElement('div'); + container.className = 'stacked-notification-container'; + document.body.appendChild(container); + return container; + } + + createStackedNotification(type, message, autoDismiss = true, dismissTime = 3000) { + const notification = document.createElement('div'); + notification.className = `stacked-notification stacked-notification-${type}`; + notification.innerText = message; + + const closeBtn = document.createElement('button'); + closeBtn.className = 'close-btn'; + closeBtn.innerHTML = '×'; + closeBtn.onclick = () => this.removeStackedNotification(notification); + + notification.appendChild(closeBtn); + this.container.appendChild(notification); + + this.notifications.push(notification); + + if (autoDismiss) { + setTimeout(() => { + if (notification.parentNode) { + this.removeStackedNotification(notification); + } + }, dismissTime); + } + } + + + removeStackedNotification(notification) { + if (notification && notification.parentNode === this.container) { + this.container.removeChild(notification); + this.notifications = this.notifications.filter(n => n !== notification); + } + } + + + clearAllStackedNotifications() { + this.notifications.forEach(notification => this.removeStackedNotification(notification)); + } +} \ No newline at end of file diff --git a/js/tests/unit/stackedNotification.spec.js b/js/tests/unit/stackedNotification.spec.js new file mode 100644 index 000000000000..82d13b146e1b --- /dev/null +++ b/js/tests/unit/stackedNotification.spec.js @@ -0,0 +1,38 @@ +import { StackedNotificationManager } from '../../src/stackedNotification'; + +describe('StackedNotificationManager', () => { + let stackedNotificationManager; + + beforeEach(() => { + stackedNotificationManager = new StackedNotificationManager(); + }); + + afterEach(() => { + stackedNotificationManager.clearAllStackedNotifications(); + }); + + it('should create a stacked notification and add it to the DOM', () => { + stackedNotificationManager.createStackedNotification('success', 'Success message'); + const notifications = document.querySelectorAll('.stacked-notification'); + expect(notifications.length).toBe(1); + expect(notifications[0].classList.contains('stacked-notification-success')).toBe(true); + expect(notifications[0].innerText.includes('Success message')).toBe(true); + }); + + it('should automatically remove the stacked notification after a certain time', (done) => { + stackedNotificationManager.createStackedNotification('error', 'Error message', true, 1000); + setTimeout(() => { + const notifications = document.querySelectorAll('.stacked-notification'); + expect(notifications.length).toBe(0); + done(); + }, 1500); + }); + + it('should allow manual removal of stacked notifications', () => { + stackedNotificationManager.createStackedNotification('warning', 'Warning message'); + const notification = document.querySelector('.stacked-notification'); + notification.querySelector('.close-btn').click(); + const notifications = document.querySelectorAll('.stacked-notification'); + expect(notifications.length).toBe(0); + }); +}); diff --git a/scss/_stackedNotification.scss b/scss/_stackedNotification.scss new file mode 100644 index 000000000000..cf3d19ec6167 --- /dev/null +++ b/scss/_stackedNotification.scss @@ -0,0 +1,41 @@ +.stacked-notification-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 1000; + max-width: 300px; +} + +.stacked-notification { + background-color: #f0f0f0; + border: 1px solid #ccc; + padding: 10px; + margin-bottom: 10px; + border-radius: 5px; + display: flex; + justify-content: space-between; + align-items: center; + + &.stacked-notification-success { + border-color: #28a745; + background-color: #d4edda; + } + + &.stacked-notification-error { + border-color: #dc3545; + background-color: #f8d7da; + } + + &.stacked-notification-warning { + border-color: #ffc107; + background-color: #fff3cd; + } +} + +.close-btn { + background: none; + border: none; + font-size: 16px; + cursor: pointer; + color: #000; +}