diff --git a/addons/rose/addon/components/rose/layout/tabs.js b/addons/rose/addon/components/rose/layout/tabs.js
new file mode 100644
index 000000000..88f0a170b
--- /dev/null
+++ b/addons/rose/addon/components/rose/layout/tabs.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/layout/tabs';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/layout/tabs.stories.mdx b/addons/rose/addon/components/rose/layout/tabs.stories.mdx
new file mode 100644
index 000000000..5ba3ad3f2
--- /dev/null
+++ b/addons/rose/addon/components/rose/layout/tabs.stories.mdx
@@ -0,0 +1,26 @@
+import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
+import { hbs } from 'ember-cli-htmlbars';
+import { action } from '@storybook/addon-actions';
+
+
+
+# Tabs
+
+A tabs layout component to hold `Rose::Tab` definitions.
+
+```html
+
+
+
+```
+
+
+ {{
+ template: hbs`
+
+
+
+
+ `,
+ }}
+
diff --git a/addons/rose/addon/components/rose/notification.js b/addons/rose/addon/components/rose/notification.js
index 856dbc863..8000e87b4 100644
--- a/addons/rose/addon/components/rose/notification.js
+++ b/addons/rose/addon/components/rose/notification.js
@@ -33,6 +33,5 @@ export default Component.extend({
}
return icon;
- }
-
+ },
});
diff --git a/addons/rose/addon/components/rose/tab.js b/addons/rose/addon/components/rose/tab.js
new file mode 100644
index 000000000..c37635f9b
--- /dev/null
+++ b/addons/rose/addon/components/rose/tab.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../templates/components/rose/tab';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/tab.stories.mdx b/addons/rose/addon/components/rose/tab.stories.mdx
new file mode 100644
index 000000000..a4b7136aa
--- /dev/null
+++ b/addons/rose/addon/components/rose/tab.stories.mdx
@@ -0,0 +1,43 @@
+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';
+
+
+
+# Tab
+
+A tab item component with ability to configure icon, title, and badge sections.
+
+```html
+
+```
+
+
+ {{
+ template: hbs`
+ `,
+ context: {
+ title: text('title', 'Tab item'),
+ badge: text('badge', '3'),
+ icon: select(
+ 'icon',
+ {
+ 'info-circle-outline': 'info-circle-outline',
+ 'user-square-fill': 'user-square-fill',
+ 'check-circle-outline': 'check-circle-outline',
+ 'copy-action': 'copy-action',
+ 'unfold-more': 'unfold-more',
+ 'unfold-less': 'unfold-less',
+ },
+ 'info-circle-outline'
+ ),
+ disabled: boolean('disabled', false)
+ },
+ }}
+
diff --git a/addons/rose/addon/styles/rose/components/_notification.scss b/addons/rose/addon/styles/rose/components/_notification.scss
index ac88ea4f4..c69dde3f1 100644
--- a/addons/rose/addon/styles/rose/components/_notification.scss
+++ b/addons/rose/addon/styles/rose/components/_notification.scss
@@ -58,11 +58,11 @@
}
&:hover {
- background-color: rgba(var(--stark-components), var(--opacity-3))
+ background-color: rgba(var(--stark-components), var(--opacity-3));
}
&:active {
- background-color: rgba(var(--stark-components), var(--opacity-2))
+ background-color: rgba(var(--stark-components), var(--opacity-2));
}
}
diff --git a/addons/rose/addon/styles/rose/components/_tab.scss b/addons/rose/addon/styles/rose/components/_tab.scss
new file mode 100644
index 000000000..f6138608a
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/_tab.scss
@@ -0,0 +1,53 @@
+@use '../variables/sizing';
+@use '../utilities/type';
+
+.rose-tab {
+ @include type.type(s);
+ display: inline-block;
+ background-color: var(--subtle);
+
+ .rose-tab-link {
+ text-decoration: none;
+ color: var(--ui-gray);
+ display: inline-block;
+ padding: sizing.rems(s) sizing.rems(m);
+ border-bottom: sizing.rems(xxxs) solid var(--subtle);
+
+ &.rose-tab-link-disabled {
+ cursor: not-allowed;
+ pointer-events: none;
+ color: var(--ui-gray-subtler-2);
+ }
+
+ &:active {
+ color: var(--action);
+ border-color: var(--action);
+ background-color: var(--subtle);
+ }
+
+ &:hover {
+ color: var(--ui-gray);
+ border-color: var(--ui-gray-subtler-2);
+ background-color: var(--ui-gray-subtler-4);
+ }
+
+ &:hover:active {
+ color: var(--action);
+ border-color: var(--action);
+ background-color: var(--ui-gray-subtler-4);
+ }
+ }
+
+ .rose-tab-icon {
+ svg {
+ width: sizing.rems(m);
+ height: sizing.rems(m);
+ vertical-align: sub;
+ }
+ }
+
+ .rose-tab-badge {
+ @include type.type(xs);
+ margin-left: sizing.rems(xxs);
+ }
+}
diff --git a/addons/rose/addon/styles/rose/components/card/_link.scss b/addons/rose/addon/styles/rose/components/card/_link.scss
index ba0e367e8..26b3e3f8c 100644
--- a/addons/rose/addon/styles/rose/components/card/_link.scss
+++ b/addons/rose/addon/styles/rose/components/card/_link.scss
@@ -3,7 +3,7 @@
.rose-card-link {
@include type.type(s);
- border: 1px solid var(--ui-gray-subtler-2);
+ border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
border-radius: sizing.rems(xxxs);
color: var(--ui-gray);
display: inline-flex;
diff --git a/addons/rose/addon/styles/rose/components/layout/_index.scss b/addons/rose/addon/styles/rose/components/layout/_index.scss
index 909cdbc83..27a5cbba3 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 'tabs';
+
.rose-layout-page {
display: grid;
grid-template-areas:
diff --git a/addons/rose/addon/styles/rose/components/layout/_tabs.scss b/addons/rose/addon/styles/rose/components/layout/_tabs.scss
new file mode 100644
index 000000000..999018422
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/layout/_tabs.scss
@@ -0,0 +1,8 @@
+@use '../../variables/sizing';
+
+.rose-layout-tabs {
+ border-bottom: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: row;
+}
diff --git a/addons/rose/addon/templates/components/rose/layout/tabs.hbs b/addons/rose/addon/templates/components/rose/layout/tabs.hbs
new file mode 100644
index 000000000..7b777cb8b
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/layout/tabs.hbs
@@ -0,0 +1,3 @@
+
+ {{yield}}
+
diff --git a/addons/rose/addon/templates/components/rose/tab.hbs b/addons/rose/addon/templates/components/rose/tab.hbs
new file mode 100644
index 000000000..cfb65cf12
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/tab.hbs
@@ -0,0 +1,22 @@
+
+
+ {{#if @icon}}
+ {{inline-svg @icon}}
+ {{/if}}
+
+ {{#if @title}}
+ {{@title}}
+ {{/if}}
+
+ {{#if @badge}}
+ {{@badge}}
+ {{/if}}
+
+
+ {{yield}}
+
diff --git a/addons/rose/app/components/rose/layout/tabs.js b/addons/rose/app/components/rose/layout/tabs.js
new file mode 100644
index 000000000..9de968192
--- /dev/null
+++ b/addons/rose/app/components/rose/layout/tabs.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/layout/tabs';
diff --git a/addons/rose/app/components/rose/notification.js b/addons/rose/app/components/rose/notification.js
index 228bd468c..bf9252122 100644
--- a/addons/rose/app/components/rose/notification.js
+++ b/addons/rose/app/components/rose/notification.js
@@ -1 +1 @@
-export { default } from 'rose/components/rose/notification';
\ No newline at end of file
+export { default } from 'rose/components/rose/notification';
diff --git a/addons/rose/app/components/rose/tab.js b/addons/rose/app/components/rose/tab.js
new file mode 100644
index 000000000..37b6e680c
--- /dev/null
+++ b/addons/rose/app/components/rose/tab.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/tab';
diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs
index a58b48caf..4b0ad9bdf 100644
--- a/addons/rose/tests/dummy/app/templates/index.hbs
+++ b/addons/rose/tests/dummy/app/templates/index.hbs
@@ -198,5 +198,7 @@
+
+
diff --git a/addons/rose/tests/integration/components/rose/layout/tabs-test.js b/addons/rose/tests/integration/components/rose/layout/tabs-test.js
new file mode 100644
index 000000000..d14c5bc5f
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/layout/tabs-test.js
@@ -0,0 +1,13 @@
+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/layout/tabs', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('.rose-layout-tabs'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/notification-test.js b/addons/rose/tests/integration/components/rose/notification-test.js
index 6027644d0..f8c1573b5 100644
--- a/addons/rose/tests/integration/components/rose/notification-test.js
+++ b/addons/rose/tests/integration/components/rose/notification-test.js
@@ -3,10 +3,10 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, find, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
-module('Integration | Component | rose/notification', function(hooks) {
+module('Integration | Component | rose/notification', function (hooks) {
setupRenderingTest(hooks);
- test('it renders', async function(assert) {
+ test('it renders', async function (assert) {
await render(hbs`
`);
assert.ok(find('.rose-notification'));
- assert.equal(find('.rose-notification-body').textContent.trim(), 'An error occurred.');
+ assert.equal(
+ find('.rose-notification-body').textContent.trim(),
+ 'An error occurred.'
+ );
});
- test('it has optional dismiss button which triggers a function', async function(assert) {
+ test('it has optional dismiss button which triggers a function', async function (assert) {
assert.expect(1);
this.dismiss = () => assert.ok(true, 'dismiss clicked');
await render(hbs`
diff --git a/addons/rose/tests/integration/components/rose/tab-test.js b/addons/rose/tests/integration/components/rose/tab-test.js
new file mode 100644
index 000000000..b8eb0a74e
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/tab-test.js
@@ -0,0 +1,50 @@
+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/tab', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('a'));
+ assert.ok(find('.rose-tab'));
+ assert.ok(find('.rose-tab-link'));
+ assert.notOk(find('.rose-tab-icon'));
+ assert.notOk(find('.rose-tab-badge'));
+ assert.notOk(find('.rose-tab-link-disabled'));
+ });
+
+ test('it renders with title', async function (assert) {
+ await render(hbs``);
+ assert.equal(find('a').title, 'Tab title');
+ assert.equal(find('.rose-tab-link').textContent.trim(), 'Tab title');
+ });
+
+ test('it renders with icon', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('svg'));
+ assert.ok(find('.rose-tab-icon'));
+ });
+
+ test('it renders with badge', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('.rose-tab-badge'));
+ assert.equal(find('.rose-tab-badge').textContent.trim(), '3');
+ });
+
+ test('it render with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#tab'));
+ });
+
+ test('it renders disabled', async function (assert) {
+ this.set('disabled', true);
+ await render(hbs``);
+ assert.ok(find('.rose-tab-link-disabled'));
+
+ this.set('disabled', false);
+ assert.notOk(find('.rose-tab-link-disabled'));
+ });
+});