chore: 🤖 add rose dropdown component

This commit is contained in:
Randall Morey committed 2020-06-22 10:03:44 -04:00
1 parent 1c2020a381
commit 403ee85a13
8 files changed
+297

No files matched your search

@@ -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;
},
});
@@ -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';
<Meta title="Rose|Dropdown" component="Dropdown" />
# Dropdown
A simple dropdown component with support for links and buttons.
<Preview>
<Story name="Basic Dropdown" height="5rem">{{
template: hbs`
<Rose::Dropdown @text="Choose One&hellip;" @icon="user-organization" as |dropdown|>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.link @route="about">Menu item</dropdown.link>
<dropdown.button>Button menu item</dropdown.button>
</Rose::Dropdown>
`,
context: {}
}}</Story>
</Preview>
<Preview>
<Story name="Icon-only Dropdown" height="5rem">{{
template: hbs`
<Rose::Dropdown @text="Choose One&hellip;" @icon="user-organization" @iconOnly={{true}} as |dropdown|>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.link @route="about">Menu item</dropdown.link>
<dropdown.button>Button menu item</dropdown.button>
</Rose::Dropdown>
`,
context: {}
}}</Story>
</Preview>
```htmlbars
<Rose::Dropdown @text="Choose One&hellip;" as |dropdown|>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.link @route="about">Menu item</dropdown.link>
<dropdown.button>Button menu item</dropdown.button>
</Rose::Dropdown>
```
@@ -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,<svg viewBox='0 0 24 24' fill='%231b1f26' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z'></path></svg>")
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%;
}
}
@@ -1,4 +1,5 @@
@import 'button';
@import 'dropdown';
@import 'form';
@import 'header';
@import 'layout';
@@ -0,0 +1,32 @@
<details
...attributes
class="rose-dropdown"
{{on-click-outside this.close}}
>
<summary
class="rose-dropdown-trigger{{if @iconOnly " has-icon-only"}}"
title={{@text}}
>
{{#if @icon}}
<Rose::Icon
@name={{@icon}}
@size="medium"
/>
{{/if}}
<span class="rose-dropdown-trigger-text">
{{@text}}
</span>
</summary>
<div class="rose-dropdown-content">
{{yield (hash
link=(component "link-to" class="rose-dropdown-link")
button=(component "rose/button" style="dropdown")
)}}
</div>
</details>
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/dropdown';
@@ -50,6 +50,12 @@
Dummy app index template
<Rose::Dropdown @text="Choose One&hellip;" as |dropdown|>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.link @route="about">Menu item</dropdown.link>
<dropdown.button>Button menu item</dropdown.button>
</Rose::Dropdown>
<Rose::Notification
@style="error"
@heading="Error"
@@ -0,0 +1,75 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, find, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | rose/dropdown', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::Dropdown />`);
assert.ok(find('.rose-dropdown'));
});
test('it renders with html attributes', async function (assert) {
await render(
hbs`<Rose::Dropdown id="custom-id" class="custom-class"/>`
);
assert.ok(find('#custom-id'));
assert.ok(find('.custom-class'));
});
test('it renders with trigger', async function (assert) {
await render(hbs`<Rose::Dropdown @text="Click me" />`);
assert.equal(
find('.rose-dropdown-trigger').textContent.trim(),
'Click me'
);
});
test('it supports an icon', async function (assert) {
await render(hbs`<Rose::Dropdown @icon="user-square-fill" />`);
assert.ok(find('svg'));
});
test('it supports icon-only display', async function (assert) {
await render(hbs`<Rose::Dropdown @iconOnly={{true}} />`);
assert.ok(find('.has-icon-only'));
});
test('it renders with content', async function (assert) {
await render(hbs`<Rose::Dropdown @text="Click me" as |dropdown|>
<dropdown.link @route="about"/>
<dropdown.link @route="about"/>
<dropdown.button />
<dropdown.button />
<dropdown.button />
</Rose::Dropdown>`);
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`<Rose::Dropdown id="dropdown" />`);
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`
<div id="wrapper">
<Rose::Dropdown id="dropdown" />
</div>
`);
await click('summary');
assert.ok(find('#dropdown').open);
await click('#wrapper');
assert.notOk(find('#dropdown').open);
});
});