chore: 🤖 Introduce main content header rose component
Main content header component contains a title(basic & dropdown) with actions.
This commit is contained in:
15 files changed
+345
-7
No files matched your search
@@ -0,0 +1,7 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../../../templates/components/rose/layout/main/header';
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
tagName: '',
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
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|Layout|Main" component="Header" />
|
||||
|
||||
# Layout | Main Header
|
||||
|
||||
A header component for main layout component with ability to configure title and action buttons. Title can be configured to be a either a dropdown component or a simple heading title. The title dropdown is `Rose::Header::Dropdown` component where as all header actions are `Rose::Button` components.
|
||||
|
||||
```html
|
||||
<Rose::Layout::Main::Header as |header|>
|
||||
<header.title as |title|>
|
||||
<title.dropdown @text="Page title" 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>
|
||||
</title.dropdown>
|
||||
</header.title>
|
||||
<header.action @style="secondary">Secondary</header.action>
|
||||
<header.action @style="primary">Primary</header.action>
|
||||
</Rose::Layout::Main::Header>
|
||||
```
|
||||
|
||||
<Preview>
|
||||
<Story name="Main Header">{{
|
||||
template: hbs`
|
||||
<Rose::Layout::Main::Header as |header|>
|
||||
<header.title as |title|>
|
||||
<title.dropdown
|
||||
@text={{pagetitle}}
|
||||
@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>
|
||||
</title.dropdown>
|
||||
</header.title>
|
||||
<header.action @style="secondary">Secondary</header.action>
|
||||
<header.action @style="primary">Primary</header.action>
|
||||
</Rose::Layout::Main::Header>
|
||||
`,
|
||||
context: {
|
||||
pagetitle: text('page title', 'Page Section Title'),
|
||||
dropdownIcon: select(
|
||||
'dropdown icon',
|
||||
{
|
||||
'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>
|
||||
|
||||
Main page title can configured to be a basic title.
|
||||
|
||||
```html
|
||||
<Rose::Layout::Main::Header as |header|>
|
||||
<header.title @text="Page title" @icon="user-square-fill"/>
|
||||
</Rose::Layout::Main::Header>
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../../../../templates/components/rose/layout/main/header/title';
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
tagName: '',
|
||||
});
|
||||
@@ -1,22 +1,32 @@
|
||||
@use 'sass:map';
|
||||
@use '../../variables/sizing';
|
||||
@use '../../utilities/type';
|
||||
@use '../../utilities/dropdown';
|
||||
|
||||
$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>",
|
||||
@include dropdown.dropdown-style(
|
||||
'light',
|
||||
(
|
||||
color: var(--white),
|
||||
chevron: 'light',
|
||||
)
|
||||
);
|
||||
|
||||
@include dropdown.dropdown-style(
|
||||
'dark',
|
||||
(
|
||||
color: var(--black),
|
||||
chevron: 'dark',
|
||||
)
|
||||
);
|
||||
|
||||
.rose-header-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.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);
|
||||
@@ -68,6 +78,7 @@ $dropdown-chevron: (
|
||||
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 {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@use '../../variables/sizing';
|
||||
@use '../../variables/media';
|
||||
|
||||
@import 'main';
|
||||
|
||||
.rose-layout-page {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@import 'header';
|
||||
@@ -0,0 +1,75 @@
|
||||
@use '../../../../utilities/type' as typography;
|
||||
@use '../../../../variables/sizing';
|
||||
@use '../../../../variables/media';
|
||||
|
||||
.rose-layout-main-header {
|
||||
display: grid;
|
||||
grid-template-areas: 'title title actions';
|
||||
grid-template-columns: 1fr auto;
|
||||
|
||||
.rose-layout-main-header-title {
|
||||
display: inline-block;
|
||||
grid-area: title;
|
||||
|
||||
.rose-layout-main-header-title-text {
|
||||
display: inline-block;
|
||||
color: var(--stark);
|
||||
}
|
||||
|
||||
.rose-layout-main-header-title-icon {
|
||||
vertical-align: sub;
|
||||
|
||||
svg {
|
||||
width: sizing.rems(l);
|
||||
width: sizing.rems(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rose-header-dropdown {
|
||||
.rose-header-dropdown-trigger-text {
|
||||
// Apply heading level 1 style to text to fit page section style spec
|
||||
// Duplicate of h1 style defined in typography
|
||||
@include typography.type(h1, bold);
|
||||
margin-top: sizing.rems(m);
|
||||
margin-bottom: sizing.rems(xs);
|
||||
|
||||
margin-right: sizing.rems(xs);
|
||||
}
|
||||
|
||||
.rose-header-dropdown-trigger {
|
||||
background-size: sizing.rems(l);
|
||||
background-position: right (sizing.rems(m) + sizing.rems(s));
|
||||
}
|
||||
|
||||
.rose-icon > svg {
|
||||
vertical-align: sub;
|
||||
}
|
||||
}
|
||||
|
||||
.rose-layout-main-header-actions {
|
||||
grid-area: actions;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
// Remove margin to align action buttons with h1 text
|
||||
button {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: media.width(small)) {
|
||||
.rose-layout-main-header {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr auto;
|
||||
grid-template-areas:
|
||||
'title'
|
||||
'actions';
|
||||
|
||||
.rose-layout-main-header-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
@use 'sass:map';
|
||||
|
||||
$dropdown-chevron: (
|
||||
'light':
|
||||
"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>",
|
||||
'dark':
|
||||
"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>",
|
||||
);
|
||||
|
||||
@mixin dropdown-style($name, $default) {
|
||||
.rose-header-dropdown-#{$name} {
|
||||
.rose-header-dropdown-trigger {
|
||||
color: map.get($default, color);
|
||||
background: url(map.get($dropdown-chevron, map.get($default, chevron)))
|
||||
no-repeat right center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<details
|
||||
...attributes
|
||||
class="rose-header-dropdown{{if @alignRight " dropdown-right"}}"
|
||||
class="rose-header-dropdown{{if @alignRight " dropdown-right"}} {{if @style (concat "rose-header-dropdown-" @style) "rose-header-dropdown-light"}}"
|
||||
{{on-click-outside this.close}}
|
||||
>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<div ...attributes class="rose-layout-main-header" >
|
||||
{{yield (hash
|
||||
title=(component 'rose/layout/main/header/title')
|
||||
)}}
|
||||
|
||||
<div class="rose-layout-main-header-actions">
|
||||
{{yield (hash
|
||||
action=(component 'rose/button')
|
||||
)}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<div ...attributes
|
||||
class="rose-layout-main-header-title"
|
||||
>
|
||||
{{#if (has-block)}}
|
||||
{{yield (hash
|
||||
dropdown=(component 'rose/header/dropdown' style='dark')
|
||||
)}}
|
||||
{{else}}
|
||||
<section class="rose-layout-main-header-title-section">
|
||||
{{#if @icon}}
|
||||
<span class="rose-layout-main-header-title-icon">{{inline-svg @icon}}</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if @text}}
|
||||
<h1 class="rose-layout-main-header-title-text">{{@text}}</h1>
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/layout/main/header';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/layout/main/header/title';
|
||||
@@ -0,0 +1,60 @@
|
||||
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/layout/main/header', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header />`);
|
||||
assert.ok(find('.rose-layout-main-header'));
|
||||
});
|
||||
|
||||
test('it renders with title', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header as |header|>
|
||||
<header.title @text="header title" />
|
||||
</Rose::Layout::Main::Header>`);
|
||||
assert.ok(find('.rose-layout-main-header-title'));
|
||||
assert.equal(
|
||||
find('.rose-layout-main-header-title').textContent.trim(),
|
||||
'header title'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with dropdown title', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header as |header|>
|
||||
<header.title as |title|>
|
||||
<title.dropdown @text="Dropdown title" />
|
||||
</header.title>
|
||||
</Rose::Layout::Main::Header>`);
|
||||
assert.ok(find('.rose-header-dropdown'));
|
||||
assert.equal(
|
||||
find('.rose-layout-main-header-title').textContent.trim(),
|
||||
'Dropdown title'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with dropdown title menu', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header as |header|>
|
||||
<header.title as |title|>
|
||||
<title.dropdown as |dropdown|>
|
||||
<dropdown.link @route="index">Menu item</dropdown.link>
|
||||
<dropdown.button>Button menu item</dropdown.button>
|
||||
</title.dropdown>
|
||||
</header.title>
|
||||
</Rose::Layout::Main::Header>`);
|
||||
assert.ok(find('.rose-header-dropdown'));
|
||||
assert.equal(findAll('a').length, 1);
|
||||
assert.equal(findAll('button').length, 1);
|
||||
});
|
||||
|
||||
test('it renders with actions', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header as |header|>
|
||||
<header.action />
|
||||
<header.action />
|
||||
</Rose::Layout::Main::Header>`);
|
||||
assert.ok(find('.rose-layout-main-header-actions'));
|
||||
assert.ok(findAll('button').length, 2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
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/layout/main/header/title', function (
|
||||
hooks
|
||||
) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header::Title />`);
|
||||
assert.ok(find('.rose-layout-main-header-title'));
|
||||
});
|
||||
|
||||
test('it renders with title', async function (assert) {
|
||||
await render(
|
||||
hbs`<Rose::Layout::Main::Header::Title @text="header title" />`
|
||||
);
|
||||
assert.equal(
|
||||
find('.rose-layout-main-header-title-text').textContent.trim(),
|
||||
'header title'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with title as dropdown', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header::Title as |title|>
|
||||
<title.dropdown @text="header title" as |dropdown|>
|
||||
<dropdown.link @route="index">Menu item</dropdown.link>
|
||||
<dropdown.link @route="index">Menu item</dropdown.link>
|
||||
<dropdown.button>Button menu item</dropdown.button>
|
||||
<dropdown.button>Button menu item</dropdown.button>
|
||||
<dropdown.button>Button menu item</dropdown.button>
|
||||
</title.dropdown>
|
||||
</Rose::Layout::Main::Header::Title>`);
|
||||
assert.equal(
|
||||
find('.rose-header-dropdown-trigger').textContent.trim(),
|
||||
'header title'
|
||||
);
|
||||
assert.equal(findAll('a').length, 2);
|
||||
assert.equal(findAll('button').length, 3);
|
||||
});
|
||||
|
||||
test('it opens title dropdown on click', async function (assert) {
|
||||
await render(hbs`<Rose::Layout::Main::Header::Title id="title" as |title|>
|
||||
<title.dropdown id="dropdown" as |dropdown|>
|
||||
<dropdown.link @route="index">Menu item</dropdown.link>
|
||||
<dropdown.link @route="index">Menu item</dropdown.link>
|
||||
<dropdown.button>Button menu item</dropdown.button>
|
||||
<dropdown.button>Button menu item</dropdown.button>
|
||||
<dropdown.button>Button menu item</dropdown.button>
|
||||
</title.dropdown>
|
||||
</Rose::Layout::Main::Header::Title>`);
|
||||
await click('summary');
|
||||
assert.ok(find('#dropdown').open);
|
||||
await click('#title');
|
||||
assert.notOk(find('#dropdown').open);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user