Merge pull request #28 from hashicorp/refactor-header

Refactor header
This commit is contained in:
Susmitha Girumala authored and GitHub committed 2020-05-27 10:26:35 -04:00
commit 0eac96e69d
44 files changed
+475 -517

No files matched your search

@@ -18,5 +18,5 @@ import layout from '../../../templates/components/rose/form/input';
*/
export default Component.extend({
layout,
tagName: ''
tagName: '',
});
@@ -18,5 +18,5 @@ import layout from '../../../templates/components/rose/form/textarea';
*/
export default Component.extend({
layout,
tagName: ''
tagName: '',
});
@@ -1,36 +1,37 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { action } from '@storybook/addon-actions';
import { text, boolean } from '@storybook/addon-knobs';
import { text, boolean, select } from '@storybook/addon-knobs';
<Meta title="Rose|Header" component="Header" />
# Header
A header component that allows three distinct sections to be configured: brand, navigation, and utilities.
A header component that allows three distinct sections to be configured: brand, nav, and utilities.
```htmlbars
<Rose::Header name="main header" as |header|>
<header.brand />
<Rose::Header as |header|>
<header.brand @logo="logo" @text="Product Name" />
<header.navigation as |nav|>
<nav.dropdown as |dropdown|>
<dropdown.trigger>Org name</dropdown.trigger>
<dropdown.content as |content|>
<content.link @route="about">link</content.link>
<content.button>button</content.button>
</dropdown.content>
<header.nav as |nav|>
<nav.dropdown @text="org-name" 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>
</nav.dropdown>
<nav.link @route="index">Section</nav.link>
<nav.link @route="about">Section</nav.link>
</header.navigation>
</header.nav>
<header.utilities as |utilities|>
<utilities.dropdown as |dropdown|>
<dropdown.trigger>Click me</dropdown.trigger>
<dropdown.content as |content|>
<content.link @route="about">link</content.link>
<content.button>button</content.button>
</dropdown.content>
<utilities.dropdown
@text="user@example.net"
@iconOnly={{true}}
@icon="user-square-fill" 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>
</utilities.dropdown>
</header.utilities>
</Rose::Header>
@@ -39,34 +40,43 @@ A header component that allows three distinct sections to be configured: brand,
<Preview>
<Story name="Basic">{{
template: hbs`
<Rose::Header name="main header" as |header|>
<header.brand />
<header.navigation as |nav|>
<nav.dropdown as |dropdown|>
<dropdown.trigger>{{navigation-dropdown-label}}</dropdown.trigger>
<dropdown.content as |content|>
<content.link @route="about">{{navigation-dropdown-link-label}}</content.link>
<content.button>{{navigation-dropdown-button-label}}</content.button>
</dropdown.content>
<Rose::Header as |header|>
<header.brand @logo="logo" @text={{brandName}} />
<header.nav as |nav|>
<nav.dropdown @text="org-name" 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>
</nav.dropdown>
<nav.link @route="about">{{navigation-link-label}}</nav.link>
</header.navigation>
<nav.link @route="index">Section</nav.link>
<nav.link @route="about">Section</nav.link>
</header.nav>
<header.utilities as |utilities|>
<utilities.dropdown @alignRight={{true}} as |dropdown|>
<dropdown.trigger><Rose::Icon @name="user-square-outline" /></dropdown.trigger>
<dropdown.content as |content|>
<content.link @route="about">link</content.link>
<content.button>button</content.button>
</dropdown.content>
<utilities.dropdown
@text="user@example.net"
@iconOnly={{true}}
@icon={{dropdownIcon}} 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>
</utilities.dropdown>
</header.utilities>
</Rose::Header>
`,
context: {
'navigation-dropdown-label': text('navigation-dropdown-label', 'Org Name'),
'navigation-dropdown-link-label': text('navigation-dropdown-link-label', 'Menu Item'),
'navigation-dropdown-button-label': text('navigation-dropdown-button-label', 'Menu Item'),
'navigation-link-label': text('navigation-link-label', 'Section'),
brandName: text('brandName', 'Brand Name'),
dropdownIcon: select(
'dropdownIcon',
{
'user-square-fill': 'user-square-fill',
'check-circle-outline': 'check-circle-outline',
'copy-action': 'copy-action',
'unfold-more': 'unfold-more',
'unfold-less': 'unfold-less',
},
'user-square-fill'
),
},
}}</Story>
</Preview>
@@ -1,7 +0,0 @@
import Component from '@ember/component';
import layout from '../../../../templates/components/rose/header/dropdown/content';
export default Component.extend({
layout,
tagName: '',
});
@@ -1,7 +0,0 @@
import Component from '@ember/component';
import layout from '../../../../../templates/components/rose/header/dropdown/content/button';
export default Component.extend({
layout,
tagName: '',
});
@@ -1,7 +0,0 @@
import Component from '@ember/component';
import layout from '../../../../templates/components/rose/header/dropdown/trigger';
export default Component.extend({
layout,
tagName: '',
});
@@ -1,5 +1,5 @@
import Component from '@ember/component';
import layout from '../../../templates/components/rose/header/navigation';
import layout from '../../../templates/components/rose/header/nav';
export default Component.extend({
layout,
@@ -1,5 +1,10 @@
import { modifier } from 'ember-modifier';
/**
* Used to detect clicks occuring _outside_ of the modified element.
* This is useful especially for dropdowns to toggle content visibility
* when the user clicks away.
*/
export default modifier((element, [callback]) => {
function handleClick(event) {
if (!element.contains(event.target)) {
@@ -1,168 +0,0 @@
@use '../variables/sizing';
@use '../variables/media';
@use '../utilities/type';
$brand-hashicorp: "data:image/svg+xml;utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 106.86 113.14' fill='%23ffffff'><g id='Layer_2' data-name='Layer 2'><g id='Logo'><polygon points='44.54 0 0 25.69 0 25.71 0 87.41 16.73 97.07 16.73 35.35 44.54 19.3 44.54 0'/><polygon points='62.32 0 62.32 49.15 44.54 49.15 44.54 30.81 27.8 40.47 27.8 103.44 44.54 113.12 44.54 64.11 62.32 64.11 62.32 82.33 79.05 72.67 79.05 9.66 62.32 0'/><polygon points='62.32 113.14 106.86 87.45 106.86 87.43 106.86 25.73 90.12 16.07 90.12 77.79 62.32 93.84 62.32 113.14'/></g></g></svg>";
$dropdown-chevron: (
'default':
"data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%23ebeef2' 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>",
'open':
"data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231f2124' 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>",
);
.rose-header {
@include type.type(s, semibold);
display: grid;
grid-template-areas: 'brand navigation utilities';
grid-template-columns: 0.01fr auto 1fr;
grid-gap: sizing.rems(xxxs);
background: var(--black);
color: var(--white);
.rose-header-brand {
grid-area: brand;
width: sizing.rems(l);
height: sizing.rems(l);
background-image: url($brand-hashicorp);
background-repeat: no-repeat;
margin: sizing.rems(xxs);
}
.rose-header-navigation {
grid-area: navigation;
.rose-header-navigation-flex {
display: flex;
flex-wrap: wrap;
}
.rose-header-navigation-link {
text-decoration: none;
display: block;
color: var(--white);
border: 1px solid var(--stark);
border-radius: sizing.rems(xxs);
text-align: center;
padding: sizing.rems(xxs) sizing.rems(s);
position: relative;
top: sizing.rems(xxxs);
cursor: pointer;
margin: 0 sizing.rems(xs);
&:hover {
background: var(--ui-gray-starker-3);
}
&:active {
background: var(--ui-gray-starker-2);
}
}
}
.rose-header-utilities {
grid-area: utilities;
.rose-header-utilities-flex {
margin-right: sizing.rems(s);
display: flex;
flex-wrap: wrap;
flex-direction: row-reverse;
}
}
.rose-header-navigation,
.rose-header-utilities {
.rose-header-dropdown {
&[open] {
.rose-header-dropdown-trigger {
background: var(--white);
color: var(--ui-gray-starker-4);
.rose-header-dropdown-chevron {
background-image: url(map-get($dropdown-chevron, 'open'));
}
}
}
.rose-header-dropdown-trigger {
padding: sizing.rems(xxs) sizing.rems(s);
border-radius: sizing.rems(xxxs);
color: var(--white);
position: relative;
top: sizing.rems(xxxs);
cursor: pointer;
&::-webkit-details-marker {
display: none;
}
.rose-header-dropdown-chevron {
background-image: url(map-get($dropdown-chevron, 'default'));
width: sizing.rems(m);
height: sizing.rems(m);
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle;
}
}
.rose-header-dropdown-content {
position: absolute;
background-color: var(--white);
border-radius: sizing.rems(xxxs);
border: 1px solid var(--ui-gray-subtler-2);
padding-right: sizing.rems(xl);
margin-top: sizing.rems(xxxs);
&.rose-header-dropdown-content-right {
right: sizing.rems(s);
}
.rose-header-dropdown-content-link {
padding: sizing.rems(xs) sizing.rems(s);
text-decoration: none;
display: block;
color: var(--ui-gray-starker-4);
height: sizing.rems(l);
}
.rose-header-dropdown-content-button {
button {
cursor: pointer;
padding: sizing.rems(xs) sizing.rems(s);
color: var(--ui-gray-starker-4);
display: inline-block;
border: none;
text-decoration: none;
font-size: inherit;
font-weight: inherit;
-webkit-appearance: none;
}
}
}
}
}
}
@media (max-width: media.width(small)) {
.rose-header {
grid-template-columns: 1fr 3fr;
grid-template-areas:
'brand utilities'
'navigation navigation';
padding-bottom: 0;
.rose-header-navigation {
background: var(--ui-header-subtler);
padding: sizing.rems(xs) sizing.rems(s);
.rose-header-navigation-link {
border: 1px solid var(--ui-gray-subtler);
&:hover {
background: var(--ui-gray-starker-3);
}
}
}
}
}
@@ -0,0 +1,31 @@
@use '../../variables/sizing';
@use '../../utilities/type';
.rose-header-brand {
grid-area: brand;
display: flex;
align-items: center;
color: var(--white);
text-decoration: none;
padding: 0 10px;
margin: 10px;
svg {
// 28px... header sizes do not follow the sizing system
$logoSize: sizing.rems(l) + sizing.rems(xxs);
display: block;
width: $logoSize;
}
.rose-header-brand-text {
@include type.type(m, normal);
margin: 0;
white-space: nowrap;
position: absolute;
top: -1000px;
left: -1000px;
opacity: 0;
width: 1px;
height: 1px;
}
}
@@ -0,0 +1,105 @@
@use 'sass:map';
@use '../../variables/sizing';
@use '../../utilities/type';
$dropdown-chevron: (
'default':
"data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%23ebeef2' 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>",
);
.rose-header-dropdown {
position: relative;
.rose-header-dropdown-trigger {
cursor: default;
overflow: hidden;
display: flex;
align-items: center;
color: var(--white);
background: url(map.get($dropdown-chevron, default)) no-repeat right center;
background-size: sizing.rems(m);
margin-right: sizing.rems(s);
padding-right: sizing.rems(l) - sizing.rems(xxs);
$lineHeight: 48 / 16;
line-height: $lineHeight;
&.icon-only {
padding-right: sizing.rems(m);
.rose-header-dropdown-trigger-text {
// TODO: factor out into visually-hidden mixin utility
position: absolute;
left: -1000px;
top: -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-icon {
display: inline-block;
height: var(--size);
> svg {
vertical-align: top;
}
}
}
.rose-header-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));
}
&.dropdown-right {
.rose-header-dropdown-content {
right: 0;
}
}
.rose-header-dropdown-link,
.rose-button-header-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-header-dropdown {
width: 100%;
}
}
@@ -0,0 +1,26 @@
@use '../../variables/sizing';
@use '../../variables/media';
@use '../../utilities/type';
@import 'brand';
@import 'nav';
@import 'utilities';
@import 'link';
@import 'dropdown';
.rose-header {
display: grid;
grid-template-areas: 'brand nav utilities';
grid-template-columns: 0.01fr auto 1fr;
background: var(--black) linear-gradient(90deg, #191a1c 0%, #1b212d 100%);
}
@media (max-width: media.width(small)) {
.rose-header {
grid-template-columns: 1fr 3fr;
grid-template-areas:
'brand utilities'
'nav nav';
padding-bottom: 0;
}
}
@@ -0,0 +1,29 @@
@use '../../variables/sizing';
@use '../../utilities/type';
.rose-header-nav-link {
@include type.type(s, semibold);
// target line height is 16, which is not a standard line height
$lineHeight: 16 / 14;
line-height: $lineHeight;
text-decoration: none;
color: var(--white);
opacity: 0.75;
// again, nonstandard sizing for header stuff
margin: 10px 0;
padding: 6px 12px;
border-radius: sizing.rems(xxs);
&.active {
opacity: 1;
background: #{color(ui-gray, 800)};
}
&:hover {
opacity: 1;
text-decoration: underline;
}
}
@@ -0,0 +1,14 @@
@use '../../variables/sizing';
@use '../../variables/media';
.rose-header-nav {
grid-area: nav;
display: flex;
flex-wrap: wrap;
align-items: center;
@media (max-width: media.width(small)) {
padding: 0 20px;
background: var(--ui-header-subtler);
}
}
@@ -0,0 +1,9 @@
@use '../../variables/sizing';
.rose-header-utilities {
grid-area: utilities;
display: flex;
flex-wrap: wrap;
flex-direction: row-reverse;
align-items: center;
}
@@ -1,8 +1,10 @@
<div ...attributes
class="rose-header" >
<header
...attributes
class="rose-header"
>
{{yield (hash
brand=(component 'rose/header/brand')
utilities=(component 'rose/header/utilities')
navigation=(component 'rose/header/navigation')
nav=(component 'rose/header/nav')
)}}
</div>
</header>
@@ -1 +1,16 @@
<span class="rose-header-brand" />
<LinkTo
...attributes
class="rose-header-brand"
title={{@text}}
@route={{if @route @route "index"}}
>
{{#if @logo}}
{{inline-svg @logo}}
{{/if}}
{{#if @text}}
<h1 class="rose-header-brand-text">{{@text}}</h1>
{{/if}}
</LinkTo>
@@ -1,8 +1,32 @@
<details class="rose-header-dropdown" ...attributes
{{on-click-outside this.close}}>
<details
...attributes
class="rose-header-dropdown{{if @alignRight " dropdown-right"}}"
{{on-click-outside this.close}}
>
<summary
class="rose-header-dropdown-trigger{{if @iconOnly " icon-only"}}"
title={{@text}}
>
{{#if @icon}}
<Rose::Icon
@name={{@icon}}
@size="large"
/>
{{/if}}
<span class="rose-header-dropdown-trigger-text">
{{@text}}
</span>
</summary>
<div class="rose-header-dropdown-content">
{{yield (hash
link=(component "link-to" class="rose-header-dropdown-link")
button=(component "rose/button" style="header-dropdown")
)}}
</div>
{{yield (hash
trigger=(component 'rose/header/dropdown/trigger')
content=(component 'rose/header/dropdown/content' alignRight=@alignRight)
)}}
</details>
@@ -1,6 +0,0 @@
<span class="rose-header-dropdown-content {{if @alignRight "rose-header-dropdown-content-right"}}" ...attributes>
{{yield (hash
link=(component 'link-to' class="rose-header-dropdown-content-link")
button=(component 'rose/header/dropdown/content/button')
)}}
</span>
@@ -1,3 +0,0 @@
<span class="rose-header-dropdown-content-button">
<button type="button" ...attributes>{{yield}}</button>
</span>
@@ -1,4 +0,0 @@
<summary class="rose-header-dropdown-trigger" ...attributes>
{{yield}}
<span class="rose-header-dropdown-chevron" />
</summary>
@@ -0,0 +1,6 @@
<nav class="rose-header-nav">
{{yield (hash
link=(component 'link-to' class="rose-header-nav-link")
dropdown=(component 'rose/header/dropdown')
)}}
</nav>
@@ -1,8 +0,0 @@
<span class="rose-header-navigation">
<span class="rose-header-navigation-flex">
{{yield (hash
link=(component 'link-to' class="rose-header-navigation-link")
dropdown=(component 'rose/header/dropdown')
)}}
</span>
</span>
@@ -1,7 +1,5 @@
<span class="rose-header-utilities">
<span class="rose-header-utilities-flex">
{{yield (hash
dropdown=(component 'rose/header/dropdown' position="true")
)}}
</span>
</span>
<div class="rose-header-utilities">
{{yield (hash
dropdown=(component 'rose/header/dropdown' alignRight=true)
)}}
</div>
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/header/dropdown/content';
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/header/dropdown/content/button';
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/header/dropdown/trigger';
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/header/nav';
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/header/navigation';
+30
View File
@@ -11,6 +11,8 @@ module.exports = {
this.includeStyles(app);
this.includeIcons(app);
this.includePublic(app);
this.setupSVGO(app);
},
/**
@@ -56,4 +58,32 @@ module.exports = {
if (addon.name === 'ember-inline-svg') addon.included(app);
});
},
/**
* Finds the public folder and includes it into the ember-inline-svg addon.
*/
includePublic(app) {
const publicPath = path.resolve('public');
const dummyPublicPath = path.resolve('tests/dummy/public');
app.options.svg = app.options.svg || {};
app.options.svg.paths = app.options.svg.paths || [];
app.options.svg.paths.push(publicPath);
if (this.isDevelopingAddon()) app.options.svg.paths.push(dummyPublicPath);
this.addons.forEach((addon) => {
if (addon.name === 'ember-inline-svg') addon.included(app);
});
},
/**
* Adjust SVG optimizer defaults (used by ember-inline-svg).
*/
setupSVGO(app) {
app.options.svg = app.options.svg || {};
app.options.svg.optimize = app.options.svg.optimize || {};
app.options.svg.optimize.plugins = app.options.svg.optimize.plugins || [];
app.options.svg.optimize.plugins.push({ removeViewBox: false });
},
};
+1 -1
View File
@@ -16,7 +16,7 @@
"lint:sass": "sass-lint -v -q",
"format": "npm run format:js && npm run format:sass",
"format:js": "prettier --write '{addon,app,config,stories,tests}/**/*.js' *.js",
"format:sass": "prettier --write {addon,app}/**/*.scss",
"format:sass": "prettier --write addon/**/*.scss",
"start": "ember serve",
"test": "ember test",
"test:all": "ember try:each",
+20 -16
View File
@@ -1,26 +1,30 @@
<Rose::Header name="main header" as |header|>
<header.brand />
<Rose::Header as |header|>
<header.navigation as |nav|>
<nav.dropdown as |dropdown|>
<dropdown.trigger>Org name</dropdown.trigger>
<dropdown.content as |content|>
<content.link @route="about">link</content.link>
<content.button>button</content.button>
</dropdown.content>
<header.brand @logo="logo" @text="Product Name" />
<header.nav as |nav|>
<nav.dropdown @text="org-name" 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>
</nav.dropdown>
<nav.link @route="index">Section</nav.link>
<nav.link @route="about">Section</nav.link>
</header.navigation>
</header.nav>
<header.utilities as |utilities|>
<utilities.dropdown @alignRight={{true}} as |dropdown|>
<dropdown.trigger><Rose::Icon @name="user-square-outline" /></dropdown.trigger>
<dropdown.content as |content|>
<content.link @route="about">link</content.link>
<content.button>button</content.button>
</dropdown.content>
<utilities.dropdown
@text="user@example.net"
@iconOnly={{true}}
@icon="user-square-fill" 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>
</utilities.dropdown>
</header.utilities>
</Rose::Header>
+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.278 9.313a2.021 2.021 0 0 1-2.01-.951c-.552-.928-.224-2.115.732-2.65.956-.536 2.179-.218 2.732.71.407.684.346 1.54-.155 2.164l2.354 3.955c2.02-.731 4.352.038 5.466 1.91 1.242 2.087.504 4.76-1.647 5.964a4.608 4.608 0 0 1-3.51.41l.474-1.383c.77.204 1.594.1 2.286-.288 1.434-.804 1.925-2.583 1.098-3.975a3.006 3.006 0 0 0-2.193-1.429 3.059 3.059 0 0 0-2.507.807l-3.12-5.244zm3.367 6.592a2.003 2.003 0 0 1 1.855-1.213c1.104 0 2 .87 2 1.94 0 1.072-.896 1.94-2 1.941-.815 0-1.549-.48-1.855-1.213h-4.707C10.58 19.424 8.728 21 6.5 21c-2.484 0-4.5-1.957-4.5-4.366 0-1.24.533-2.358 1.389-3.153l.997 1.088a2.859 2.859 0 0 0-.886 2.065c0 1.607 1.343 2.91 3 2.91.928 0 1.803-.417 2.371-1.128a2.844 2.844 0 0 0 .534-2.51h6.24zm-7.568-.49c.501.623.562 1.479.155 2.164-.553.927-1.775 1.245-2.732.71-.956-.536-1.284-1.723-.732-2.65a2.022 2.022 0 0 1 2.01-.953l2.354-3.955c-1.664-1.332-2.143-3.676-1.03-5.548C9.345 3.096 12.1 2.38 14.25 3.585a4.37 4.37 0 0 1 2.12 2.744l-1.47.293a2.915 2.915 0 0 0-1.4-1.777c-1.435-.802-3.269-.326-4.098 1.065a2.835 2.835 0 0 0-.179 2.557 2.973 2.973 0 0 0 1.974 1.704l-3.12 5.243z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -16,26 +16,26 @@ module('Integration | Component | rose/header', function (hooks) {
<header.brand></header.brand>
</Rose::Header>`);
assert.ok(find('.rose-header-brand'));
assert.notOk(find('.rose-header-navigation'));
assert.notOk(find('.rose-header-nav'));
assert.notOk(find('.rose-header-utilities'));
});
test('it renders with navigation', async function (assert) {
test('it renders with nav', async function (assert) {
await render(hbs`<Rose::Header as |header| >
<header.navigation />
<header.nav />
</Rose::Header>`);
assert.ok(find('.rose-header-navigation'));
assert.ok(find('.rose-header-nav'));
assert.notOk(find('.rose-header-brand'));
assert.notOk(find('.rose-header-utilities'));
});
test('it renders with navigation elements', async function (assert) {
test('it renders with nav elements', async function (assert) {
await render(hbs`<Rose::Header as |header| >
<header.navigation as |nav|>
<header.nav as |nav|>
<nav.link @route="about"/>
</header.navigation>
</header.nav>
</Rose::Header>`);
assert.ok(find('.rose-header-navigation-link'));
assert.ok(find('.rose-header-nav-link'));
});
test('it renders with utilities', async function (assert) {
@@ -43,7 +43,7 @@ module('Integration | Component | rose/header', function (hooks) {
<header.utilities />
</Rose::Header>`);
assert.notOk(find('.rose-header-brand'));
assert.notOk(find('.rose-header-navigation'));
assert.notOk(find('.rose-header-nav'));
assert.ok(find('.rose-header-utilities'));
});
@@ -7,7 +7,15 @@ module('Integration | Component | rose/header/brand', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::Header::Brand />`);
assert.expect(3);
await render(hbs`
<Rose::Header::Brand @logo="logo" @text="Product Name" />
`);
assert.ok(find('.rose-header-brand'));
assert.ok(find('.rose-header-brand svg'));
assert.equal(
find('.rose-header-brand-text').textContent.trim(),
'Product Name'
);
});
});
@@ -20,25 +20,35 @@ module('Integration | Component | rose/header/dropdown', function (hooks) {
});
test('it renders with trigger', async function (assert) {
await render(hbs`<Rose::Header::Dropdown as |dropdown|>
<dropdown.trigger>Click me</dropdown.trigger>
</Rose::Header::Dropdown>`);
assert.ok(find('.rose-header-dropdown-trigger'));
await render(hbs`<Rose::Header::Dropdown @text="Click me" />`);
assert.equal(
find('.rose-header-dropdown-trigger').textContent.trim(),
'Click me'
);
});
test('it supports an icon', async function (assert) {
await render(hbs`<Rose::Header::Dropdown @icon="user-square-fill" />`);
assert.ok(find('svg'));
});
test('it supports icon-only display', async function (assert) {
await render(hbs`<Rose::Header::Dropdown @iconOnly={{true}} />`);
assert.ok(find('.icon-only'));
});
test('it supports right-aligned content', async function (assert) {
await render(hbs`<Rose::Header::Dropdown @alignRight={{true}} />`);
assert.ok(find('.dropdown-right'));
});
test('it renders with content', async function (assert) {
await render(hbs`<Rose::Header::Dropdown as |dropdown|>
<dropdown.content as |content|>
<content.link @route="about"/>
<content.link @route="about"/>
<content.button />
<content.button />
<content.button />
</dropdown.content>
await render(hbs`<Rose::Header::Dropdown @text="Click me" as |dropdown|>
<dropdown.link @route="about"/>
<dropdown.link @route="about"/>
<dropdown.button />
<dropdown.button />
<dropdown.button />
</Rose::Header::Dropdown>`);
assert.ok(find('.rose-header-dropdown-content'));
assert.equal(findAll('a').length, 2);
@@ -46,32 +56,24 @@ module('Integration | Component | rose/header/dropdown', function (hooks) {
});
test('it is toggled on click on trigger content', async function (assert) {
await render(hbs`<Rose::Header::Dropdown id="dropdown" as |dropdown|>
<dropdown.trigger id="trigger">Click me</dropdown.trigger>
<dropdown.content as |content|>
<content.button />
</dropdown.content>
</Rose::Header::Dropdown>`);
await render(hbs`<Rose::Header::Dropdown id="dropdown" />`);
assert.notOk(find('#dropdown').open);
await click('#trigger');
await click('summary');
assert.ok(find('#dropdown').open);
await click('#trigger');
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::Header::Dropdown id="dropdown" as |dropdown|>
<dropdown.trigger id="trigger">Click me</dropdown.trigger>
<dropdown.content as |content|>
<content.button />
</dropdown.content>
</Rose::Header::Dropdown></div>`);
await render(hbs`
<div id="wrapper">
<Rose::Header::Dropdown id="dropdown" />
</div>
`);
await click('#trigger');
await click('summary');
assert.ok(find('#dropdown').open);
await click('#wrapper');
assert.notOk(find('#dropdown').open);
});
@@ -1,75 +0,0 @@
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/header/dropdown/content', function (
hooks
) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content />`);
assert.ok(find('.rose-header-dropdown-content'));
});
test('it renders with content', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content>
content
</Rose::Header::Dropdown::Content>`);
assert.equal(
find('.rose-header-dropdown-content').textContent.trim(),
'content'
);
});
test('it renders with html attributes', async function (assert) {
await render(
hbs`<Rose::Header::Dropdown::Content id="dropdown-content" class="universe" />`
);
assert.equal(find('.rose-header-dropdown-content').id, 'dropdown-content');
assert.ok(
find('.rose-header-dropdown-content').className.match('universe')
);
});
test('it is positioned right when @alignRight={{true}}', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content />`);
assert.notOk(find('.rose-header-dropdown-content-right'));
await render(
hbs`<Rose::Header::Dropdown::Content @alignRight="{{true}}"/>`
);
assert.ok(find('.rose-header-dropdown-content-right'));
});
test('it render link element', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content as |content|>
<content.link @route="about" />
</Rose::Header::Dropdown::Content>`);
assert.ok(find('a'));
assert.ok(find('.rose-header-dropdown-content-link'));
});
test('it render link element with content', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content as |content|>
<content.link @route="about">linkify</content.link>
</Rose::Header::Dropdown::Content>`);
assert.equal(find('a').textContent.trim(), 'linkify');
});
test('it render button element', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content as |content|>
<content.button />
</Rose::Header::Dropdown::Content>`);
assert.ok(find('button'));
assert.ok(find('.rose-header-dropdown-content-button'));
});
test('it render button element with content', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content as |content|>
<content.button>dropdown button</content.button>
</Rose::Header::Dropdown::Content>`);
assert.equal(find('button').textContent.trim(), 'dropdown button');
});
});
@@ -1,32 +0,0 @@
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/header/dropdown/content/button',
function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content::Button />`);
assert.ok(find('.rose-header-dropdown-content-button'));
});
test('it renders with content', async function (assert) {
await render(
hbs`<Rose::Header::Dropdown::Content::Button>Button content</Rose::Header::Dropdown::Content::Button>`
);
assert.equal(
find('.rose-header-dropdown-content-button').textContent.trim(),
'Button content'
);
});
test('it render button element', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Content::Button />`);
assert.ok(find('button'));
assert.equal(find('button').type, 'button');
});
}
);
@@ -1,41 +0,0 @@
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/header/dropdown/trigger', function (
hooks
) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Trigger />`);
assert.ok(find('.rose-header-dropdown-trigger'));
assert.notOk(find('.rose-header-dropdown-open'));
});
test('it renders with content', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Trigger>
trigger content
</Rose::Header::Dropdown::Trigger>`);
assert.equal(
find('.rose-header-dropdown-trigger').textContent.trim(),
'trigger content'
);
});
test('it renders with html attributes', async function (assert) {
await render(
hbs`<Rose::Header::Dropdown::Trigger id="trigger" class="dropdown-trigger" />`
);
assert.equal(find('.rose-header-dropdown-trigger').id, 'trigger');
assert.ok(
find('.rose-header-dropdown-trigger').className.match('dropdown-trigger')
);
});
test('it renders chevron', async function (assert) {
await render(hbs`<Rose::Header::Dropdown::Trigger />`);
assert.ok(find('.rose-header-dropdown-chevron'));
});
});
@@ -0,0 +1,29 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | rose/header/nav', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::Header::nav />`);
assert.ok(find('.rose-header-nav'));
});
test('it renders nav link elements', async function (assert) {
await render(hbs`<Rose::Header::nav as |nav| >
<nav.link @route="index"/>
<nav.link @route="about"/>
</Rose::Header::nav>`);
assert.equal(findAll('a').length, 2);
});
test('it renders nav dropdown elements', async function (assert) {
await render(hbs`<Rose::Header::nav as |nav| >
<nav.dropdown />
<nav.dropdown />
</Rose::Header::nav>`);
assert.equal(findAll('.rose-header-dropdown').length, 2);
});
});
@@ -1,29 +0,0 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | rose/header/navigation', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::Header::Navigation />`);
assert.ok(find('.rose-header-navigation'));
});
test('it renders navigation link elements', async function (assert) {
await render(hbs`<Rose::Header::Navigation as |navigation| >
<navigation.link @route="about"/>
<navigation.link @route="about"/>
</Rose::Header::Navigation>`);
assert.equal(findAll('a').length, 2);
});
test('it renders navigation dropdown elements', async function (assert) {
await render(hbs`<Rose::Header::Navigation as |navigation| >
<navigation.dropdown />
<navigation.dropdown />
</Rose::Header::Navigation>`);
assert.equal(findAll('.rose-header-dropdown').length, 2);
});
});
@@ -11,10 +11,10 @@ module('Integration | Component | rose/header/utilities', function (hooks) {
assert.ok(find('.rose-header-utilities'));
});
test('it renders navigation dropdown elements', async function (assert) {
await render(hbs`<Rose::Header::Utilities as |navigation| >
<navigation.dropdown />
<navigation.dropdown />
test('it renders nav dropdown elements', async function (assert) {
await render(hbs`<Rose::Header::Utilities as |nav| >
<nav.dropdown />
<nav.dropdown />
</Rose::Header::Utilities>`);
assert.equal(findAll('.rose-header-dropdown').length, 2);
});
+4 -5
View File
@@ -1,9 +1,8 @@
<HeadLayout />
<Rose::Header as |header|>
<header.brand />
</Rose::Header>
{{title (app-name)}}
<Rose::Header as |header|>
<header.brand @logo="logo" @text={{app-name}} />
</Rose::Header>
{{outlet}}
+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.278 9.313a2.021 2.021 0 0 1-2.01-.951c-.552-.928-.224-2.115.732-2.65.956-.536 2.179-.218 2.732.71.407.684.346 1.54-.155 2.164l2.354 3.955c2.02-.731 4.352.038 5.466 1.91 1.242 2.087.504 4.76-1.647 5.964a4.608 4.608 0 0 1-3.51.41l.474-1.383c.77.204 1.594.1 2.286-.288 1.434-.804 1.925-2.583 1.098-3.975a3.006 3.006 0 0 0-2.193-1.429 3.059 3.059 0 0 0-2.507.807l-3.12-5.244zm3.367 6.592a2.003 2.003 0 0 1 1.855-1.213c1.104 0 2 .87 2 1.94 0 1.072-.896 1.94-2 1.941-.815 0-1.549-.48-1.855-1.213h-4.707C10.58 19.424 8.728 21 6.5 21c-2.484 0-4.5-1.957-4.5-4.366 0-1.24.533-2.358 1.389-3.153l.997 1.088a2.859 2.859 0 0 0-.886 2.065c0 1.607 1.343 2.91 3 2.91.928 0 1.803-.417 2.371-1.128a2.844 2.844 0 0 0 .534-2.51h6.24zm-7.568-.49c.501.623.562 1.479.155 2.164-.553.927-1.775 1.245-2.732.71-.956-.536-1.284-1.723-.732-2.65a2.022 2.022 0 0 1 2.01-.953l2.354-3.955c-1.664-1.332-2.143-3.676-1.03-5.548C9.345 3.096 12.1 2.38 14.25 3.585a4.37 4.37 0 0 1 2.12 2.744l-1.47.293a2.915 2.915 0 0 0-1.4-1.777c-1.435-.802-3.269-.326-4.098 1.065a2.835 2.835 0 0 0-.179 2.557 2.973 2.973 0 0 0 1.974 1.704l-3.12 5.243z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB