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"