diff --git a/addons/rose/addon/components/rose/layout/main/header.js b/addons/rose/addon/components/rose/layout/main/header.js new file mode 100644 index 000000000..74d539127 --- /dev/null +++ b/addons/rose/addon/components/rose/layout/main/header.js @@ -0,0 +1,7 @@ +import Component from '@ember/component'; +import layout from '../../../../templates/components/rose/layout/main/header'; + +export default Component.extend({ + layout, + tagName: '', +}); diff --git a/addons/rose/addon/components/rose/layout/main/header.stories.mdx b/addons/rose/addon/components/rose/layout/main/header.stories.mdx new file mode 100644 index 000000000..3d2dda848 --- /dev/null +++ b/addons/rose/addon/components/rose/layout/main/header.stories.mdx @@ -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'; + + + +# 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 + + + + Menu item + Menu item + Button menu item + + + Secondary + Primary + +``` + + + {{ + template: hbs` + + + + Menu item + Menu item + Button menu item + + + Secondary + Primary + + `, + 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' + ), + }, + }} + + +Main page title can configured to be a basic title. + +```html + + + +``` diff --git a/addons/rose/addon/components/rose/layout/main/header/title.js b/addons/rose/addon/components/rose/layout/main/header/title.js new file mode 100644 index 000000000..285af1ebb --- /dev/null +++ b/addons/rose/addon/components/rose/layout/main/header/title.js @@ -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: '', +}); diff --git a/addons/rose/addon/styles/rose/components/header/_dropdown.scss b/addons/rose/addon/styles/rose/components/header/_dropdown.scss index bf6ab366d..5436c1b57 100644 --- a/addons/rose/addon/styles/rose/components/header/_dropdown.scss +++ b/addons/rose/addon/styles/rose/components/header/_dropdown.scss @@ -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,", +@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 { diff --git a/addons/rose/addon/styles/rose/components/layout/_index.scss b/addons/rose/addon/styles/rose/components/layout/_index.scss index 909cdbc83..33944c8fe 100644 --- a/addons/rose/addon/styles/rose/components/layout/_index.scss +++ b/addons/rose/addon/styles/rose/components/layout/_index.scss @@ -1,6 +1,8 @@ @use '../../variables/sizing'; @use '../../variables/media'; +@import 'main'; + .rose-layout-page { display: grid; grid-template-areas: diff --git a/addons/rose/addon/styles/rose/components/layout/main/_index.scss b/addons/rose/addon/styles/rose/components/layout/main/_index.scss new file mode 100644 index 000000000..6053ec318 --- /dev/null +++ b/addons/rose/addon/styles/rose/components/layout/main/_index.scss @@ -0,0 +1 @@ +@import 'header'; diff --git a/addons/rose/addon/styles/rose/components/layout/main/header/_index.scss b/addons/rose/addon/styles/rose/components/layout/main/header/_index.scss new file mode 100644 index 000000000..6e4be91b6 --- /dev/null +++ b/addons/rose/addon/styles/rose/components/layout/main/header/_index.scss @@ -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; + } + } +} diff --git a/addons/rose/addon/styles/rose/utilities/_dropdown.scss b/addons/rose/addon/styles/rose/utilities/_dropdown.scss new file mode 100644 index 000000000..059d2d56d --- /dev/null +++ b/addons/rose/addon/styles/rose/utilities/_dropdown.scss @@ -0,0 +1,18 @@ +@use 'sass:map'; + +$dropdown-chevron: ( + 'light': + "data:image/svg+xml;utf-8,", + 'dark': + "data:image/svg+xml;utf-8,", +); + +@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; + } + } +} diff --git a/addons/rose/addon/templates/components/rose/header/dropdown.hbs b/addons/rose/addon/templates/components/rose/header/dropdown.hbs index 86406b2d9..cc874c284 100644 --- a/addons/rose/addon/templates/components/rose/header/dropdown.hbs +++ b/addons/rose/addon/templates/components/rose/header/dropdown.hbs @@ -1,6 +1,6 @@
diff --git a/addons/rose/addon/templates/components/rose/layout/main/header.hbs b/addons/rose/addon/templates/components/rose/layout/main/header.hbs new file mode 100644 index 000000000..9634fdb25 --- /dev/null +++ b/addons/rose/addon/templates/components/rose/layout/main/header.hbs @@ -0,0 +1,11 @@ +
+ {{yield (hash + title=(component 'rose/layout/main/header/title') + )}} + +
+ {{yield (hash + action=(component 'rose/button') + )}} +
+
diff --git a/addons/rose/addon/templates/components/rose/layout/main/header/title.hbs b/addons/rose/addon/templates/components/rose/layout/main/header/title.hbs new file mode 100644 index 000000000..1147d6c48 --- /dev/null +++ b/addons/rose/addon/templates/components/rose/layout/main/header/title.hbs @@ -0,0 +1,19 @@ +
+ {{#if (has-block)}} + {{yield (hash + dropdown=(component 'rose/header/dropdown' style='dark') + )}} + {{else}} +
+ {{#if @icon}} + {{inline-svg @icon}} + {{/if}} + + {{#if @text}} +

{{@text}}

+ {{/if}} +
+ {{/if}} +
diff --git a/addons/rose/app/components/rose/layout/main/header.js b/addons/rose/app/components/rose/layout/main/header.js new file mode 100644 index 000000000..cc3ca8769 --- /dev/null +++ b/addons/rose/app/components/rose/layout/main/header.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/layout/main/header'; diff --git a/addons/rose/app/components/rose/layout/main/header/title.js b/addons/rose/app/components/rose/layout/main/header/title.js new file mode 100644 index 000000000..c189eb8d5 --- /dev/null +++ b/addons/rose/app/components/rose/layout/main/header/title.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/layout/main/header/title'; diff --git a/addons/rose/tests/integration/components/rose/layout/main/header-test.js b/addons/rose/tests/integration/components/rose/layout/main/header-test.js new file mode 100644 index 000000000..e2261a66d --- /dev/null +++ b/addons/rose/tests/integration/components/rose/layout/main/header-test.js @@ -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``); + assert.ok(find('.rose-layout-main-header')); + }); + + test('it renders with title', async function (assert) { + await render(hbs` + + `); + 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` + + + + `); + 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` + + + Menu item + Button menu item + + + `); + 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` + + + `); + assert.ok(find('.rose-layout-main-header-actions')); + assert.ok(findAll('button').length, 2); + }); +}); diff --git a/addons/rose/tests/integration/components/rose/layout/main/header/title-test.js b/addons/rose/tests/integration/components/rose/layout/main/header/title-test.js new file mode 100644 index 000000000..8d7fef6ac --- /dev/null +++ b/addons/rose/tests/integration/components/rose/layout/main/header/title-test.js @@ -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``); + assert.ok(find('.rose-layout-main-header-title')); + }); + + test('it renders with title', async function (assert) { + await render( + hbs`` + ); + 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` + + Menu item + Menu item + Button menu item + Button menu item + Button menu item + + `); + 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` + + Menu item + Menu item + Button menu item + Button menu item + Button menu item + + `); + await click('summary'); + assert.ok(find('#dropdown').open); + await click('#title'); + assert.notOk(find('#dropdown').open); + }); +});