chore: 🤖 add rose dropdown component
This commit is contained in:
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…" @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…" @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…" 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…" 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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user