From 652b973426cbd1e8c41c29708f7c6263ba686bca Mon Sep 17 00:00:00 2001 From: Florian Pichler Date: Fri, 10 Dec 2021 15:31:22 +0100 Subject: [PATCH 1/6] Breaking: Update container component - simplify component class - move @z-index@ handling to CSS custom property Replaces #272 --- addon/components/notification-container.js | 13 ---------- .../components/notification-container.css | 26 +++++++++++-------- .../components/notification-container.hbs | 3 +-- tests/dummy/app/templates/application.hbs | 8 +++--- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/addon/components/notification-container.js b/addon/components/notification-container.js index 08aa2099..7825cb7e 100644 --- a/addon/components/notification-container.js +++ b/addon/components/notification-container.js @@ -1,7 +1,5 @@ /* eslint-disable ember/no-classic-components, ember/no-computed-properties-in-native-classes */ import Component from '@ember/component'; -import { computed } from '@ember/object'; -import { htmlSafe } from '@ember/template'; import { inject as service } from '@ember/service'; import layout from '../templates/components/notification-container'; @@ -12,15 +10,4 @@ export default class NotificationContainerComponent extends Component { tagName = ''; layout = layout; position = 'top'; - zindex = '1060'; - - @computed('position') - get positionClass() { - return `ember-cli-notifications-notification__container--${this.position}`; - } - - @computed('zindex') - get inlineStyle() { - return htmlSafe(`z-index: ${this.zindex};`); - } } diff --git a/addon/styles/components/notification-container.css b/addon/styles/components/notification-container.css index f8c38c78..3a3efea0 100644 --- a/addon/styles/components/notification-container.css +++ b/addon/styles/components/notification-container.css @@ -1,5 +1,11 @@ :root { - --ecn-container-position: 10px; + /* Spacing */ + --ecn-spacing-1: 0.5rem; + --ecn-spacing-2: 1rem; + + /* Container */ + --ecn-container-z-index: 9999; + --ecn-container-position: var(--ecn-spacing-1); --ecn-container-width: 80%; --ecn-container-max-with: 400px; @@ -17,51 +23,49 @@ --ecn-orange: #ff7f48; --ecn-red: #e74c3c; - /* Spacing */ - --ecn-spacing-1: .5rem; - --ecn-spacing-2: 1rem; } /* Base */ -.ember-cli-notifications-notification__container { +.ecn_container { position: fixed; margin: 0 auto; width: var(--ecn-container-width); max-width: var(--ecn-container-max-with); + z-index: var(--ecn-container-z-index, 9999); } /* Position */ -.ember-cli-notifications-notification__container--top { +.ecn_container-top { top: var(--ecn-container-position); right: 0; left: 0; } -.ember-cli-notifications-notification__container--top-left { +.ecn_container-top-left { top: var(--ecn-container-position); right: auto; left: var(--ecn-container-position); } -.ember-cli-notifications-notification__container--top-right { +.ecn_container-top-right { top: var(--ecn-container-position); right: var(--ecn-container-position); left: auto; } -.ember-cli-notifications-notification__container--bottom { +.ecn_container-bottom { right: 0; bottom: var(--ecn-container-position); left: 0; } -.ember-cli-notifications-notification__container--bottom-left { +.ecn_container-bottom-left { right: auto; bottom: var(--ecn-container-position); left: var(--ecn-container-position); } -.ember-cli-notifications-notification__container--bottom-right { +.ecn_container-bottom-right { right: var(--ecn-container-position); bottom: var(--ecn-container-position); left: auto; diff --git a/addon/templates/components/notification-container.hbs b/addon/templates/components/notification-container.hbs index 232aa422..33482e58 100644 --- a/addon/templates/components/notification-container.hbs +++ b/addon/templates/components/notification-container.hbs @@ -1,6 +1,5 @@
{{#each this.notifications.content as |notification|}} diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index f34d706e..0dbbf1a1 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -1,5 +1,5 @@ {{! template-lint-disable link-rel-noopener no-action no-unbalanced-curlies require-button-type require-input-label }} - +
@@ -26,9 +26,9 @@

Basic usage


Include the container component in your template.

-

Optionally, change the position or z-index value of the notifications container.

-

Default value is top

- {{notification-container position="top-right" zindex="9999"}} +

Optionally, change the position of the notifications container. The default value is top.

+ {{notification-container position="top-right"}} +

To change the z-index of the container, override the CSS custom property --ecn-container-zindex. The default value is 9999.

Inject the notifications service where required.

import Controller from '@ember/controller'; From 303a9a18da28a4dd61493d4e202682e7c9e23b38 Mon Sep 17 00:00:00 2001 From: Florian Pichler Date: Fri, 10 Dec 2021 15:52:32 +0100 Subject: [PATCH 2/6] Internal: Update prettier configuration --- .prettierrc.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.prettierrc.js b/.prettierrc.js index 534e6d35..bc165960 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -2,4 +2,12 @@ module.exports = { singleQuote: true, + overrides: [ + { + files: '**/*.{hbs,yml,yaml}', + options: { + singleQuote: false, + }, + }, + ], }; From fdc72d3aee24bfd79476e250626746f7215f6bcb Mon Sep 17 00:00:00 2001 From: Florian Pichler Date: Fri, 10 Dec 2021 16:03:59 +0100 Subject: [PATCH 3/6] Refactor: Move keyframes to container styles --- .../components/notification-container.css | 48 +++++++++++++++++++ .../components/notification-message.css | 48 ------------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/addon/styles/components/notification-container.css b/addon/styles/components/notification-container.css index 3a3efea0..11f824ab 100644 --- a/addon/styles/components/notification-container.css +++ b/addon/styles/components/notification-container.css @@ -22,7 +22,55 @@ --ecn-blue: #3ea2ff; --ecn-orange: #ff7f48; --ecn-red: #e74c3c; +} + +/* Keyframes */ +@keyframes notification-show { + 0% { + opacity: 0; + transform: perspective(450px) translate(0, -30px) rotateX(90deg); + } + + 100% { + opacity: 1; + transform: perspective(450px) translate(0, 0) rotateX(0deg); + } +} + +@keyframes notification-out { + 0% { + opacity: 0; + max-height: var(--ecn-notification-max-height); + transform: scale(0.8); + } + + 100% { + opacity: 0; + max-height: 0; + transform: scale(0.8); + } +} + +@keyframes notification-hide { + 0% { + opacity: 1; + transform: scale(1); + } + + 100% { + opacity: 0; + transform: scale(0.8); + } +} + +@keyframes notification-countdown { + 0% { + width: 100%; + } + 100% { + width: 0%; + } } /* Base */ diff --git a/addon/styles/components/notification-message.css b/addon/styles/components/notification-message.css index 2c75ccb5..7ed98371 100644 --- a/addon/styles/components/notification-message.css +++ b/addon/styles/components/notification-message.css @@ -90,51 +90,3 @@ background-color: var(--ecn-red); } -/* Keyframes */ -@keyframes notification-show { - 0% { - opacity: 0; - transform: perspective(450px) translate(0, -30px) rotateX(90deg); - } - - 100% { - opacity: 1; - transform: perspective(450px) translate(0, 0) rotateX(0deg); - } -} - -@keyframes notification-shrink { - 0% { - opacity: 0; - max-height: var(--ecn-notification-max-height); - transform: scale(.8); - } - - 100% { - opacity: 0; - max-height: 0; - transform: scale(.8); - } -} - -@keyframes notification-hide { - 0% { - opacity: 1; - transform: scale(1); - } - - 100% { - opacity: 0; - transform: scale(.8); - } -} - -@keyframes notification-countdown { - 0% { - width: 100%; - } - - 100% { - width: 0%; - } -} From 5d0b6010fcf7bda487511334c9d66020801b575d Mon Sep 17 00:00:00 2001 From: Florian Pichler Date: Wed, 15 Dec 2021 13:02:33 +0100 Subject: [PATCH 4/6] Refactor: Handle notififaction htmlContent in backing class --- addon/components/notification-message.js | 6 ++++++ addon/templates/components/notification-message.hbs | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/addon/components/notification-message.js b/addon/components/notification-message.js index d398c4eb..bec73205 100644 --- a/addon/components/notification-message.js +++ b/addon/components/notification-message.js @@ -15,6 +15,12 @@ export default class NotificationMessage extends Component { paused = false; + @computed('notification.{htmlContent,message}') + get message() { + const { htmlContent, message } = this.notification; + return htmlContent ? htmlSafe(message) : message; + } + @computed('notification.dismiss') get dismissClass() { return !this.notification.dismiss ? 'c-notification--in' : ''; diff --git a/addon/templates/components/notification-message.hbs b/addon/templates/components/notification-message.hbs index e5f7a36a..c7d4371a 100644 --- a/addon/templates/components/notification-message.hbs +++ b/addon/templates/components/notification-message.hbs @@ -23,11 +23,7 @@ {{/if}}
- {{#if @notification.htmlContent}} - {{{@notification.message}}} - {{else}} - {{@notification.message}} - {{/if}} + {{this.message}}
Date: Wed, 15 Dec 2021 13:09:22 +0100 Subject: [PATCH 5/6] Redesign: Improved notification message (Potentially breaking) * Better variables * Slightly more readable colors * Support `prefers-reduced-motion` and reduces motion in Ember tests by default * Added proper ARIA role to notification * Partially replaces #250 --- addon/components/notification-message.js | 17 ++- .../components/notification-container.css | 46 ++++++- .../components/notification-message.css | 121 ++++++++++-------- .../components/notification-message.hbs | 59 +++++---- .../components/notification-message-test.js | 4 +- 5 files changed, 155 insertions(+), 92 deletions(-) diff --git a/addon/components/notification-message.js b/addon/components/notification-message.js index bec73205..abd74991 100644 --- a/addon/components/notification-message.js +++ b/addon/components/notification-message.js @@ -23,7 +23,9 @@ export default class NotificationMessage extends Component { @computed('notification.dismiss') get dismissClass() { - return !this.notification.dismiss ? 'c-notification--in' : ''; + return this.notification.dismiss + ? 'ecn_notification-out' + : 'ecn_notification-in'; } @computed('notification.onClick') @@ -78,6 +80,7 @@ export default class NotificationMessage extends Component { @action handleOnClick(event) { event.preventDefault(); + this.notification.onClick?.(this.notification); } @@ -90,17 +93,21 @@ export default class NotificationMessage extends Component { @action handleMouseEnter() { - if (this.notification.autoClear) { + let notification = this.notification; + + if (notification.autoClear) { set(this, 'paused', true); - this.notifications.pauseAutoClear(this.notification); + this.notifications.pauseAutoClear(notification); } } @action handleMouseLeave() { - if (this.notification.autoClear) { + let notification = this.notification; + + if (notification.autoClear) { set(this, 'paused', false); - this.notifications.setupAutoClear(this.notification); + this.notifications.setupAutoClear(notification); } } } diff --git a/addon/styles/components/notification-container.css b/addon/styles/components/notification-container.css index 11f824ab..e3dbfb06 100644 --- a/addon/styles/components/notification-container.css +++ b/addon/styles/components/notification-container.css @@ -9,19 +9,53 @@ --ecn-container-width: 80%; --ecn-container-max-with: 400px; + /* Notifications */ --ecn-icon-width: 30px; --ecn-icon-position: 10px; - --ecn-icon-color: rgba(255, 255, 255, 0.74); + --ecn-icon-color: inherit; + --ecn-icon-opacity: 0.74; --ecn-icon-lighten-background: rgba(255, 255, 255, 0.2); --ecn-countdown-lighten-background: rgba(255, 255, 255, 0.4); --ecn-notification-max-height: 800px; - --ecn-notification-border-radius: 3px; + --ecn-notification-border-radius: 4px; /* Colours */ - --ecn-green: #64ce83; - --ecn-blue: #3ea2ff; - --ecn-orange: #ff7f48; - --ecn-red: #e74c3c; + --ecn-green: #22c55e; + --ecn-blue: #0ea5e9; + --ecn-orange: #f97316; + --ecn-red: #ef4444; + --ecn-white: #fff; + + /* Types */ + --ecn-success-background: var(--ecn-green); + --ecn-info-background: var(--ecn-blue); + --ecn-warning-background: var(--ecn-orange); + --ecn-error-background: var(--ecn-red); + --ecn-success-text: var(--ecn-white); + --ecn-info-text: var(--ecn-white); + --ecn-warning-text: var(--ecn-white); + --ecn-error-text: var(--ecn-white); + + /* Animation */ + --ecn-animation-in: notification-show 180ms 0ms + cubic-bezier(0.175, 0.885, 0.32, 1.27499); + --ecn-animation-out: notification-hide 250ms + cubic-bezier(0.33859, -0.42, 1, -0.22), + notification-out 250ms 250ms cubic-bezier(0.5, 0, 0, 1); +} + +/* Prefers reduced motion */ +@media (prefers-reduced-motion: reduce) { + :root { + --ecn-animation-in: notification-show 0.001ms; + --ecn-animation-out: notification-out 0.001ms; + } +} + +/* Apply reduced motion in tests */ +#ember-testing { + --ecn-animation-in: notification-show 0.001ms; + --ecn-animation-out: notification-out 0.001ms; } /* Keyframes */ diff --git a/addon/styles/components/notification-message.css b/addon/styles/components/notification-message.css index 7ed98371..a2eeeef0 100644 --- a/addon/styles/components/notification-message.css +++ b/addon/styles/components/notification-message.css @@ -1,69 +1,102 @@ /* Values */ -.ember-cli-notifications-notification__container .c-notification { - display: flex; - align-items: stretch; +.ecn_notification { position: relative; - overflow: hidden; - border-radius: var(--ecn-notification-border-radius); - border-bottom: 1rem; - color: white; max-height: var(--ecn-notification-max-height); - animation: notification-hide 250ms cubic-bezier(.33859, -.42, 1, -.22), notification-shrink 250ms 250ms cubic-bezier(.5, 0, 0, 1); + color: white; + border-radius: var(--ecn-notification-border-radius); + box-shadow: 0 3px 12px -4px rgba(20,20,20,0.5), 0 2px 2px 0 rgba(20,20,20,0.08); + font-size: 1em; + line-height: 1.5em; + animation: var(--ecn-animation-out); animation-fill-mode: forwards; - margin-bottom: var(--ecn-spacing-2); } -.ember-cli-notifications-notification__container .c-notification--clickable { - cursor: pointer; +.ecn_notification + .ecn_notification { + margin-top: var(--ecn-spacing-2); +} + +.ecn_notification-info { + background-color: var(--ecn-info-background); + color: var(--ecn-info-text); +} + +.ecn_notification-success { + background-color: var(--ecn-success-background); + color: var(--ecn-success-text); +} + +.ecn_notification-warning { + background-color: var(--ecn-warning-background); + color: var(--ecn-warning-text); +} + +.ecn_notification-error { + background-color: var(--ecn-error-background); + color: var(--ecn-error-text); } -.ember-cli-notifications-notification__container .c-notification--in { - animation: notification-show 180ms cubic-bezier(.175, .885, .32, 1.27499); +.ecn_notification.ecn_notification-in { + animation: var(--ecn-animation-in); } -.ember-cli-notifications-notification__container .c-notification__content { +.ecn_notification_body { display: flex; + align-items: stretch; + justify-content: stretch; + overflow: hidden; +} + +.ecn_content { flex: 1 1 auto; - min-width: 0; - min-height: 0; - justify-content: space-between; padding: var(--ecn-spacing-1) var(--ecn-spacing-2); word-break: break-word; + vertical-align: middle; } -.ember-cli-notifications-notification__container .c-notification__content a { - color: #fff; +.ecn_content a { + color: currentColor; text-decoration: underline; + font-weight: 700; } -.ember-cli-notifications-notification__container .c-notification__icon { - padding: var(--ecn-spacing-1) 0; - text-align: center; +.ecn_icon { + display: flex; + flex-flow: column; flex: none; + padding: var(--ecn-spacing-1); + opacity: var(--ecn-icon-opacity, 1); +} + +.ecn_icon-type { background-color: var(--ecn-icon-lighten-background); - width: var(--ecn-icon-width); - color: var(--ecn-icon-color); + color: var(--ecn-icon-color, inherit); } -.ember-cli-notifications-notification__container .c-notification__svg { - width: 16px; - height: 16px; - vertical-align: text-top; +.ecn_icon svg { + display: block; + height: 1.5em; } -.ember-cli-notifications-notification__container .c-notification__close { - margin-left: var(--ecn-spacing-2); - align-self: flex-start; - opacity: .74; +.ecn_icon-close { + margin: 0 0 0 var(--ecn-spacing-2); + padding: var(--ecn-spacing-1); + background: transparent; + color: inherit; + border: 0; + border-radius: 0; + font-size: inherit; + line-height: inherit; + appearance: none; cursor: pointer; + transition: 0.2s opacity ease; } -.ember-cli-notifications-notification__container .c-notification__close:hover, -.ember-cli-notifications-notification__container .c-notification__close:focus { +.ecn_icon-close:hover, +.ecn_icon-close:focus { opacity: 1; } -.ember-cli-notifications-notification__container .c-notification__countdown { +.ecn_countdown { position: absolute; bottom: 0; left: 0; @@ -72,21 +105,3 @@ height: 4px; animation: notification-countdown linear 1; } - -/* Theme */ -.ember-cli-notifications-notification__container .c-notification--info { - background-color: var(--ecn-blue); -} - -.ember-cli-notifications-notification__container .c-notification--success { - background-color: var(--ecn-green); -} - -.ember-cli-notifications-notification__container .c-notification--warning { - background-color: var(--ecn-orange); -} - -.ember-cli-notifications-notification__container .c-notification--error { - background-color: var(--ecn-red); -} - diff --git a/addon/templates/components/notification-message.hbs b/addon/templates/components/notification-message.hbs index c7d4371a..0068bc80 100644 --- a/addon/templates/components/notification-message.hbs +++ b/addon/templates/components/notification-message.hbs @@ -1,42 +1,49 @@ -{{! template-lint-disable no-invalid-interactive no-triple-curlies }} +{{! template-lint-disable no-invalid-interactive }} \ No newline at end of file diff --git a/tests/integration/components/notification-message-test.js b/tests/integration/components/notification-message-test.js index 5d547b15..d5a8a42c 100644 --- a/tests/integration/components/notification-message-test.js +++ b/tests/integration/components/notification-message-test.js @@ -21,7 +21,7 @@ module('Integration | Component | notification message', function (hooks) { await settled(); - await click('.c-notification__content'); + await click('[data-test-notification-content][role="button"]'); }); test('clicking the notification close button does not call the callback defined on the notification message', async function (assert) { @@ -37,7 +37,7 @@ module('Integration | Component | notification message', function (hooks) { }); await settled(); - await click('.c-notification__close'); + await click('[data-test-notification-close]'); await settled(); }); }); From b8196dcaee7a9c66053fafe65899e9b873470275 Mon Sep 17 00:00:00 2001 From: Florian Pichler Date: Wed, 15 Dec 2021 13:09:36 +0100 Subject: [PATCH 6/6] Refactor: Use `animationend` event to remove notification Much more stable than setTimeout and no longer blocks tests --- addon/components/notification-message.js | 16 +++++++++++++++- addon/services/notifications.js | 19 +++++++------------ .../components/notification-message.hbs | 1 + 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/addon/components/notification-message.js b/addon/components/notification-message.js index abd74991..27e911f3 100644 --- a/addon/components/notification-message.js +++ b/addon/components/notification-message.js @@ -88,7 +88,8 @@ export default class NotificationMessage extends Component { removeNotification(event) { event.preventDefault(); event.stopPropagation(); - this.notifications.removeNotification(this.notification); + + this.notifications.dismissNotification(this.notification); } @action @@ -110,4 +111,17 @@ export default class NotificationMessage extends Component { this.notifications.setupAutoClear(notification); } } + + @action + handleAnimationEnd({ animationName }) { + let notification = this.notification; + + if (!notification.dismiss) { + return; + } + + if (animationName.endsWith('-out')) { + this.notifications.removeNotification(notification); + } + } } diff --git a/addon/services/notifications.js b/addon/services/notifications.js index 2027925e..fc3793bb 100644 --- a/addon/services/notifications.js +++ b/addon/services/notifications.js @@ -71,21 +71,16 @@ export default class NotificationsService extends Service { }); } + dismissNotification(notification) { + set(notification, 'dismiss', true); + } + removeNotification(notification) { if (!notification) { return; } - notification.set('dismiss', true); - - // Delay removal from DOM for dismissal animation - later( - this, - () => { - this.content.removeObject(notification); - }, - 500 - ); + this.content.removeObject(notification); } setupAutoClear(notification) { @@ -96,7 +91,7 @@ export default class NotificationsService extends Service { () => { // Hasn't been closed manually if (this.content.indexOf(notification) >= 0) { - this.removeNotification(notification); + this.dismissNotification(notification); } }, notification.remaining @@ -116,7 +111,7 @@ export default class NotificationsService extends Service { clearAll() { this.content.forEach((notification) => { - this.removeNotification(notification); + this.dismissNotification(notification); }); return this; diff --git a/addon/templates/components/notification-message.hbs b/addon/templates/components/notification-message.hbs index 0068bc80..81e04db1 100644 --- a/addon/templates/components/notification-message.hbs +++ b/addon/templates/components/notification-message.hbs @@ -7,6 +7,7 @@ {{@notification.cssClasses}}" data-test-notification-message={{@notification.type}} role="alert" + {{on "animationend" this.handleAnimationEnd}} {{on "mouseenter" this.handleMouseEnter}} {{on "mouseleave" this.handleMouseLeave}} >