diff --git a/addons/rose/addon/components/rose/nav/primary.js b/addons/rose/addon/components/rose/nav/primary.js
new file mode 100644
index 000000000..47266ffef
--- /dev/null
+++ b/addons/rose/addon/components/rose/nav/primary.js
@@ -0,0 +1,11 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/nav/primary';
+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/primary.stories.mdx b/addons/rose/addon/components/rose/nav/primary.stories.mdx
new file mode 100644
index 000000000..36e0e4d4c
--- /dev/null
+++ b/addons/rose/addon/components/rose/nav/primary.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';
+
+
+
+# Primary 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/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss
index bbd2ebcda..ec7bd22af 100644
--- a/addons/rose/addon/styles/rose/components/_index.scss
+++ b/addons/rose/addon/styles/rose/components/_index.scss
@@ -4,3 +4,4 @@
@import 'layout';
@import 'notification';
@import 'card';
+@import 'nav';
diff --git a/addons/rose/addon/styles/rose/components/nav/_index.scss b/addons/rose/addon/styles/rose/components/nav/_index.scss
new file mode 100644
index 000000000..77d16beb5
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/nav/_index.scss
@@ -0,0 +1 @@
+@import "primary";
diff --git a/addons/rose/addon/styles/rose/components/nav/_primary.scss b/addons/rose/addon/styles/rose/components/nav/_primary.scss
new file mode 100644
index 000000000..e91f6de8f
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/nav/_primary.scss
@@ -0,0 +1,40 @@
+@use '../../variables/sizing';
+@use '../../utilities/type';
+
+.rose-nav-primary {
+ $threePX: sizing.rems(xxxs) + sizing.rems(xxxxs);
+
+ .rose-nav-title {
+ @include type.type(xs, semibold);
+ display: block;
+ color: var(--ui-gray);
+ text-transform: uppercase;
+ $lineHeight: 20 / 12;
+ line-height: $lineHeight;
+ padding: 0 sizing.rems(m);
+ margin-bottom: $threePX;
+ border-bottom: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
+ }
+
+ .rose-nav-link {
+ @include type.type(s);
+ display: block;
+ color: var(--stark);
+ $lineHeight: 36 / 14;
+ line-height: $lineHeight;
+ text-decoration: none;
+ padding: 0 sizing.rems(m);
+ border-right: $threePX solid transparent;
+
+ &.active {
+ color: var(--action);
+ border-color: currentColor;
+ }
+
+ &:hover {
+ text-decoration: underline;
+ color: var(--action);
+ background-color: var(--action-subtler-1h);
+ }
+ }
+}
diff --git a/addons/rose/addon/styles/rose/variables/color/_css.scss b/addons/rose/addon/styles/rose/variables/color/_css.scss
index ca6df5c3d..a0644f2e4 100644
--- a/addons/rose/addon/styles/rose/variables/color/_css.scss
+++ b/addons/rose/addon/styles/rose/variables/color/_css.scss
@@ -56,6 +56,7 @@
--success-starker-1: #{color(green, 800)};
--action-subtler-2: #{color(blue, 010)};
+ --action-subtler-1h: #{color(blue, 050)};
--action-subtler-1: #{color(blue, 100)};
--action: #{color(blue)};
--action-starker-1: #{color(blue, 700)};
@@ -115,6 +116,7 @@
--success-starker-1: #{color(green, 200)};
--action-subtler-2: #{color(blue, 900)};
+ --action-subtler-1h: #{color(blue, 800)};
--action-subtler-1: #{color(blue, 800)};
--action: #{color(blue)};
--action-starker-1: #{color(blue, 300)};
diff --git a/addons/rose/addon/templates/components/rose/nav/primary.hbs b/addons/rose/addon/templates/components/rose/nav/primary.hbs
new file mode 100644
index 000000000..dd0406587
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/nav/primary.hbs
@@ -0,0 +1,17 @@
+
diff --git a/addons/rose/app/components/rose/nav/primary.js b/addons/rose/app/components/rose/nav/primary.js
new file mode 100644
index 000000000..adaf57f51
--- /dev/null
+++ b/addons/rose/app/components/rose/nav/primary.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/nav/primary';
\ No newline at end of file
diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs
index e47846e50..0eb142a9b 100644
--- a/addons/rose/tests/dummy/app/templates/index.hbs
+++ b/addons/rose/tests/dummy/app/templates/index.hbs
@@ -1,187 +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
+
+
+
+
+ Button
+ Primary
+
+ Primary
+
+
+ Secondary
+
+ Secondary
+
+
+ Form
+
+
+ Inputs
+
+
+
+
+
+ Textareas
+
+
+
+
+
+ Selects
+
+
+ Choose an option
+ Value 1
+ Value 2
+ Value 3
+
+
+
+
+ Choose an option
+
+
-Dummy app index template
+ Radio Group
+
+
+
+
+
-
- An error occurred.
-
+ Checkboxes
+
+
+
+
+
-Icon
-
-
-
+
+
-Button
-Primary
-
-Primary
-
-
-Secondary
-
-Secondary
-
-
-Form
-
-
- Inputs
-
-
-
-
-
- Textareas
-
-
-
-
-
- Selects
-
-
- Choose an option
- Value 1
- Value 2
- Value 3
-
-
-
-
- Choose an option
-
-
-
-
- Radio Group
-
-
-
-
-
-
- Checkboxes
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/addons/rose/tests/integration/components/rose/nav/primary-test.js b/addons/rose/tests/integration/components/rose/nav/primary-test.js
new file mode 100644
index 000000000..20229e9e3
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/nav/primary-test.js
@@ -0,0 +1,34 @@
+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/primary', 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-primary');
+ const id = el.id;
+ const titleId = `title-${id}`;
+ const titleEl = find('.rose-nav-title');
+ assert.equal(titleEl.id, titleId);
+ assert.equal(el.getAttribute('aria-labelledby').trim(), titleId);
+ });
+});
diff --git a/ui/core/app/styles/app.scss b/ui/core/app/styles/app.scss
index ae92ab2d8..d04a9ed2b 100644
--- a/ui/core/app/styles/app.scss
+++ b/ui/core/app/styles/app.scss
@@ -3,6 +3,10 @@
@import 'rose';
@import 'notify';
+.rose-nav-primary {
+ margin-top: sizing.rems(xxl);
+}
+
.projects-overview {
margin: sizing.rems(l);
diff --git a/ui/core/app/templates/application.hbs b/ui/core/app/templates/application.hbs
index 4a6e12e89..a3f55929f 100644
--- a/ui/core/app/templates/application.hbs
+++ b/ui/core/app/templates/application.hbs
@@ -29,7 +29,15 @@
{{outlet}}
-
+
+
+ {{t 'dev.item-name'}}
+ {{t 'dev.item-name'}}
+ {{t 'dev.item-name'}}
+ {{t 'dev.item-name'}}
+
+
+
diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml
index 3abe145c0..ca4bc7961 100644
--- a/ui/core/translations/en-us.yaml
+++ b/ui/core/translations/en-us.yaml
@@ -21,3 +21,5 @@ dev:
menu-item: Menu item
button-menu-item: Button menu item
section: Section
+ title: Title
+ item-name: Item Name