diff --git a/addons/rose/addon/components/rose/notification.js b/addons/rose/addon/components/rose/notification.js new file mode 100644 index 000000000..0ff646e2d --- /dev/null +++ b/addons/rose/addon/components/rose/notification.js @@ -0,0 +1,38 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/rose/notification'; +import { computed } from '@ember/object'; + +/** + * An accessible alert notification component that displays an icon, + * a heading, and optional block content. Notifications may be static or + * dismissible. To make a notification dismissible, pass in a + * `@dismiss` function. + */ +export default Component.extend({ + layout, + tagName: '', + + /** + * Returns an icon name based on the notification style. + * @type {string} + */ + @computed('style') + get icon() { + let icon = 'info-circle-fill'; + + switch(this.style) { + case 'failure': + icon = 'cancel-square-fill'; + break; + case 'success': + icon = 'check-circle-fill'; + break; + case 'warning': + icon = 'alert-triangle'; + break; + } + + return icon; + } + +}); diff --git a/addons/rose/addon/components/rose/notification.stories.mdx b/addons/rose/addon/components/rose/notification.stories.mdx new file mode 100644 index 000000000..04cd3f533 --- /dev/null +++ b/addons/rose/addon/components/rose/notification.stories.mdx @@ -0,0 +1,84 @@ +import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@storybook/addon-actions'; +import { text, boolean, select } from '@storybook/addon-knobs'; + + + +# Notification + +Notifications come in four semantic styles: `info` (default), `success`, +`warning`, and `failure`. Heading text is required. Additional block content +is optional. + +Notifications may be made dismissible by passing a `@dismiss` function into +the component. Dismissible notifications _must_ also have `@dismissText`. +This component does not handle dismissal though. Be sure your `@dismiss` +handler takes appropriate action and hides the notification. + +```htmlbars + + An error occurred. + +``` + + + {{ + template: hbs` + + Some useful information. + + `, + context: {} + }} + + + + {{ + template: hbs` + + Some useful information. + + + Good job! + + + Careful now. + + + An error occurred. + + `, + context: { + dismiss: action('dismiss'), + } + }} + diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss index 475417601..e1518b033 100644 --- a/addons/rose/addon/styles/rose/components/_index.scss +++ b/addons/rose/addon/styles/rose/components/_index.scss @@ -1,3 +1,4 @@ @import 'button'; @import 'form'; @import 'header'; +@import 'notification'; diff --git a/addons/rose/addon/styles/rose/components/_notification.scss b/addons/rose/addon/styles/rose/components/_notification.scss new file mode 100644 index 000000000..71de0adf0 --- /dev/null +++ b/addons/rose/addon/styles/rose/components/_notification.scss @@ -0,0 +1,105 @@ +@use '../variables/sizing'; +@use '../utilities/type'; + +.rose-notification { + $s: sizing.rems(s); // 12 + $xs: sizing.rems(xs); // 8 + $xxs: sizing.rems(xxs); // 4 + $xxxs: sizing.rems(xxxs); // 2 + $xxxxs: sizing.rems(xxxxs); // 1 + + position: relative; + padding: sizing.rems(m); + margin-bottom: sizing.rems(m); + border: sizing.rems(xxxxs) solid; + border-radius: $xxxs; + box-shadow: 0 $xs $s rgba(var(--stark-components), var(--opacity-3)), + 0 $xxs $xxs rgba(var(--stark-components), var(--opacity-4)); + + .rose-notification-icon { + position: relative; + top: $xxxxs; + } + + .rose-notification-header { + @include type.type(m, semibold); + } + + .rose-notification-body { + @include type.type(s); + // hack alert, because the icons are still hard to work with + margin-left: sizing.rems(m) + $xxs; + } + + .rose-notification-dismiss { + position: absolute; + color: currentColor; + background-color: transparent; + border-radius: $xxxs; + border: 0; + padding: 0; + cursor: pointer; + + top: sizing.rems(s); + right: sizing.rems(s); + + .rose-icon { + display: block; + padding: $xxs; + } + + .rose-notification-dismiss-text { + position: absolute; + top: -1000px; + left: -1000px; + width: 1px; + height: 1px; + opacity: 0; + } + + &:hover { + background-color: rgba(var(--stark-components), var(--opacity-3)) + } + + &:active { + background-color: rgba(var(--stark-components), var(--opacity-2)) + } + } + + --icon-color: var(--info); + --header-color: var(--info-starker-1); + --border-color: var(--info-subtler-1); + --background-color: var(--info-subtler-3); + + border-color: var(--border-color); + background-color: var(--background-color); + + .rose-notification-icon { + color: var(--icon-color); + } + + .rose-notification-header { + color: var(--header-color); + } + + &.is-failure { + --icon-color: var(--failure); + --header-color: var(--failure); + --border-color: var(--failure-subtler-1); + --background-color: var(--failure-subtler-2); + } + + &.is-success { + --icon-color: var(--success); + --header-color: var(--success-starker-1); + --border-color: var(--success-subtler-1); + --background-color: var(--success-subtler-2); + } + + &.is-warning { + --icon-color: var(--warning); + --header-color: var(--warning-starker-1); + --border-color: var(--warning-subtler-1); + --background-color: var(--warning-subtler-2); + } +} diff --git a/addons/rose/addon/templates/components/rose/notification.hbs b/addons/rose/addon/templates/components/rose/notification.hbs new file mode 100644 index 000000000..04d60df5f --- /dev/null +++ b/addons/rose/addon/templates/components/rose/notification.hbs @@ -0,0 +1,37 @@ + diff --git a/addons/rose/app/components/rose/notification.js b/addons/rose/app/components/rose/notification.js new file mode 100644 index 000000000..228bd468c --- /dev/null +++ b/addons/rose/app/components/rose/notification.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/notification'; \ No newline at end of file diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs index 70c9d1747..12fac556c 100644 --- a/addons/rose/tests/dummy/app/templates/index.hbs +++ b/addons/rose/tests/dummy/app/templates/index.hbs @@ -30,6 +30,15 @@ Dummy app index template + + An error occurred. + +

Icon

diff --git a/addons/rose/tests/integration/components/rose/notification-test.js b/addons/rose/tests/integration/components/rose/notification-test.js new file mode 100644 index 000000000..6027644d0 --- /dev/null +++ b/addons/rose/tests/integration/components/rose/notification-test.js @@ -0,0 +1,33 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find, click } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | rose/notification', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + await render(hbs` + + An error occurred. + + `); + assert.ok(find('.rose-notification')); + assert.equal(find('.rose-notification-body').textContent.trim(), 'An error occurred.'); + }); + + test('it has optional dismiss button which triggers a function', async function(assert) { + assert.expect(1); + this.dismiss = () => assert.ok(true, 'dismiss clicked'); + await render(hbs` + + `); + await click('.rose-notification-dismiss'); + }); +});