diff --git a/addons/rose/addon/components/rose/form/radio.js b/addons/rose/addon/components/rose/form/radio.js new file mode 100644 index 000000000..882c4eea2 --- /dev/null +++ b/addons/rose/addon/components/rose/form/radio.js @@ -0,0 +1,20 @@ +import Component from '@ember/component'; +import layout from '../../../templates/components/rose/form/radio'; + +/** + * A HTML `` element. + * + * @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..e86b33d1c --- /dev/null +++ b/addons/rose/addon/styles/rose/components/form/_radio.scss @@ -0,0 +1,131 @@ +@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; + 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.hbs b/addons/rose/addon/templates/components/rose/form/radio.hbs new file mode 100644 index 000000000..f01831c6d --- /dev/null +++ b/addons/rose/addon/templates/components/rose/form/radio.hbs @@ -0,0 +1,16 @@ +
+ + + + + + {{yield}} + +
diff --git a/addons/rose/app/components/rose/form/radio.js b/addons/rose/app/components/rose/form/radio.js new file mode 100644 index 000000000..609e3db99 --- /dev/null +++ b/addons/rose/app/components/rose/form/radio.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/form/radio'; \ No newline at end of file diff --git a/addons/rose/app/templates/index.hbs b/addons/rose/app/templates/index.hbs index 5ac78d798..47336ad5e 100644 --- a/addons/rose/app/templates/index.hbs +++ b/addons/rose/app/templates/index.hbs @@ -118,3 +118,34 @@ Dummy app index template @label="Disabled + Checked" disabled={{true}} /> + +

Radio

+ + + + + + diff --git a/addons/rose/stories/form/radio.stories.js b/addons/rose/stories/form/radio.stories.js new file mode 100644 index 000000000..68bcc04c6 --- /dev/null +++ b/addons/rose/stories/form/radio.stories.js @@ -0,0 +1,27 @@ +import { hbs } from 'ember-cli-htmlbars'; +import { text, boolean } from '@storybook/addon-knobs'; + +export default { + title: 'Rose::Form::Radio', +}; + +export const Normal = () => ({ + template: hbs` + + `, + context: { + label: text('text', 'Radiofield'), + id: text('id', 'radio-1'), + name: text('name', 'radio-1'), + checked: boolean('checked', false), + error: boolean('error', false), + disabled: boolean('disabled', false), + }, +}); diff --git a/addons/rose/tests/integration/components/rose/form/radio-test.js b/addons/rose/tests/integration/components/rose/form/radio-test.js new file mode 100644 index 000000000..88ac59173 --- /dev/null +++ b/addons/rose/tests/integration/components/rose/form/radio-test.js @@ -0,0 +1,49 @@ +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', 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 not checked by default', async function(assert) { + 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 checked when @checked={{true}}', async function(assert) { + await render(hbs``); + assert.equal(await find('input').checked, true); + }); + + 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')); + }); +});