From 403ee85a137b308ab91a9b1cdf1f766964d21166 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Mon, 22 Jun 2020 10:03:44 -0400 Subject: [PATCH] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20rose=20dropdown?= =?UTF-8?q?=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/rose/addon/components/rose/dropdown.js | 26 ++++ .../components/rose/dropdown.stories.mdx | 45 +++++++ .../styles/rose/components/_dropdown.scss | 111 ++++++++++++++++++ .../addon/styles/rose/components/_index.scss | 1 + .../templates/components/rose/dropdown.hbs | 32 +++++ addons/rose/app/components/rose/dropdown.js | 1 + .../tests/dummy/app/templates/playground.hbs | 6 + .../components/rose/dropdown-test.js | 75 ++++++++++++ 8 files changed, 297 insertions(+) create mode 100644 addons/rose/addon/components/rose/dropdown.js create mode 100644 addons/rose/addon/components/rose/dropdown.stories.mdx create mode 100644 addons/rose/addon/styles/rose/components/_dropdown.scss create mode 100644 addons/rose/addon/templates/components/rose/dropdown.hbs create mode 100644 addons/rose/app/components/rose/dropdown.js create mode 100644 addons/rose/tests/integration/components/rose/dropdown-test.js diff --git a/addons/rose/addon/components/rose/dropdown.js b/addons/rose/addon/components/rose/dropdown.js new file mode 100644 index 000000000..76723d8bf --- /dev/null +++ b/addons/rose/addon/components/rose/dropdown.js @@ -0,0 +1,26 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/rose/dropdown'; +import { action } from '@ember/object'; + +export default Component.extend({ + + // =attributes + + layout, + tagName: '', + + // =actions + + /** + * 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. + * @param {HTMLElement} element + */ + @action + close(element) { + if (element.open) element.open = false; + }, + +}); diff --git a/addons/rose/addon/components/rose/dropdown.stories.mdx b/addons/rose/addon/components/rose/dropdown.stories.mdx new file mode 100644 index 000000000..87c50f1f1 --- /dev/null +++ b/addons/rose/addon/components/rose/dropdown.stories.mdx @@ -0,0 +1,45 @@ +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'; + + + +# Dropdown + +A simple dropdown component with support for links and buttons. + + + {{ + template: hbs` + + Menu item + Menu item + Button menu item + + `, + context: {} + }} + + + + {{ + template: hbs` + + Menu item + Menu item + Button menu item + + `, + context: {} + }} + + + +```htmlbars + + Menu item + Menu item + Button menu item + +``` diff --git a/addons/rose/addon/styles/rose/components/_dropdown.scss b/addons/rose/addon/styles/rose/components/_dropdown.scss new file mode 100644 index 000000000..bd87f3acb --- /dev/null +++ b/addons/rose/addon/styles/rose/components/_dropdown.scss @@ -0,0 +1,111 @@ +@use '../variables/sizing'; +@use '../utilities/type'; +@use '../utilities/dropdown'; + +.rose-dropdown { + position: relative; + display: inline-block; + + .rose-dropdown-trigger { + @include type.type(button, semibold); + cursor: default; + overflow: hidden; + display: flex; + align-items: center; + margin-right: sizing.rems(s); + padding: 0 sizing.rems(xl) 0 sizing.rems(l); + $lineHeight: 36 / 13; + line-height: $lineHeight; + + //color: var(--subtle); + background: url("data:image/svg+xml;utf-8,") + no-repeat right sizing.rems(s) center; + background-size: sizing.rems(m); + + &:hover { + background-color: var(--ui-gray-subtler-5); + } + + &:not(.has-icon-only) .rose-icon:first-child { + margin-left: -(sizing.rems(xs)); + margin-right: sizing.rems(xxs); + } + + &.has-icon-only { + padding-right: sizing.rems(l); + padding-left: sizing.rems(s); + background-position: right sizing.rems(xs) center; + + .rose-dropdown-trigger-text { + position: absolute; + top: -1000px; + left: -1000px; + width: 1px; + height: 1px; + opacity: 0; + } + } + + // Hide default twistie triangle + // Webkit / Chromium + &::-webkit-details-marker { + display: none; + } + // Firefox + list-style-type: none; + &::marker { + display: none; + } + } + + .rose-dropdown-trigger:active, + &[open] > .rose-dropdown-trigger { + background-color: var(--ui-gray-subtler-4); + } + + .rose-dropdown-content { + $xs: sizing.rems(xs); // 8 + $xxs: sizing.rems(xxs); // 4 + position: absolute; + padding: $xxs 0; + border: 1px solid var(--ui-gray-subtler-2); + border-radius: sizing.rems(xxxs); + background: var(--subtle); + box-shadow: 0 $xxs $xxs rgba(var(--stark-components), var(--opacity-3)), + 0 $xxs $xs rgba(var(--stark-components), var(--opacity-4)); + z-index: 99; + } + + &.dropdown-right { + .rose-dropdown-content { + right: 0; + } + } + + .rose-dropdown-link, + .rose-button-dropdown { + @include type.type(m, normal); + display: block; + // Targeting min-width of 200 + min-width: (sizing.rems(m) + sizing.rems(xxs)) * 10; + padding: 0 sizing.rems(s); + margin: 0; + border: 0; + color: var(--stark); + background-color: transparent; + // target line height is 36, which is not a standard line height for this + // font size + $lineHeight: 36 / 16; + line-height: $lineHeight; + text-align: left; + text-decoration: none; + + &:hover { + background-color: var(--ui-gray-subtler-4); + } + } + + .rose-button-dropdown { + width: 100%; + } +} diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss index 66b1f3043..6c0e6f1df 100644 --- a/addons/rose/addon/styles/rose/components/_index.scss +++ b/addons/rose/addon/styles/rose/components/_index.scss @@ -1,4 +1,5 @@ @import 'button'; +@import 'dropdown'; @import 'form'; @import 'header'; @import 'layout'; diff --git a/addons/rose/addon/templates/components/rose/dropdown.hbs b/addons/rose/addon/templates/components/rose/dropdown.hbs new file mode 100644 index 000000000..51b429435 --- /dev/null +++ b/addons/rose/addon/templates/components/rose/dropdown.hbs @@ -0,0 +1,32 @@ +
+ + + + {{#if @icon}} + + {{/if}} + + + {{@text}} + + + + +
+ {{yield (hash + link=(component "link-to" class="rose-dropdown-link") + button=(component "rose/button" style="dropdown") + )}} +
+ +
diff --git a/addons/rose/app/components/rose/dropdown.js b/addons/rose/app/components/rose/dropdown.js new file mode 100644 index 000000000..f51c98374 --- /dev/null +++ b/addons/rose/app/components/rose/dropdown.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/dropdown'; \ No newline at end of file diff --git a/addons/rose/tests/dummy/app/templates/playground.hbs b/addons/rose/tests/dummy/app/templates/playground.hbs index 5f02a47ef..8f7d026b4 100644 --- a/addons/rose/tests/dummy/app/templates/playground.hbs +++ b/addons/rose/tests/dummy/app/templates/playground.hbs @@ -50,6 +50,12 @@ Dummy app index template + + Menu item + Menu item + Button menu item + + `); + assert.ok(find('.rose-dropdown')); + }); + + test('it renders with html attributes', async function (assert) { + 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' + ); + }); + + test('it supports an icon', async function (assert) { + await render(hbs``); + assert.ok(find('svg')); + }); + + test('it supports icon-only display', async function (assert) { + await render(hbs``); + assert.ok(find('.has-icon-only')); + }); + + test('it renders with content', async function (assert) { + await render(hbs` + + + + + + `); + assert.ok(find('.rose-dropdown-content')); + assert.equal(findAll('a').length, 2); + assert.equal(findAll('button').length, 3); + }); + + test('it is toggled on click on trigger content', async function (assert) { + await render(hbs``); + + assert.notOk(find('#dropdown').open); + await click('summary'); + assert.ok(find('#dropdown').open); + await click('summary'); + assert.notOk(find('#dropdown').open); + }); + + test('it is closed with outside click is triggered', async function (assert) { + await render(hbs` +
+ +
+ `); + + await click('summary'); + assert.ok(find('#dropdown').open); + await click('#wrapper'); + assert.notOk(find('#dropdown').open); + }); +});