diff --git a/addons/rose/addon/components/rose/badge.js b/addons/rose/addon/components/rose/badge.js new file mode 100644 index 000000000..8a5cbcec5 --- /dev/null +++ b/addons/rose/addon/components/rose/badge.js @@ -0,0 +1,7 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/rose/badge'; + +export default Component.extend({ + layout, + tagName: '', +}); diff --git a/addons/rose/addon/components/rose/badge.stories.mdx b/addons/rose/addon/components/rose/badge.stories.mdx new file mode 100644 index 000000000..1f91c7beb --- /dev/null +++ b/addons/rose/addon/components/rose/badge.stories.mdx @@ -0,0 +1,75 @@ +import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@storybook/addon-actions'; +import { text, select } from '@storybook/addon-knobs'; + + + +# Badge + +A badge component that supports icons and predefined styles. +When style definition isn't supplied, a default style will be supplied. + +Styles: outline, dark, informational, informational-outline, success, success-outline, error, error-outline, warning, warning-outline, alert, alert-outline + +```html +Badge +``` + + + {{ + template: hbs` + {{text}} + `, + context: { + text: text('text', 'Badge'), + style: select('style', { + 'none': 'none', + 'outline': 'outline', + 'dark': 'dark', + 'informational': 'informational', + 'informational-outline': 'informational-outline', + 'success': 'success', + 'success-outline': 'success-outline', + 'alert': 'alert', + 'alert-outline': 'alert-outline', + 'error': 'error', + 'error-outline': 'error-outline', + 'warning': 'warning', + 'warning-outline': 'warning-outline', + }, + 'none' + ), + icon: select('icon', { + 'user-organization': 'user-organization', + 'partner': 'partner', + 'gift-fill': 'gift-fill', + }, + 'user-organization' + ), + } + }} + + + + {{ + template: hbs` + default style + outline + default style with icon + dark + informational + informational-outline + alert + alert-outline + error + error-outline + success + success-outline + warning + warning-outline + `, + }} + diff --git a/addons/rose/addon/components/rose/dropdown.js b/addons/rose/addon/components/rose/dropdown.js index 76723d8bf..4e6ffea81 100644 --- a/addons/rose/addon/components/rose/dropdown.js +++ b/addons/rose/addon/components/rose/dropdown.js @@ -3,7 +3,6 @@ import layout from '../../templates/components/rose/dropdown'; import { action } from '@ember/object'; export default Component.extend({ - // =attributes layout, @@ -13,14 +12,15 @@ export default Component.extend({ /** * Closes the details element by setting its `open` attribute to `false`. - * While the details element handles open/close functionality without - * JavaScript, we include this helper action to ensure that when the user - * clicks _outside_ of the element, it still closes. + * While the details element handles open/close functionality when clicking + * directly on the summar elemenet without JavaScript, we include this helper + * action to ensure that when the user clicks _outside_ of the element, + * or _inside_ the content, it still closes. * @param {HTMLElement} element */ @action close(element) { if (element.open) element.open = false; + if (element.parentElement.open) element.parentElement.open = false; }, - }); diff --git a/addons/rose/addon/modifiers/on-click-inside.js b/addons/rose/addon/modifiers/on-click-inside.js new file mode 100644 index 000000000..ec64ff5f7 --- /dev/null +++ b/addons/rose/addon/modifiers/on-click-inside.js @@ -0,0 +1,20 @@ +import { modifier } from 'ember-modifier'; + +/** + * Used to detect clicks occuring _inside_ the modified element. + * This is useful especially for dropdowns to toggle content visibility + * when the user clicks an item. + */ +export default modifier((element, [callback]) => { + function handleClick(event) { + if (element.contains(event.target)) { + callback(element, event); + } + } + + element.addEventListener('click', handleClick, false); + + return () => { + element.removeEventListener('click', handleClick, false); + }; +}); diff --git a/addons/rose/addon/styles/rose/components/_badge.scss b/addons/rose/addon/styles/rose/components/_badge.scss new file mode 100644 index 000000000..716c97122 --- /dev/null +++ b/addons/rose/addon/styles/rose/components/_badge.scss @@ -0,0 +1,84 @@ +@use '../utilities/type'; +@use '../variables/sizing'; + +.rose-badge { + @include type.type(s, semibold); + padding: sizing.rems(xxxs) sizing.rems(xxs); + border-radius: sizing.rems(xxxs); + border-width: sizing.rems(xxxxs); + border-style: solid; + display: inline-block; + + --color: var(--ui-gray-starker-1); + --border-color: var(--ui-gray-subtler-5); + --background-color: none; + + color: var(--color); + border-color: var(--border-color); + background-color: var(--background-color); + + &.rose-badge-outline { + --border-color: var(--ui-gray-starker-1); + } + + &.rose-badge-dark { + --color: var(--ui-gray-subtler-6); + --border-color: var(--ui-gray-starker-1); + --background-color: var(--ui-gray-starker-1); + } + + &.rose-badge-active { + --color: var(--info); + } + + &.rose-badge-informational { + --color: var(--info-starker-1); + --border-color: var(--info-subtler-2); + --background-color: var(--info-subtler-2); + } + + &.rose-badge-informational-outline { + --color: var(--info-starker-1); + --border-color: var(--info-starker-1); + } + + &.rose-badge-alert { + --color: var(--alert-starker-1); + --background-color: var(--alert-subtler-1); + } + + &.rose-badge-alert-outline { + --color: var(--alert-starker-1); + --border-color: var(--alert-starker-1); + } + + &.rose-badge-error { + --color: var(--failure-starker-1); + --background-color: var(--failure-subtler-1); + } + + &.rose-badge-error-outline { + --color: var(--failure-starker-1); + --border-color: var(--failure-starker-1); + } + + &.rose-badge-success { + --color: var(--success-starker-1); + --background-color: var(--success-subtler-1); + } + + &.rose-badge-success-outline { + --color: var(--success-starker-1); + --border-color: var(--success-starker-1); + } + + &.rose-badge-warning { + --color: var(--warning-starker-1); + --background-color: var(--warning-subtler-1); + } + + &.rose-badge-warning-outline { + --color: var(--warning-starker-1); + --border-color: var(--warning-starker-1); + } +} diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss index 6c0e6f1df..dd35e9d22 100644 --- a/addons/rose/addon/styles/rose/components/_index.scss +++ b/addons/rose/addon/styles/rose/components/_index.scss @@ -13,3 +13,4 @@ @import 'table'; @import 'message'; @import 'link-button'; +@import 'badge'; diff --git a/addons/rose/addon/templates/components/rose/badge.hbs b/addons/rose/addon/templates/components/rose/badge.hbs new file mode 100644 index 000000000..6ef77906f --- /dev/null +++ b/addons/rose/addon/templates/components/rose/badge.hbs @@ -0,0 +1,14 @@ + + {{#if @icon}} + + {{/if}} + + + {{yield}} + + diff --git a/addons/rose/addon/templates/components/rose/dropdown.hbs b/addons/rose/addon/templates/components/rose/dropdown.hbs index 51b429435..c65c640ba 100644 --- a/addons/rose/addon/templates/components/rose/dropdown.hbs +++ b/addons/rose/addon/templates/components/rose/dropdown.hbs @@ -22,7 +22,7 @@ -
+
{{yield (hash link=(component "link-to" class="rose-dropdown-link") button=(component "rose/button" style="dropdown") diff --git a/addons/rose/app/components/rose/badge.js b/addons/rose/app/components/rose/badge.js new file mode 100644 index 000000000..6a69f03c6 --- /dev/null +++ b/addons/rose/app/components/rose/badge.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/badge'; diff --git a/addons/rose/app/components/rose/dropdown.js b/addons/rose/app/components/rose/dropdown.js index f51c98374..a0154aec2 100644 --- a/addons/rose/app/components/rose/dropdown.js +++ b/addons/rose/app/components/rose/dropdown.js @@ -1 +1 @@ -export { default } from 'rose/components/rose/dropdown'; \ No newline at end of file +export { default } from 'rose/components/rose/dropdown'; diff --git a/addons/rose/app/components/rose/layout/sidebar.js b/addons/rose/app/components/rose/layout/sidebar.js index f078ea1c6..47c59c143 100644 --- a/addons/rose/app/components/rose/layout/sidebar.js +++ b/addons/rose/app/components/rose/layout/sidebar.js @@ -1 +1 @@ -export { default } from 'rose/components/rose/layout/sidebar'; \ No newline at end of file +export { default } from 'rose/components/rose/layout/sidebar'; diff --git a/addons/rose/app/modifiers/on-click-inside.js b/addons/rose/app/modifiers/on-click-inside.js new file mode 100644 index 000000000..c137452fe --- /dev/null +++ b/addons/rose/app/modifiers/on-click-inside.js @@ -0,0 +1 @@ +export { default } from 'rose/modifiers/on-click-inside'; diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs index 2422caa03..95220225a 100644 --- a/addons/rose/tests/dummy/app/templates/index.hbs +++ b/addons/rose/tests/dummy/app/templates/index.hbs @@ -1,9 +1,21 @@ -Primary -Secondary -Warning -Ghost -
+Default +Default Outline +Default with Icon -Left Icon -Right Icon - +Light +Dark + +Informational +Informational Outline + +Alert +Alert Outline + +Error +Error Outline + +Success +Success Outline + +Warning +Warning Outline diff --git a/addons/rose/tests/integration/components/rose/badge-test.js b/addons/rose/tests/integration/components/rose/badge-test.js new file mode 100644 index 000000000..8665085e7 --- /dev/null +++ b/addons/rose/tests/integration/components/rose/badge-test.js @@ -0,0 +1,49 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | rose/badge', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs``); + assert.ok(find('.rose-badge')); + }); + + test('it renders with content', async function (assert) { + await render(hbs`host-catalog-1`); + assert.equal(find('.rose-badge').textContent.trim(), 'host-catalog-1'); + }); + + test('it renders an icon using @icon', async function (assert) { + await render(hbs``); + assert.ok(find('.rose-icon')); + }); + + test('it styles content using @style', async function (assert) { + let styles = [ + 'outline', + 'dark', + 'informational', + 'informational-outline', + 'alert', + 'alert-outline', + 'warning', + 'warning-outline', + 'error', + 'error-outline', + 'success', + 'success-outline', + 'warning', + 'warning-outline', + ]; + + await render(hbs``); + + styles.forEach((style) => { + this.set('style', style); + assert.ok(find(`.rose-badge-${style}`), `defines style: ${style}`); + }); + }); +}); diff --git a/addons/rose/tests/integration/components/rose/dropdown-test.js b/addons/rose/tests/integration/components/rose/dropdown-test.js index 1b4e0240a..3d4597c95 100644 --- a/addons/rose/tests/integration/components/rose/dropdown-test.js +++ b/addons/rose/tests/integration/components/rose/dropdown-test.js @@ -12,19 +12,14 @@ module('Integration | Component | rose/dropdown', function (hooks) { }); test('it renders with html attributes', async function (assert) { - await render( - hbs`` - ); + await render(hbs``); assert.ok(find('#custom-id')); assert.ok(find('.custom-class')); }); test('it renders with trigger', async function (assert) { await render(hbs``); - assert.equal( - find('.rose-dropdown-trigger').textContent.trim(), - 'Click me' - ); + assert.equal(find('.rose-dropdown-trigger').textContent.trim(), 'Click me'); }); test('it supports an icon', async function (assert) { @@ -72,4 +67,17 @@ module('Integration | Component | rose/dropdown', function (hooks) { await click('#wrapper'); assert.notOk(find('#dropdown').open); }); + + test('it is closed when inside content is clicked', async function (assert) { + await render(hbs` + + Button + + `); + + await click('summary'); + assert.ok(find('.rose-dropdown').open); + await click('button'); + assert.notOk(find('.rose-dropdown').open); + }); }); diff --git a/addons/rose/tests/integration/components/rose/layout/sidebar-test.js b/addons/rose/tests/integration/components/rose/layout/sidebar-test.js index 727710ca1..a186775a5 100644 --- a/addons/rose/tests/integration/components/rose/layout/sidebar-test.js +++ b/addons/rose/tests/integration/components/rose/layout/sidebar-test.js @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, find } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; -module('Integration | Component | rose/layout/sidebar', function(hooks) { +module('Integration | Component | rose/layout/sidebar', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { diff --git a/addons/rose/tests/integration/components/rose/link-button-test.js b/addons/rose/tests/integration/components/rose/link-button-test.js index 617f76ed0..ac2be94aa 100644 --- a/addons/rose/tests/integration/components/rose/link-button-test.js +++ b/addons/rose/tests/integration/components/rose/link-button-test.js @@ -19,7 +19,9 @@ module('Integration | Component | rose/link-button', function (hooks) { }); test('it renders with content', async function (assert) { - await render(hbs`Link button content`); + await render( + hbs`Link button content` + ); assert.ok( find('.rose-link-button').textContent.trim(), 'Link button content' @@ -32,19 +34,25 @@ module('Integration | Component | rose/link-button', function (hooks) { }); test('it supports left icon with @iconLeft', async function (assert) { - await render(hbs``); + await render( + hbs`` + ); assert.ok(find('.rose-icon')); assert.ok(find('.has-icon-left')); }); test('it supports left icon with @iconRight', async function (assert) { - await render(hbs``); + await render( + hbs`` + ); assert.ok(find('.rose-icon')); assert.ok(find('.has-icon-right')); }); test('it supports only icon with @iconOnly', async function (assert) { - await render(hbs``); + await render( + hbs`` + ); assert.ok(find('.rose-icon')); assert.ok(find('.has-icon-only')); }); diff --git a/addons/rose/tests/integration/modifiers/on-click-inside-test.js b/addons/rose/tests/integration/modifiers/on-click-inside-test.js new file mode 100644 index 000000000..8197a2258 --- /dev/null +++ b/addons/rose/tests/integration/modifiers/on-click-inside-test.js @@ -0,0 +1,37 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, click } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Modifier | on-click-inside', function (hooks) { + setupRenderingTest(hooks); + + test('it fires event when inside is clicked', async function (assert) { + assert.expect(2); + this.set('clickedInside', (element) => { + assert.ok(element); + assert.equal(element.id, 'inside'); + }); + await render(hbs` +
+
+
+
+ `); + await click('#item'); + }); + + test('it does not fire event when outside is clicked', async function (assert) { + assert.expect(0); + this.set('clickedInside', () => { + assert.ok(true); + }); + await render(hbs` +
+
+
+
+ `); + await click('#outside'); + }); +});