diff --git a/addons/rose/addon/components/rose/nav/sidebar.js b/addons/rose/addon/components/rose/nav/sidebar.js new file mode 100644 index 000000000..40b8900e4 --- /dev/null +++ b/addons/rose/addon/components/rose/nav/sidebar.js @@ -0,0 +1,11 @@ +import Component from '@ember/component'; +import layout from '../../../templates/components/rose/nav/sidebar'; +import ComponentAutoId from '../../../mixins/component-auto-id'; + +/** + * A simple navigation component intended for the sidebar layout region. + */ +export default Component.extend(ComponentAutoId, { + layout, + tagName: '', +}); diff --git a/addons/rose/addon/components/rose/nav/sidebar.stories.mdx b/addons/rose/addon/components/rose/nav/sidebar.stories.mdx new file mode 100644 index 000000000..353cf0e86 --- /dev/null +++ b/addons/rose/addon/components/rose/nav/sidebar.stories.mdx @@ -0,0 +1,34 @@ +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'; + + + +# Sidebar Navigation + +A simple navigation component intended for use in the sidebar layout region. + + + {{ + template: hbs` + + Item Name + Item Name + + `, + context: { + title: text('title', 'Title'), + }, + }} + + +_Note_: do not assign the `active` class directly. This is done by the link +component automatically. We use it in this story for illustrative purposes. + +```htmlbars + + Item Name + Item Name + +``` diff --git a/addons/rose/addon/components/rose/notification.js b/addons/rose/addon/components/rose/notification.js index 2741d7f66..f1df73616 100644 --- a/addons/rose/addon/components/rose/notification.js +++ b/addons/rose/addon/components/rose/notification.js @@ -20,8 +20,9 @@ export default Component.extend({ get icon() { let icon = 'info-circle-fill'; - switch (this.style) { - case 'failure': + + switch(this.style) { + case 'error': icon = 'cancel-square-fill'; break; case 'success': diff --git a/addons/rose/addon/components/rose/notification.stories.mdx b/addons/rose/addon/components/rose/notification.stories.mdx index 04cd3f533..a20532158 100644 --- a/addons/rose/addon/components/rose/notification.stories.mdx +++ b/addons/rose/addon/components/rose/notification.stories.mdx @@ -8,7 +8,7 @@ import { text, boolean, select } from '@storybook/addon-knobs'; # Notification Notifications come in four semantic styles: `info` (default), `success`, -`warning`, and `failure`. Heading text is required. Additional block content +`warning`, and `error`. Heading text is required. Additional block content is optional. Notifications may be made dismissible by passing a `@dismiss` function into @@ -69,7 +69,7 @@ handler takes appropriate action and hides the notification. Careful now. + + {{#if @title}} + + {{@title}} + + {{/if}} + + {{yield (hash + link=(component "link-to" class="rose-nav-link") + )}} + + diff --git a/addons/rose/app/components/rose/nav/sidebar.js b/addons/rose/app/components/rose/nav/sidebar.js new file mode 100644 index 000000000..c8316dbfa --- /dev/null +++ b/addons/rose/app/components/rose/nav/sidebar.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/nav/sidebar'; diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs index fe5cfac6e..ec3965039 100644 --- a/addons/rose/tests/dummy/app/templates/index.hbs +++ b/addons/rose/tests/dummy/app/templates/index.hbs @@ -1,190 +1,202 @@ - + + + - + - - - Menu item - Menu item - Button menu item - - Section - Section - + + + Menu item + Menu item + Button menu item + + Section + Section + - - + + Menu item + Menu item + Button menu item + + + + + + + + + Item Name + Item Name + Item Name + Item Name + + + + + + Dummy app index template + + - Menu item - Menu item - Button menu item - - + An error occurred. + - +

Icon

+ + + -Dummy app index template +

Button

+ Primary +
+ Primary +
- - An error occurred. - + Secondary +
+ Secondary +
-

Icon

- - - +

Form

+ -

Button

-Primary -
-Primary -
+

Inputs

+ + + + -Secondary -
-Secondary -
+

Textareas

+ + + + -

Form

- +

Selects

+ + + Choose an option + Value 1 + Value 2 + Value 3 + + + Inputs - - - - - -

Textareas

- - - - - -

Selects

- - - Choose an option - Value 1 - Value 2 - Value 3 - - - - - Choose an option - - + @label="Label" + @helperText="Helper text" + @onChange={{fn this.actionHandler}} + as |field| + > + + Choose an option + +
-

Radio Group

- - - - - +

Radio Group

+ + + + + -

Checkboxes

- - - - - +

Checkboxes

+ + + + + - -
+ +
- - - - - - - + + + + + diff --git a/addons/rose/tests/integration/components/rose/nav/sidebar-test.js b/addons/rose/tests/integration/components/rose/nav/sidebar-test.js new file mode 100644 index 000000000..dfda90191 --- /dev/null +++ b/addons/rose/tests/integration/components/rose/nav/sidebar-test.js @@ -0,0 +1,33 @@ +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/nav/sidebar', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + assert.expect(2) + await render(hbs` + + Item Name + + `); + assert.equal(find('.rose-nav-title').textContent.trim(), 'Title'); + assert.equal(find('.rose-nav-link').textContent.trim(), 'Item Name'); + }); + + test('it associates title with nav via aria-labelledby', async function (assert) { + assert.expect(2) + await render(hbs` + + Item Name + + `); + const el = find('.rose-nav-sidebar'); + const id = el.id; + const titleId = `title-${id}`; + assert.equal(find('.rose-nav-title').id, titleId); + assert.equal(el.getAttribute('aria-labelledby').trim(), titleId); + }); +}); diff --git a/ui/core/app/components/form/project/index.hbs b/ui/core/app/components/form/project/index.hbs index 3842fc226..7541f8ed5 100644 --- a/ui/core/app/components/form/project/index.hbs +++ b/ui/core/app/components/form/project/index.hbs @@ -1,44 +1,30 @@ -
+ - + readonly={{false}} /> - + readonly={{false}} /> -
- - {{t 'actions.save'}} - + - - {{t 'actions.cancel'}} - -
- +
diff --git a/ui/core/app/routes/orgs/org/projects/index.js b/ui/core/app/routes/orgs/org/projects/index.js index 717ffbac5..be2068f6d 100644 --- a/ui/core/app/routes/orgs/org/projects/index.js +++ b/ui/core/app/routes/orgs/org/projects/index.js @@ -1,9 +1,3 @@ import Route from '@ember/routing/route'; -import { action } from '@ember/object'; -export default class OrgsOrgProjectsIndexRoute extends Route { - @action - new() { - this.transitionTo('orgs.org.projects.new'); - } -} +export default class OrgsOrgProjectsIndexRoute extends Route {} diff --git a/ui/core/app/routes/orgs/org/projects/new.js b/ui/core/app/routes/orgs/org/projects/new.js index d8ffa28ae..23012cbfa 100644 --- a/ui/core/app/routes/orgs/org/projects/new.js +++ b/ui/core/app/routes/orgs/org/projects/new.js @@ -1,7 +1,12 @@ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; import { action } from '@ember/object'; export default class OrgsOrgProjectsNewRoute extends Route { + // =services + + @service notify; + // =methods /** @@ -31,15 +36,15 @@ export default class OrgsOrgProjectsNewRoute extends Route { * @param {Event} e */ @action - async save(project, e) { - // Prevent default behavior, since this was trigger from a form submission. - e.preventDefault(); + async save(project) { try { await project.save(); this.transitionTo('orgs.org.projects.project', project); + // TODO: replace with translated strings + this.notify.success('Project created.'); } catch (e) { - // TODO: error handling - throw e; + // TODO: replace with translated strings + this.notify.error('Project could not be created.', {closeAfter: null}); } } } diff --git a/ui/core/app/routes/orgs/org/projects/project.js b/ui/core/app/routes/orgs/org/projects/project.js index 5441e1aaa..a6b9947bf 100644 --- a/ui/core/app/routes/orgs/org/projects/project.js +++ b/ui/core/app/routes/orgs/org/projects/project.js @@ -6,6 +6,7 @@ export default class OrgsOrgProjectsProjectRoute extends Route { // =services @service scope; + @service notify; // =methods @@ -44,14 +45,14 @@ export default class OrgsOrgProjectsProjectRoute extends Route { * @param {Event} e */ @action - async save(project, e) { - // Prevent default behavior, since this was trigger from a form submission. - e.preventDefault(); + async save(project) { try { await project.save(); + // TODO: replace with translated strings + this.notify.success('Save succeeded.'); } catch (e) { - // TODO: error handling - throw e; + // TODO: replace with translated strings + this.notify.error('An error occurred.', {closeAfter: null}); } } @@ -64,9 +65,11 @@ export default class OrgsOrgProjectsProjectRoute extends Route { try { await project.destroyRecord(); this.transitionTo('orgs.org.projects'); + // TODO: replace with translated strings + this.notify.success('Deleted project succesfully.'); } catch (e) { - // TODO: error handling - throw e; + // TODO: replace with translated strings + this.notify.error('An error occurred.', {closeAfter: null}); } } } diff --git a/ui/core/app/styles/_notify.scss b/ui/core/app/styles/_notify.scss new file mode 100644 index 000000000..df7e9b58e --- /dev/null +++ b/ui/core/app/styles/_notify.scss @@ -0,0 +1,89 @@ +/** + * This file is essentially a rip of the default ember notify styles, with some + * minor tweaks for positioning. + */ + +@-webkit-keyframes "ember-notify-show" { + from { + left: -310px; + } + to { + left: 0; + } +} +@keyframes "ember-notify-show" { + from { + left: -310px; + } + to { + left: 0; + } +} +@-webkit-keyframes "ember-notify-hide" { + from { + left: 0; + } + to { + left: -310px; + } +} +@keyframes "ember-notify-hide" { + from { + left: 0; + } + to { + left: -310px; + } +} +.ember-notify-cn { + position: fixed; + z-index: 5000; + bottom: 0px; + left: 0px; +} +.ember-notify { + display: block; + margin: 10px; + position: relative; + width: 300px; + box-sizing: border-box; + //border: 1px solid #ccc; + //padding: 10px; + .close { + float: right; + cursor: pointer; + } +} +.ember-notify-show { + -webkit-animation-duration: 500ms; + animation-duration: 500ms; + -webkit-animation-name: ember-notify-show; + animation-name: ember-notify-show; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); +} +.ember-notify-hide { + -webkit-animation-duration: 250ms; + animation-duration: 250ms; + -webkit-animation-name: ember-notify-hide; + animation-name: ember-notify-hide; + -webkit-animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045); + animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045); + &:hover { + -webkit-animation-play-state: paused; + -moz-animation-play-state: paused; + -o-animation-play-state: paused; + animation-play-state: paused; + } +} +@media only screen and (max-width: 680px) { + .ember-notify-cn { + width: 100%; + padding-left: 20px; + box-sizing: border-box; + } + .ember-notify { + width: 100%; + float: right; + } +} diff --git a/ui/core/app/styles/app.scss b/ui/core/app/styles/app.scss index 4b6c7f9a3..667cc9039 100644 --- a/ui/core/app/styles/app.scss +++ b/ui/core/app/styles/app.scss @@ -1,7 +1,26 @@ +@use 'rose/variables/sizing'; + @import 'rose'; +@import 'notify'; + +.rose-nav-sidebar { + margin-top: sizing.rems(xxl); +} + +.rose-form { + width: 66%; + + .rose-form-input { + width: 100%; + + .rose-form-input-field { + width: 100%; + } + } +} .projects-overview { - margin: 0 10px; + margin: sizing.rems(l); .projects { display: flex; @@ -17,25 +36,10 @@ } } -.project-new-link { - cursor: pointer; - text-decoration: none; - padding: 10px; - color: #ffffff; - text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25); - background: #1563ff; - border: 1px solid #082766; - box-sizing: border-box; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); - border-radius: 2px; - position: relative; - float: right; - top: -50px; -} - .project-overview, .project-new { - margin: 0 10px; + margin: sizing.rems(l); + .breadcrumbs, .breadcrumbs a { color: #6f7682; diff --git a/ui/core/app/templates/application.hbs b/ui/core/app/templates/application.hbs index ff8c801fe..5d66d58f1 100644 --- a/ui/core/app/templates/application.hbs +++ b/ui/core/app/templates/application.hbs @@ -1,14 +1,53 @@ + + +{{title (app-name)}} + - + + + + {{t 'dev.menu-item'}} + {{t 'dev.menu-item'}} + {{t 'dev.button-menu-item'}} + + {{t 'dev.section'}} + {{t 'dev.section'}} + + + + {{t 'dev.menu-item'}} + {{t 'dev.button-menu-item'}} + + - {{title (app-name)}} {{outlet}} - + + + {{t 'dev.item-name'}} + {{t 'dev.item-name'}} + + + + + + + {{message.text}} + + diff --git a/ui/core/app/templates/orgs/org/projects.hbs b/ui/core/app/templates/orgs/org/projects.hbs index c24cd6895..1f709c7f7 100644 --- a/ui/core/app/templates/orgs/org/projects.hbs +++ b/ui/core/app/templates/orgs/org/projects.hbs @@ -1 +1,3 @@ +{{title (t "resources.projects")}} + {{outlet}} diff --git a/ui/core/app/templates/orgs/org/projects/index.hbs b/ui/core/app/templates/orgs/org/projects/index.hbs index 312844b0d..c2fb6fa59 100644 --- a/ui/core/app/templates/orgs/org/projects/index.hbs +++ b/ui/core/app/templates/orgs/org/projects/index.hbs @@ -1,13 +1,11 @@
+

{{t "resources.projects"}}

- - {{t 'titles.new'}} - + + + + {{t "titles.new"}} +
    {{#each @model as |project|}} diff --git a/ui/core/app/templates/orgs/org/projects/new.hbs b/ui/core/app/templates/orgs/org/projects/new.hbs index 8a101b818..920983b02 100644 --- a/ui/core/app/templates/orgs/org/projects/new.hbs +++ b/ui/core/app/templates/orgs/org/projects/new.hbs @@ -1,3 +1,5 @@ +{{title (t "titles.new")}} +