chore: 🤖 Separate page header into layout & content components

Introduce new PageHeader layout, title, and utilities components
This commit is contained in:
Susmitha Girumala committed 2020-06-05 14:46:23 -04:00
1 parent 33d85940bf
commit 1a8a0d062f
27 files changed
+237 -265

No files matched your search

@@ -1,66 +0,0 @@
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>
```
@@ -6,7 +6,7 @@ import { action } from '@storybook/addon-actions';
# Layout|Page Header
A page layout/positioning component that configures a single column grid for use as a header.
A page layout/positioning component that configures a two column grid with defined areas: title and utilities
```htmlbars
<Rose::Layout::PageHeader />
@@ -15,8 +15,9 @@ A page layout/positioning component that configures a single column grid for use
<Preview>
<Story name="Page Header">{{
template: hbs`
<Rose::Layout::PageHeader style="background: var(--brand-gray-5); padding: 1rem;">
Page Header
<Rose::Layout::PageHeader>
<div style="grid-area: title; background: var(--brand-gray-7)">Grid area: title</div>
<div style="grid-area: utilities; background: var(--brand-gray-5)">Grid area: utilities</div>
</Rose::Layout::PageHeader>
`,
}}</Story>
@@ -1,5 +1,5 @@
import Component from '@ember/component';
import layout from '../../../../templates/components/rose/layout/main/header';
import layout from '../../../templates/components/rose/page-header/title';
export default Component.extend({
layout,
@@ -0,0 +1,62 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { action } from '@storybook/addon-actions';
import { text, select } from '@storybook/addon-knobs';
<Meta title="Rose|PageHeader|Title" component="Title" />
# Title
A header title component with ability to configure dropdown using `Rose::Header::Dropdown`. To be used with `Rose::Layout::PageHeader`.
```html
<Rose::Layout::PageHeader style="border: 1px solid var(--brand-gray-5);">
<Rose::PageHeader::Title as |title| >
<title.dropdown
@text="Page Header Title"
@icon="user-square-fill" as |dropdown|
>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.button>Button menu item</dropdown.button>
</title.dropdown>
</Rose::PageHeader::Title>
</Rose::Layout::PageHeader>
```
<Preview>
<Story name="Title">{{
template: hbs`
<Rose::Layout::PageHeader style="border: 1px solid var(--brand-gray-5);">
<Rose::PageHeader::Title as |title| >
<title.dropdown
@text={{pageTitle}}
@icon={{dropdownIcon}} as |dropdown|
>
<dropdown.link @route="index">Menu item</dropdown.link>
<dropdown.button>Button menu item</dropdown.button>
</title.dropdown>
</Rose::PageHeader::Title>
</Rose::Layout::PageHeader>
`,
context: {
pageTitle: text('pageTitle', 'Page Section Title'),
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>
Title can be configured to be a level 1 heading with optional icon.
```html
<Rose::PageHeader::Title @text="Page Header Title" @icon="alert-circle-fill" />
```
@@ -1,5 +1,5 @@
import Component from '@ember/component';
import layout from '../../../../../templates/components/rose/layout/main/header/title';
import layout from '../../../templates/components/rose/page-header/utilities';
export default Component.extend({
layout,
@@ -0,0 +1,31 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { action } from '@storybook/addon-actions';
<Meta title="Rose|PageHeader" component="Utilities" />
# Utilities
An component comprised of `Rose::Button` as actions. To be used with `Rose::Layout::PageHeader`.
```html
<Rose::Layout::PageHeader style="border: 1px solid var(--brand-gray-5);">
<Rose::PageHeader::Utilities as |utilities|>
<utilities.action @style="secondary">Secondary</utilities.action>
<utilities.action @style="primary">Primary</utilities.action>
</Rose::PageHeader::Utilities>
</Rose::Layout::PageHeader>
```
<Preview>
<Story name="Utilities">{{
template: hbs`
<Rose::Layout::PageHeader style="border: 1px solid var(--brand-gray-5);">
<Rose::PageHeader::Utilities as |utilities|>
<utilities.action @style="secondary">Secondary</utilities.action>
<utilities.action @style="primary">Primary</utilities.action>
</Rose::PageHeader::Utilities>
</Rose::Layout::PageHeader>
`,
}}</Story>
</Preview>
@@ -4,4 +4,4 @@
@import 'layout';
@import 'notification';
@import 'card';
@import 'nav';
@import 'page-header';
@@ -1,7 +1,6 @@
@use '../../variables/sizing';
@use '../../variables/media';
@import 'main';
@import 'page-header';
.rose-layout-page {
@@ -2,16 +2,21 @@
.rose-layout-page-header {
display: grid;
grid-template-areas:
'title title utilities';
grid-template-areas: 'title title utilities';
grid-template-columns: 1fr auto;
grid-template-rows: auto 1fr;
.rose-page-header-utilities {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
}
}
@media (max-width: media.width(small)) {
.rose-layout-page {
.rose-layout-page-header {
grid-template-columns: 1fr;
grid-template-rows: 0.1fr auto 1fr;
grid-template-areas:
'title'
'utilities';
@@ -1 +0,0 @@
@import 'header';
@@ -1,75 +0,0 @@
@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,2 @@
@import 'title';
@import 'utilities';
@@ -0,0 +1,35 @@
@use '../../variables/sizing';
@use '../../utilities/type' as typography;
.rose-page-header-title {
display: inline-block;
grid-area: title;
.rose-page-header-title-text {
display: inline-block;
color: var(--stark);
}
.rose-page-header-title-icon {
vertical-align: sub;
svg {
width: sizing.rems(l);
width: sizing.rems(l);
}
}
.rose-header-dropdown {
.rose-header-dropdown-trigger-text {
@include typography.type(h1, bold);
margin-top: sizing.rems(xs);
margin-bottom: sizing.rems(xs);
margin-right: sizing.rems(xs);
}
.rose-header-dropdown-trigger {
background-size: sizing.rems(l);
}
}
}
@@ -0,0 +1,8 @@
.rose-page-header-utilities {
grid-area: utilities;
// Remove margin to align action buttons with h1 text
button {
margin-bottom: 0;
}
}
@@ -1,11 +0,0 @@
<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>
@@ -1,19 +0,0 @@
<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,17 @@
<div ...attributes class="rose-page-header-title">
{{#if (has-block)}}
{{yield (hash
dropdown=(component 'rose/header/dropdown' style='dark')
)}}
{{else}}
<section class="rose-page-header-title-section">
{{#if @icon}}
<span class="rose-page-header-title-icon">{{inline-svg @icon}}</span>
{{/if}}
{{#if @text}}
<h1 class="rose-page-header-title-text">{{@text}}</h1>
{{/if}}
</section>
{{/if}}
</div>
@@ -0,0 +1,5 @@
<div ...attributes class="rose-page-header-utilities">
{{yield (hash
action=(component 'rose/button')
)}}
</div>
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/layout/main/header';
@@ -1 +0,0 @@
export { default } from 'rose/components/rose/layout/main/header/title';
@@ -1 +1 @@
export { default } from 'rose/components/rose/layout/page-header';
export { default } from 'rose/components/rose/layout/page-header';
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/page-header/title';
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/page-header/utilities';
@@ -1,60 +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/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);
});
});
@@ -3,21 +3,26 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | rose/layout/page-header', function(hooks) {
module('Integration | Component | rose/layout/page-header', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
test('it renders', async function (assert) {
await render(hbs`<Rose::Layout::PageHeader />`);
assert.ok(find('.rose-layout-page-header'));
});
test('it renders with html attributes', async function(assert) {
test('it renders with html attributes', async function (assert) {
await render(hbs`<Rose::Layout::PageHeader id="page-header"/>`);
assert.ok(find('#page-header'));
});
test('it renders with content', async function(assert) {
await render(hbs`<Rose::Layout::PageHeader>Page header content</Rose::Layout::PageHeader>`);
assert.equal(find('.rose-layout-page-header').textContent.trim(), 'Page header content');
test('it renders with content', async function (assert) {
await render(
hbs`<Rose::Layout::PageHeader>Page header content</Rose::Layout::PageHeader>`
);
assert.equal(
find('.rose-layout-page-header').textContent.trim(),
'Page header content'
);
});
});
@@ -3,28 +3,24 @@ 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
) {
module('Integration | Component | rose/page-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'));
await render(hbs`<Rose::PageHeader::Title />`);
assert.ok(find('.rose-page-header-title'));
});
test('it renders with title', async function (assert) {
await render(
hbs`<Rose::Layout::Main::Header::Title @text="header title" />`
);
await render(hbs`<Rose::PageHeader::Title @text="header title" />`);
assert.equal(
find('.rose-layout-main-header-title-text').textContent.trim(),
find('.rose-page-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|>
await render(hbs`<Rose::PageHeader::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>
@@ -32,7 +28,7 @@ module('Integration | Component | rose/layout/main/header/title', function (
<dropdown.button>Button menu item</dropdown.button>
<dropdown.button>Button menu item</dropdown.button>
</title.dropdown>
</Rose::Layout::Main::Header::Title>`);
</Rose::PageHeader::Title>`);
assert.equal(
find('.rose-header-dropdown-trigger').textContent.trim(),
'header title'
@@ -42,7 +38,7 @@ module('Integration | Component | rose/layout/main/header/title', function (
});
test('it opens title dropdown on click', async function (assert) {
await render(hbs`<Rose::Layout::Main::Header::Title id="title" as |title|>
await render(hbs`<Rose::PageHeader::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>
@@ -50,7 +46,7 @@ module('Integration | Component | rose/layout/main/header/title', function (
<dropdown.button>Button menu item</dropdown.button>
<dropdown.button>Button menu item</dropdown.button>
</title.dropdown>
</Rose::Layout::Main::Header::Title>`);
</Rose::PageHeader::Title>`);
await click('summary');
assert.ok(find('#dropdown').open);
await click('#title');
@@ -0,0 +1,38 @@
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/page-header/utilities', function (
hooks
) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`<Rose::PageHeader::Utilities />`);
assert.ok(find('.rose-page-header-utilities'));
});
test('it renders with attributes', async function (assert) {
await render(hbs`<Rose::PageHeader::Utilities id="utilities"/>`);
assert.ok(find('#utilities'));
});
test('it renders with content', async function (assert) {
await render(
hbs`<Rose::PageHeader::Utilities>Page header utilities</Rose::PageHeader::Utilities>`
);
assert.equal(
find('.rose-page-header-utilities').textContent.trim(),
'Page header utilities'
);
});
test('it renders utility action buttons', async function (assert) {
await render(hbs`<Rose::PageHeader::Utilities as |utility|>
<utility.action />
<utility.action />
</Rose::PageHeader::Utilities>`);
assert.equal(findAll('button').length, 2);
});
});