diff --git a/addons/rose/addon/components/rose/table.js b/addons/rose/addon/components/rose/table.js
new file mode 100644
index 000000000..bd991b9d8
--- /dev/null
+++ b/addons/rose/addon/components/rose/table.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../templates/components/rose/table';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table.stories.mdx b/addons/rose/addon/components/rose/table.stories.mdx
new file mode 100644
index 000000000..0af8d7d0b
--- /dev/null
+++ b/addons/rose/addon/components/rose/table.stories.mdx
@@ -0,0 +1,67 @@
+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';
+
+
+
+# Table
+
+A table component to display data in structure of rows and columns. It supports `header`, `body` and `footer` optional definitions. Each definition allows for a table row to be defined with table cells.
+
+```htmlbars
+
+
+
+ Address
+ Host Set
+ Name
+ Description
+
+
+
+
+ 1.1.1.1
+ host-set-1, host-set-2
+ optional name
+ description
+
+
+
+
+ Footer
+
+
+
+```
+
+
+ {{
+ template: hbs`
+
+
+
+ Address
+ Host Set
+ Name
+ Description
+
+
+
+
+ 1.1.1.1
+ host-set-1, host-set-2
+ optional name
+ description
+
+
+
+
+ Footer
+
+
+
+ `,
+ context: {}
+ }}
+
diff --git a/addons/rose/addon/components/rose/table/body.js b/addons/rose/addon/components/rose/table/body.js
new file mode 100644
index 000000000..2702ed983
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/body.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/body';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table/cell.js b/addons/rose/addon/components/rose/table/cell.js
new file mode 100644
index 000000000..3943c8054
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/cell.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/cell';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table/footer.js b/addons/rose/addon/components/rose/table/footer.js
new file mode 100644
index 000000000..ff0346354
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/footer.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/footer';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table/header.js b/addons/rose/addon/components/rose/table/header.js
new file mode 100644
index 000000000..bcc2ff040
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/header.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/header';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/table/row.js b/addons/rose/addon/components/rose/table/row.js
new file mode 100644
index 000000000..4012b9afd
--- /dev/null
+++ b/addons/rose/addon/components/rose/table/row.js
@@ -0,0 +1,7 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/table/row';
+
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss
index bebf8b7de..9376545de 100644
--- a/addons/rose/addon/styles/rose/components/_index.scss
+++ b/addons/rose/addon/styles/rose/components/_index.scss
@@ -7,3 +7,4 @@
@import 'page-header';
@import 'separator';
@import 'nav';
+@import 'table';
diff --git a/addons/rose/addon/styles/rose/components/table/_index.scss b/addons/rose/addon/styles/rose/components/table/_index.scss
new file mode 100644
index 000000000..ef81e56e7
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/table/_index.scss
@@ -0,0 +1,17 @@
+@use '../../variables/sizing';
+@use '../../utilities/type';
+
+.rose-table {
+ @include type.type(s, normal);
+ width: 100%;
+
+ .rose-table-header {
+ background-color: var(--ui-gray-subtler-5);
+ text-align: left;
+ }
+
+ .rose-table-cell {
+ border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3);
+ padding: sizing.rems(xs) sizing.rems(xxs);
+ }
+}
diff --git a/addons/rose/addon/templates/components/rose/table.hbs b/addons/rose/addon/templates/components/rose/table.hbs
new file mode 100644
index 000000000..580885405
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table.hbs
@@ -0,0 +1,11 @@
+
+ {{#if @caption}}
+ {{@caption}}
+ {{/if}}
+
+ {{yield (hash
+ header=(component 'rose/table/header')
+ body=(component 'rose/table/body')
+ footer=(component 'rose/table/footer')
+ )}}
+
diff --git a/addons/rose/addon/templates/components/rose/table/body.hbs b/addons/rose/addon/templates/components/rose/table/body.hbs
new file mode 100644
index 000000000..f82c64d04
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/body.hbs
@@ -0,0 +1,5 @@
+
+ {{yield (hash
+ row=(component 'rose/table/row')
+ )}}
+
diff --git a/addons/rose/addon/templates/components/rose/table/cell.hbs b/addons/rose/addon/templates/components/rose/table/cell.hbs
new file mode 100644
index 000000000..5cfc41c8d
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/cell.hbs
@@ -0,0 +1,9 @@
+{{#if @header}}
+
+{{else}}
+
+ {{yield}}
+ |
+{{/if}}
diff --git a/addons/rose/addon/templates/components/rose/table/footer.hbs b/addons/rose/addon/templates/components/rose/table/footer.hbs
new file mode 100644
index 000000000..107b0b53f
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/footer.hbs
@@ -0,0 +1,5 @@
+
diff --git a/addons/rose/addon/templates/components/rose/table/header.hbs b/addons/rose/addon/templates/components/rose/table/header.hbs
new file mode 100644
index 000000000..13344bc99
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/header.hbs
@@ -0,0 +1,5 @@
+
diff --git a/addons/rose/addon/templates/components/rose/table/row.hbs b/addons/rose/addon/templates/components/rose/table/row.hbs
new file mode 100644
index 000000000..aae991def
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/table/row.hbs
@@ -0,0 +1,5 @@
+
+ {{yield (hash
+ cell=(component 'rose/table/cell' header=@header footer=@footer)
+ )}}
+
diff --git a/addons/rose/app/components/rose/table.js b/addons/rose/app/components/rose/table.js
new file mode 100644
index 000000000..9101f15ac
--- /dev/null
+++ b/addons/rose/app/components/rose/table.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table';
diff --git a/addons/rose/app/components/rose/table/body.js b/addons/rose/app/components/rose/table/body.js
new file mode 100644
index 000000000..bf58d0690
--- /dev/null
+++ b/addons/rose/app/components/rose/table/body.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/body';
diff --git a/addons/rose/app/components/rose/table/cell.js b/addons/rose/app/components/rose/table/cell.js
new file mode 100644
index 000000000..7e3b6e8b8
--- /dev/null
+++ b/addons/rose/app/components/rose/table/cell.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/cell';
diff --git a/addons/rose/app/components/rose/table/footer.js b/addons/rose/app/components/rose/table/footer.js
new file mode 100644
index 000000000..8fedb5800
--- /dev/null
+++ b/addons/rose/app/components/rose/table/footer.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/footer';
diff --git a/addons/rose/app/components/rose/table/header.js b/addons/rose/app/components/rose/table/header.js
new file mode 100644
index 000000000..c0a585fe7
--- /dev/null
+++ b/addons/rose/app/components/rose/table/header.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/header';
diff --git a/addons/rose/app/components/rose/table/row.js b/addons/rose/app/components/rose/table/row.js
new file mode 100644
index 000000000..91618b6a0
--- /dev/null
+++ b/addons/rose/app/components/rose/table/row.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/table/row';
diff --git a/addons/rose/tests/dummy/app/controllers/playground.js b/addons/rose/tests/dummy/app/controllers/playground.js
new file mode 100644
index 000000000..af7c86470
--- /dev/null
+++ b/addons/rose/tests/dummy/app/controllers/playground.js
@@ -0,0 +1,12 @@
+import Controller from '@ember/controller';
+import { action } from '@ember/object';
+
+export default class PlaygroundController extends Controller {
+ color = 'green';
+
+ @action
+ actionHandler(/*value*/) {
+ // eslint-disable-next-line no-console
+ //console.log('action handled');
+ }
+}
diff --git a/addons/rose/tests/dummy/app/router.js b/addons/rose/tests/dummy/app/router.js
index c5464a74c..5668362d0 100644
--- a/addons/rose/tests/dummy/app/router.js
+++ b/addons/rose/tests/dummy/app/router.js
@@ -8,4 +8,5 @@ export default class Router extends EmberRouter {
Router.map(function () {
this.route('about');
+ this.route('playground');
});
diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs
index 576a37395..3554ea5f2 100644
--- a/addons/rose/tests/dummy/app/templates/index.hbs
+++ b/addons/rose/tests/dummy/app/templates/index.hbs
@@ -1,209 +1,23 @@
-
-
-
-
-
-
-
-
- Menu item
- Menu item
- Button menu item
-
- Section
- Section
-
-
-
-
-
- Menu item
- Menu item
- Button menu item
-
-
-
-
-
-
-
-
- Item Name
- Item Name
- Item Name
- Item Name
-
-
-
-
-
- Breadcrumbs
- Header
- Navigation
- Actions
- Body
-
-
- Dummy app index template
-
-
- 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
-
-
-
-
- Radio Group
-
-
-
-
-
-
- Checkboxes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Address
+ Host Set
+ Name
+ Description
+
+
+
+
+ 1.1.1.1
+ host-set-1, host-set-2
+ optional name
+ description
+
+
+
+
+ Footer
+
+
+
diff --git a/addons/rose/tests/dummy/app/templates/playground.hbs b/addons/rose/tests/dummy/app/templates/playground.hbs
new file mode 100644
index 000000000..576a37395
--- /dev/null
+++ b/addons/rose/tests/dummy/app/templates/playground.hbs
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+
+ Menu item
+ Menu item
+ Button menu item
+
+ Section
+ Section
+
+
+
+
+
+ Menu item
+ Menu item
+ Button menu item
+
+
+
+
+
+
+
+
+ Item Name
+ Item Name
+ Item Name
+ Item Name
+
+
+
+
+
+ Breadcrumbs
+ Header
+ Navigation
+ Actions
+ Body
+
+
+ Dummy app index template
+
+
+ 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
+
+
+
+
+ Radio Group
+
+
+
+
+
+
+ Checkboxes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/rose/tests/integration/components/rose/table-test.js b/addons/rose/tests/integration/components/rose/table-test.js
new file mode 100644
index 000000000..06f2a5b8c
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table-test.js
@@ -0,0 +1,46 @@
+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/table', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('table'));
+ assert.ok(find('.rose-table'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#table'));
+ });
+
+ test('it renders with caption', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('caption'));
+ assert.equal(find('caption').textContent.trim(), 'table caption');
+ });
+
+ test('it renders with header', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('thead'));
+ });
+
+ test('it renders with body', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tbody'));
+ });
+
+ test('it renders with footer', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tfoot'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/body-test.js b/addons/rose/tests/integration/components/rose/table/body-test.js
new file mode 100644
index 000000000..e7c026883
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/body-test.js
@@ -0,0 +1,36 @@
+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/table/body', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('tbody'));
+ assert.ok(find('.rose-table-body'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#body'));
+ });
+
+ test('it renders with row', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tr'));
+ });
+
+ test('it renders with row cell', async function (assert) {
+ await render(hbs`
+
+
+
+ `);
+ assert.ok(find('td'));
+ assert.ok(find('.rose-table-body-cell'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/cell-test.js b/addons/rose/tests/integration/components/rose/table/cell-test.js
new file mode 100644
index 000000000..aeec60376
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/cell-test.js
@@ -0,0 +1,39 @@
+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/table/cell', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('td'));
+ assert.ok(find('.rose-table-cell'));
+ assert.ok(find('.rose-table-body-cell'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#cell'));
+ });
+
+ test('it renders with content', async function (assert) {
+ await render(hbs`
+ Cell content
+ `);
+ assert.equal(find('.rose-table-cell').textContent.trim(), 'Cell content');
+ });
+
+ test('it renders as header cell', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('.rose-table-cell'));
+ assert.ok(find('.rose-table-header-cell'));
+ });
+
+ test('it renders as footer cell', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('.rose-table-cell'));
+ assert.ok(find('.rose-table-footer-cell'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/footer-test.js b/addons/rose/tests/integration/components/rose/table/footer-test.js
new file mode 100644
index 000000000..1e4cd8997
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/footer-test.js
@@ -0,0 +1,36 @@
+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/table/footer', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('tfoot'));
+ assert.ok(find('.rose-table-footer'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#footer'));
+ });
+
+ test('it renders with row', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tr'));
+ });
+
+ test('it renders with row cell', async function (assert) {
+ await render(hbs`
+
+
+
+ `);
+ assert.ok(find('td'));
+ assert.ok(find('.rose-table-cell'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/header-test.js b/addons/rose/tests/integration/components/rose/table/header-test.js
new file mode 100644
index 000000000..f6ae7c289
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/header-test.js
@@ -0,0 +1,36 @@
+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/table/header', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('thead'));
+ assert.ok(find('.rose-table-header'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#header'));
+ });
+
+ test('it renders with row', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('tr'));
+ });
+
+ test('it renders with row cell', async function (assert) {
+ await render(hbs`
+
+
+
+ `);
+ assert.ok(find('th'));
+ assert.ok(find('.rose-table-header-cell'));
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/table/row-test.js b/addons/rose/tests/integration/components/rose/table/row-test.js
new file mode 100644
index 000000000..efb3182d4
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/table/row-test.js
@@ -0,0 +1,26 @@
+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/table/row', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('tr'));
+ assert.ok(find('.rose-table-row'));
+ });
+
+ test('it renders with attributes', async function (assert) {
+ await render(hbs``);
+ assert.ok(find('#row'));
+ });
+
+ test('it renders with cell', async function (assert) {
+ await render(hbs`
+
+ `);
+ assert.ok(find('.rose-table-cell'));
+ });
+});