diff --git a/addons/rose/addon/components/rose/form/radio/group.js b/addons/rose/addon/components/rose/form/radio/group.js
new file mode 100644
index 000000000..a9508a084
--- /dev/null
+++ b/addons/rose/addon/components/rose/form/radio/group.js
@@ -0,0 +1,31 @@
+import Component from '@ember/component';
+import layout from '../../../../templates/components/rose/form/radio/group';
+
+/**
+ * A `` element.
+ *
+ * @example
+ *
+ *
+ *
+ *
+ *
+ */
+
+export default Component.extend({
+ layout,
+
+ tagName: '',
+});
diff --git a/addons/rose/addon/components/rose/form/radio/radio.js b/addons/rose/addon/components/rose/form/radio/radio.js
new file mode 100644
index 000000000..f32a6835e
--- /dev/null
+++ b/addons/rose/addon/components/rose/form/radio/radio.js
@@ -0,0 +1,22 @@
+import Component from '@ember/component';
+import layout from '../../../../templates/components/rose/form/radio/radio';
+
+/**
+ * A `` element.
+ * `value` and `selectedValue` must be equal to select
+ * radiofield.
+ *
+ * @example
+ *
+ */
+export default Component.extend({
+ layout,
+ tagName: '',
+});
diff --git a/addons/rose/addon/styles/rose/components/form/_index.scss b/addons/rose/addon/styles/rose/components/form/_index.scss
index 6bcb2c8de..c8c516f8c 100644
--- a/addons/rose/addon/styles/rose/components/form/_index.scss
+++ b/addons/rose/addon/styles/rose/components/form/_index.scss
@@ -1,3 +1,4 @@
@import 'input';
@import 'checkbox';
@import 'select';
+@import 'radio';
diff --git a/addons/rose/addon/styles/rose/components/form/_radio.scss b/addons/rose/addon/styles/rose/components/form/_radio.scss
new file mode 100644
index 000000000..822505cf8
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/form/_radio.scss
@@ -0,0 +1,154 @@
+@use '../../variables/sizing';
+@use '../../utilities/type';
+
+$radio-svg: (
+ 'default':
+ "",
+ 'hover':
+ "",
+ 'focus':
+ "",
+ 'checked':
+ "",
+ 'disabled':
+ "",
+ 'disabled-checked':
+ "",
+ 'error':
+ "",
+ 'error-checked':
+ "",
+);
+
+$data-svg-prefix: 'data:image/svg+xml;utf-8,';
+
+.rose-form-radio {
+ position: relative;
+ margin-bottom: sizing.rems(xs);
+ color: var(--ui-gray);
+
+ .rose-form-radio-label {
+ @include type.type(s, semibold);
+ color: currentColor;
+ padding-left: sizing.rems(l);
+
+ &::before {
+ position: absolute;
+ top: sizing.rems(xs);
+ left: 0;
+ display: block;
+ width: sizing.rems(m);
+ height: sizing.rems(m);
+ pointer-events: none;
+ content: ' ';
+ }
+
+ &::after {
+ position: absolute;
+ top: -#{sizing.rems(xxs)};
+ left: -#{sizing.rems(xxxs) + sizing.rems(xxxxs)};
+ display: block;
+ width: sizing.rems(l);
+ height: sizing.rems(l);
+ transform: scale(2);
+ pointer-events: none;
+ content: ' ';
+ background: no-repeat 50%/50% 50%;
+ background-image: url($data-svg-prefix + map-get($radio-svg, 'default'));
+ }
+ }
+
+ .rose-form-radio-field {
+ position: absolute;
+ left: 0;
+ z-index: -1;
+ width: sizing.rems(m);
+ height: sizing.rems(m);
+ opacity: 0;
+
+ &:not(:disabled) {
+ &:hover {
+ ~ .rose-form-radio-label {
+ &::after {
+ background-image: url($data-svg-prefix + map-get(
+ $radio-svg,
+ 'hover'
+ ));
+ }
+ }
+ }
+
+ &:focus {
+ ~ .rose-form-radio-label {
+ &::after {
+ background-image: url($data-svg-prefix + map-get(
+ $radio-svg,
+ 'focus'
+ ));
+ }
+ }
+ }
+
+ &:checked {
+ ~ .rose-form-radio-label {
+ &::after {
+ background-image: url($data-svg-prefix + map-get(
+ $radio-svg,
+ 'checked'
+ ));
+ }
+ }
+ }
+ }
+
+ &:disabled {
+ ~ .rose-form-radio-label {
+ &::after {
+ background-image: url($data-svg-prefix + map-get(
+ $radio-svg,
+ 'disabled'
+ ));
+ }
+ }
+
+ &:checked {
+ ~ .rose-form-radio-label {
+ &::after {
+ background-image: url($data-svg-prefix + map-get(
+ $radio-svg,
+ 'disabled-checked'
+ ));
+ }
+ }
+ }
+ }
+ }
+
+ &.error {
+ color: var(--failure);
+
+ .rose-form-radio-field {
+ &:not(:disabled):not(:focus) {
+ ~ .rose-form-radio-label {
+ &::after {
+ background-image: url($data-svg-prefix + map-get(
+ $radio-svg,
+ 'error'
+ ));
+ }
+ }
+
+ &:checked {
+ ~ .rose-form-radio-label {
+ &::after {
+ background-image: url($data-svg-prefix + map-get(
+ $radio-svg,
+ 'error-checked'
+ ));
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/addons/rose/addon/templates/components/rose/form/radio/group.hbs b/addons/rose/addon/templates/components/rose/form/radio/group.hbs
new file mode 100644
index 000000000..6aa52cf50
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/form/radio/group.hbs
@@ -0,0 +1,3 @@
+{{yield (hash
+ radio=(component 'rose/form/radio/radio' name=@name selectedValue=@selectedValue)
+)}}
diff --git a/addons/rose/addon/templates/components/rose/form/radio/radio.hbs b/addons/rose/addon/templates/components/rose/form/radio/radio.hbs
new file mode 100644
index 000000000..088853d5b
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/form/radio/radio.hbs
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ {{yield}}
+
+
diff --git a/addons/rose/app/components/rose/form/radio/group.js b/addons/rose/app/components/rose/form/radio/group.js
new file mode 100644
index 000000000..0f546baa3
--- /dev/null
+++ b/addons/rose/app/components/rose/form/radio/group.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/form/radio/group';
diff --git a/addons/rose/app/components/rose/form/radio/radio.js b/addons/rose/app/components/rose/form/radio/radio.js
new file mode 100644
index 000000000..162aa3a66
--- /dev/null
+++ b/addons/rose/app/components/rose/form/radio/radio.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/form/radio/radio';
diff --git a/addons/rose/app/templates/index.hbs b/addons/rose/app/templates/index.hbs
index 5ac78d798..0a96fc98a 100644
--- a/addons/rose/app/templates/index.hbs
+++ b/addons/rose/app/templates/index.hbs
@@ -118,3 +118,53 @@ Dummy app index template
@label="Disabled + Checked"
disabled={{true}}
/>
+
+Radio field
+
+
+
+
+
+
+
+Radio field group
+
+
+
+
+
diff --git a/addons/rose/package.json b/addons/rose/package.json
index 6243dcff9..755adec7e 100644
--- a/addons/rose/package.json
+++ b/addons/rose/package.json
@@ -58,6 +58,7 @@
"ember-load-initializers": "^2.1.1",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-qunit": "^4.6.0",
+ "ember-radio-button": "^2.0.1",
"ember-resolver": "^7.0.0",
"ember-source": "~3.12.0",
"ember-source-channel-url": "^2.0.1",
diff --git a/addons/rose/stories/form/radio/group.stories.js b/addons/rose/stories/form/radio/group.stories.js
new file mode 100644
index 000000000..8c64c7016
--- /dev/null
+++ b/addons/rose/stories/form/radio/group.stories.js
@@ -0,0 +1,41 @@
+import { hbs } from 'ember-cli-htmlbars';
+import { text } from '@storybook/addon-knobs';
+
+export default {
+ title: 'Rose::Form::Radio::Group',
+};
+
+export const Normal = () => ({
+ template: hbs`
+
+
+
+
+
+ `,
+ context: {
+ name: text('name', 'Groupname'),
+ selectedValue: text('selectedValue', 'disagree'),
+
+ firstFieldLabel: text('first radiofield label', 'Agree'),
+ firstFieldValue: text('first radiofield value', 'agree'),
+
+ secondFieldLabel: text('second radiofield label', 'Maybe'),
+ secondFieldValue: text('second radiofield value', 'maybe'),
+
+ thirdFieldLabel: text('third radiofield label', 'Disagree'),
+ thirdFieldValue: text('third radiofield value', 'disagree'),
+ },
+});
diff --git a/addons/rose/stories/form/radio/radio.stories.js b/addons/rose/stories/form/radio/radio.stories.js
new file mode 100644
index 000000000..72bac02d3
--- /dev/null
+++ b/addons/rose/stories/form/radio/radio.stories.js
@@ -0,0 +1,30 @@
+import { hbs } from 'ember-cli-htmlbars';
+import { text, boolean } from '@storybook/addon-knobs';
+
+export default {
+ title: 'Rose::Form::Radio::Radio',
+};
+
+export const Normal = () => ({
+ template: hbs`
+
+ `,
+ context: {
+ id: text('id', 'radio-1'),
+ label: text('text', 'Radiofield'),
+ error: boolean('error', false),
+ value: text('value', 'value and selectedValue must match to select radiofield'),
+ selectedValue: text(
+ 'selectedValue',
+ 'value and selectedValue must match to select radiofield'
+ ),
+ disabled: boolean('disabled', false),
+ },
+});
diff --git a/addons/rose/tests/integration/components/rose/form/radio/group-test.js b/addons/rose/tests/integration/components/rose/form/radio/group-test.js
new file mode 100644
index 000000000..b3aa9d2f9
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/form/radio/group-test.js
@@ -0,0 +1,112 @@
+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/form/radio/group', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs`
+
+
+
+
+ `);
+
+ assert.equal(findAll('input').length, 2);
+ assert.equal(findAll('input')[0].name, 'bird');
+ assert.equal(findAll('input')[1].name, 'bird');
+ });
+
+ test('it renders with @selectedValue value selected', async function (assert) {
+ await render(hbs`
+
+
+
+
+ `);
+
+ assert.equal(find('#bird-1').checked, false);
+ assert.equal(find('#bird-2').checked, true);
+
+ await click(find('#bird-1'));
+
+ assert.equal(find('#bird-1').checked, true);
+ assert.equal(find('#bird-2').checked, false);
+ });
+
+ test('it reflects active radiofield value in @selectedValue', async function (assert) {
+ await render(hbs`
+
+
+
+
+ `);
+
+ assert.notOk(this.selectedValue);
+
+ await click(find('#bird-1'));
+ assert.equal(this.selectedValue, 'pegion');
+
+ await click(find('#bird-2'));
+ assert.equal(this.selectedValue, 'flamingo');
+ });
+
+ test('it uses @selectedValue value to set active radiofield', async function (assert) {
+ this.set('selectedValue', 'cat');
+ await render(hbs`
+
+
+
+
+ `);
+
+ assert.equal(find('#bird-1').checked, false);
+ assert.equal(find('#bird-2').checked, false);
+
+ this.set('selectedValue', 'flamingo');
+ assert.equal(find('#bird-1').checked, false);
+ assert.equal(find('#bird-2').checked, true);
+
+ this.set('selectedValue', 'pegion');
+ assert.equal(find('#bird-1').checked, true);
+ assert.equal(find('#bird-2').checked, false);
+
+ this.set('selectedValue', 'dog');
+ assert.equal(find('#bird-1').checked, false);
+ assert.equal(find('#bird-2').checked, false);
+ });
+});
diff --git a/addons/rose/tests/integration/components/rose/form/radio/radio-test.js b/addons/rose/tests/integration/components/rose/form/radio/radio-test.js
new file mode 100644
index 000000000..f3aa7df11
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/form/radio/radio-test.js
@@ -0,0 +1,59 @@
+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/form/radio/radio', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.ok(await find('input'));
+ assert.equal(await find('input').type, 'radio');
+ });
+
+ test('it renders with label', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('label').textContent.trim(), 'Label');
+ });
+
+ test('it is checked when @value and @selectedValue values match', async function (assert) {
+ await render(
+ hbs``
+ );
+ assert.equal(await find('input').checked, true);
+ });
+
+ test('it is not checked when @value and @selectedValue values are different', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('input').checked, false);
+
+ await render(hbs``);
+ assert.equal(await find('input').checked, false);
+
+ await render(
+ hbs``
+ );
+ assert.equal(await find('input').checked, false);
+ });
+
+ test('it is not disabled by default', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('input').disabled, false);
+ });
+
+ test('it is not in error state by default', async function (assert) {
+ await render(hbs``);
+ assert.notOk(await find('.error'));
+ });
+
+ test('it is disabled when @disabled={{true}}', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('input').disabled, true);
+ });
+
+ test('it is in error state when @error={{true}}', async function (assert) {
+ await render(hbs``);
+ assert.ok(await find('.error'));
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index f8ef1e81a..68f11c41b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4732,7 +4732,7 @@ broccoli-output-wrapper@^3.2.1:
heimdalljs-logger "^0.1.10"
symlink-or-copy "^1.2.0"
-broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.4.3:
+broccoli-persistent-filter@^1.0.3, broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.4.3:
version "1.4.6"
resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-1.4.6.tgz#80762d19000880a77da33c34373299c0f6a3e615"
integrity sha512-0RejLwoC95kv4kta8KAa+FmECJCK78Qgm8SRDEK7YyU0N9Cx6KpY3UCDy9WELl3mCXLN8TokNxc7/hp3lL4lfw==
@@ -6825,7 +6825,7 @@ ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.0:
resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac"
integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA==
-ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.16.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.1:
+ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.16.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.9.2:
version "6.18.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.18.0.tgz#3f6435fd275172edeff2b634ee7b29ce74318957"
integrity sha512-7ceC8joNYxY2wES16iIBlbPSxwKDBhYwC8drU3ZEvuPDMwVv1KzxCNu1fvxyFEBWhwaRNTUxSCsEVoTd9nosGA==
@@ -6913,6 +6913,17 @@ ember-cli-htmlbars-inline-precompile@^2.1.0:
heimdalljs-logger "^0.1.9"
silent-error "^1.1.0"
+ember-cli-htmlbars@^1.1.1:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-1.3.5.tgz#65be9678b274b5e7861d7f75c188780af7ef9d13"
+ integrity sha512-Qur/anb0Vk57qmIhGLkSzl8X1QIMoae6pLa14MRQ8+YD2N5fNs3qdhEFf0SDBquPOH1QxQtraiNQvji47QBJyg==
+ dependencies:
+ broccoli-persistent-filter "^1.0.3"
+ ember-cli-version-checker "^1.0.2"
+ hash-for-dep "^1.0.2"
+ json-stable-stringify "^1.0.0"
+ strip-bom "^2.0.0"
+
ember-cli-htmlbars@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-3.1.0.tgz#87806c2a0bca2ab52d4fb8af8e2215c1ca718a99"
@@ -7072,6 +7083,13 @@ ember-cli-uglify@^3.0.0:
broccoli-uglify-sourcemap "^3.1.0"
lodash.defaultsdeep "^4.6.0"
+ember-cli-version-checker@^1.0.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-1.3.1.tgz#0bc2d134c830142da64bf9627a0eded10b61ae72"
+ integrity sha1-C8LRNMgwFC2mS/lieg7e0QthrnI=
+ dependencies:
+ semver "^5.3.0"
+
ember-cli-version-checker@^2.0.0, ember-cli-version-checker@^2.1.0, ember-cli-version-checker@^2.1.1, ember-cli-version-checker@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.2.0.tgz#47771b731fe0962705e27c8199a9e3825709f3b3"
@@ -7261,6 +7279,14 @@ ember-qunit@^4.6.0:
ember-cli-test-loader "^2.2.0"
qunit "^2.9.3"
+ember-radio-button@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ember-radio-button/-/ember-radio-button-2.0.1.tgz#f081a44d581a4b1db88fef79ec5c47da712ab963"
+ integrity sha512-cAAyFgNIM3PquowkWfa0lOSlq4PPst9vpkNiLi5yhd1LJ4lVtwgn6s+OpPXWKJLZdig2m31PWU8jxobcubge5w==
+ dependencies:
+ ember-cli-babel "^6.9.2"
+ ember-cli-htmlbars "^1.1.1"
+
ember-resolver@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/ember-resolver/-/ember-resolver-7.0.0.tgz#07ca86b3bae373395b44bba784b8c133f75d61c2"
@@ -10257,7 +10283,7 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-is-utf8@^0.2.1:
+is-utf8@^0.2.0, is-utf8@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
@@ -15167,6 +15193,13 @@ strip-bom@4.0.0, strip-bom@^4.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
+strip-bom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
+ integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
+ dependencies:
+ is-utf8 "^0.2.0"
+
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"