diff --git a/addons/rose/addon/components/rose/form.stories.mdx b/addons/rose/addon/components/rose/form.stories.mdx index e31e0d1b2..f57f85da2 100644 --- a/addons/rose/addon/components/rose/form.stories.mdx +++ b/addons/rose/addon/components/rose/form.stories.mdx @@ -10,58 +10,8 @@ import { text, boolean, select } from '@storybook/addon-knobs'; A component for building accessible forms. Yields contextual fields and actions. -```htmlbars - - - - - - Choose an option - Value 1 - Value 2 - Value 3 - - - - - - - - - - -``` - - {{ + {{ template: hbs` - + Choose an option Value 1 Value 2 Value 3 - + + + +```htmlbars + + + + + + Choose an option + Value 1 + Value 2 + Value 3 + + + + + + + + + + +``` diff --git a/addons/rose/addon/components/rose/form/checkbox.stories.mdx b/addons/rose/addon/components/rose/form/checkbox.stories.mdx index 2dcf0bb14..95200ce1c 100644 --- a/addons/rose/addon/components/rose/form/checkbox.stories.mdx +++ b/addons/rose/addon/components/rose/form/checkbox.stories.mdx @@ -20,7 +20,7 @@ Renders a form input checkbox. ``` - {{ + {{ template: hbs` + + + {{ + template: hbs` + + + An error occurred. + + + `, + context: { + label: text('text', 'Checkbox 🛎'), + name: text('name', 'checkbox-1'), + checked: boolean('checked', false), + disabled: boolean('disabled', false), + } + }} + diff --git a/addons/rose/addon/components/rose/form/errors.js b/addons/rose/addon/components/rose/form/errors.js index 0d9254b87..bc13c3f56 100644 --- a/addons/rose/addon/components/rose/form/errors.js +++ b/addons/rose/addon/components/rose/form/errors.js @@ -6,5 +6,5 @@ import layout from '../../../templates/components/rose/form/errors'; */ export default Component.extend({ layout, - tagName: '' + tagName: '', }); diff --git a/addons/rose/addon/components/rose/form/errors/message.js b/addons/rose/addon/components/rose/form/errors/message.js index 23c6ebb5d..0f59c6b06 100644 --- a/addons/rose/addon/components/rose/form/errors/message.js +++ b/addons/rose/addon/components/rose/form/errors/message.js @@ -6,5 +6,5 @@ import layout from '../../../../templates/components/rose/form/errors/message'; */ export default Component.extend({ layout, - tagName: '' + tagName: '', }); diff --git a/addons/rose/addon/components/rose/form/input.stories.mdx b/addons/rose/addon/components/rose/form/input.stories.mdx index 0e7465be9..48d38020a 100644 --- a/addons/rose/addon/components/rose/form/input.stories.mdx +++ b/addons/rose/addon/components/rose/form/input.stories.mdx @@ -22,14 +22,14 @@ Renders a simple form input. ``` - {{ + {{ template: hbs` @@ -50,7 +50,44 @@ Renders a simple form input. helperText: text('helperText', 'Helper text'), disabled: boolean('disabled', false), 'read-only': boolean('readonly', false), - error: boolean('error', false), + } + }} + + + + {{ + template: hbs` + + + An error occurred. + + + `, + context: { + type: select( + 'type', + { + text: 'text', + email: 'email', + number: 'number', + password: 'password', + }, + 'text' + ), + value: text('value', 'Value'), + label: text('label', 'Label'), + helperText: text('helperText', 'Helper text'), + disabled: boolean('disabled', false), + 'read-only': boolean('readonly', false), } }} diff --git a/addons/rose/addon/components/rose/form/select.stories.mdx b/addons/rose/addon/components/rose/form/select.stories.mdx index 9ec8d3a80..114c205a0 100644 --- a/addons/rose/addon/components/rose/form/select.stories.mdx +++ b/addons/rose/addon/components/rose/form/select.stories.mdx @@ -17,14 +17,14 @@ Renders a form select element. @helperText="Set your desired color" @disabled={{false}} @error={{false}} - as |Field| + as |field| > - + Choose a color Red Green Blue - + ``` @@ -37,16 +37,60 @@ Renders a form select element. @label={{label}} @helperText={{helperText}} @disabled={{disabled}} - @error={{error}} + @error={{false}} @onChange={{fn onChange}} - as |Field| + as |field| > - + Choose an option Value 1 Value 2 Value 3 - + + + `, + context: { + value: select( + 'value', + { + null: null, + 'value-1': 'value-1', + 'value-2': 'value-2', + 'value-3': 'value-3', + }, + null + ), + name: text('name', 'select-name'), + label: text('label', 'Label'), + helperText: text('helperText', 'Helper text'), + disabled: boolean('disabled', false), + onChange: action('changed'), + } + }} + + + + {{ + template: hbs` + + + Choose an option + Value 1 + Value 2 + Value 3 + + + An error occurred. + `, context: { @@ -64,7 +108,6 @@ Renders a form select element. label: text('label', 'Label'), helperText: text('helperText', 'Helper text'), disabled: boolean('disabled', false), - error: boolean('error', false), onChange: action('changed'), } }} diff --git a/addons/rose/addon/components/rose/form/textarea.stories.mdx b/addons/rose/addon/components/rose/form/textarea.stories.mdx index 17ae649f5..2ddb54819 100644 --- a/addons/rose/addon/components/rose/form/textarea.stories.mdx +++ b/addons/rose/addon/components/rose/form/textarea.stories.mdx @@ -21,13 +21,13 @@ Renders a simple form textarea. ``` - {{ + {{ template: hbs` @@ -38,7 +38,32 @@ Renders a simple form textarea. helperText: text('helperText', 'Helper text'), disabled: boolean('disabled', false), 'read-only': boolean('readonly', false), - error: boolean('error', false), + } + }} + + + + {{ + template: hbs` + + + An error occurred. + + + `, + context: { + value: text('value', 'Value'), + label: text('label', 'Label'), + helperText: text('helperText', 'Helper text'), + disabled: boolean('disabled', false), + 'read-only': boolean('readonly', false), } }} diff --git a/addons/rose/addon/styles/rose/components/form/_checkbox.scss b/addons/rose/addon/styles/rose/components/form/_checkbox.scss index 4b826841e..9e4827527 100644 --- a/addons/rose/addon/styles/rose/components/form/_checkbox.scss +++ b/addons/rose/addon/styles/rose/components/form/_checkbox.scss @@ -6,6 +6,11 @@ margin-bottom: sizing.rems(xs); color: var(--ui-gray); + .rose-form-errors { + margin-top: sizing.rems(xs); + margin-bottom: sizing.rems(xs); + } + .rose-form-checkbox-label { // label styles @include type.type(s, semibold); diff --git a/addons/rose/addon/styles/rose/components/form/_input.scss b/addons/rose/addon/styles/rose/components/form/_input.scss index 0895f7f17..c5cd9f8cb 100644 --- a/addons/rose/addon/styles/rose/components/form/_input.scss +++ b/addons/rose/addon/styles/rose/components/form/_input.scss @@ -19,6 +19,7 @@ .rose-form-input-field { @include type.type(m); + margin-bottom: sizing.rems(xs); padding: sizing.rems(xs); color: var(--stark); background-color: var(--subtle); diff --git a/addons/rose/addon/styles/rose/components/form/_select.scss b/addons/rose/addon/styles/rose/components/form/_select.scss index 9effb0b80..a6293b4f0 100644 --- a/addons/rose/addon/styles/rose/components/form/_select.scss +++ b/addons/rose/addon/styles/rose/components/form/_select.scss @@ -24,6 +24,7 @@ appearance: none; vertical-align: middle; display: inline-block; + margin-bottom: sizing.rems(xs); padding: sizing.rems(xs) sizing.rems(xxl) sizing.rems(xs) sizing.rems(xs); border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2); border-radius: sizing.rems(xxxs); diff --git a/addons/rose/addon/styles/rose/components/form/errors/_index.scss b/addons/rose/addon/styles/rose/components/form/errors/_index.scss index 7953f5b65..f422a3ca5 100644 --- a/addons/rose/addon/styles/rose/components/form/errors/_index.scss +++ b/addons/rose/addon/styles/rose/components/form/errors/_index.scss @@ -1,6 +1,6 @@ @use '../../../variables/sizing'; -@import "message"; +@import 'message'; .rose-form-errors { margin-bottom: sizing.rems(l); diff --git a/addons/rose/addon/templates/components/rose/form/checkbox.hbs b/addons/rose/addon/templates/components/rose/form/checkbox.hbs index e2aa2c6ce..f24ed4478 100644 --- a/addons/rose/addon/templates/components/rose/form/checkbox.hbs +++ b/addons/rose/addon/templates/components/rose/form/checkbox.hbs @@ -6,12 +6,15 @@ id={{this.id}} disabled={{@disabled}} @type="checkbox" - @checked={{if @checked true false}} /> + @checked={{if @checked true false}} + aria-describedby="{{if @error (concat 'errors-' this.id)}}" /> - {{yield}} + {{yield (hash + errors=(component 'rose/form/errors' id=(concat 'errors-' this.id)) + )}} diff --git a/addons/rose/addon/templates/components/rose/form/errors.hbs b/addons/rose/addon/templates/components/rose/form/errors.hbs index dffe51b70..70e523944 100644 --- a/addons/rose/addon/templates/components/rose/form/errors.hbs +++ b/addons/rose/addon/templates/components/rose/form/errors.hbs @@ -1,4 +1,4 @@ -
+
{{yield (hash message=(component 'rose/form/errors/message') )}} diff --git a/addons/rose/addon/templates/components/rose/form/input.hbs b/addons/rose/addon/templates/components/rose/form/input.hbs index 9a79d8935..273ce0dc2 100644 --- a/addons/rose/addon/templates/components/rose/form/input.hbs +++ b/addons/rose/addon/templates/components/rose/form/input.hbs @@ -19,8 +19,10 @@ disabled={{@disabled}} @type={{@type}} @value={{@value}} - aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}}" /> + aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}} {{if @error (concat 'errors-' this.id)}}" /> - {{yield}} + {{yield (hash + errors=(component 'rose/form/errors' id=(concat 'errors-' this.id)) + )}}
diff --git a/addons/rose/addon/templates/components/rose/form/select.hbs b/addons/rose/addon/templates/components/rose/form/select.hbs index d695ca9fd..db870f1fd 100644 --- a/addons/rose/addon/templates/components/rose/form/select.hbs +++ b/addons/rose/addon/templates/components/rose/form/select.hbs @@ -12,13 +12,17 @@ {{/if}} - {{yield (component 'rose/form/select/select' - id=this.id - name=@name - disabled=@disabled - value=@value - onChange=@onChange - helperText=@helperText + {{yield (hash + field=(component 'rose/form/select/select' + id=this.id + name=@name + disabled=@disabled + value=@value + onChange=@onChange + helperText=@helperText + error=@error + ) + errors=(component 'rose/form/errors' id=(concat 'errors-' this.id)) )}}
diff --git a/addons/rose/addon/templates/components/rose/form/select/select.hbs b/addons/rose/addon/templates/components/rose/form/select/select.hbs index ee183f90c..3d780deaa 100644 --- a/addons/rose/addon/templates/components/rose/form/select/select.hbs +++ b/addons/rose/addon/templates/components/rose/form/select/select.hbs @@ -5,7 +5,7 @@ disabled={{@disabled}} @value={{@value}} @onChange={{@onChange}} - aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}}" + aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}} {{if @error (concat 'errors-' this.id)}}" as |xs| > {{yield xs}} diff --git a/addons/rose/addon/templates/components/rose/form/textarea.hbs b/addons/rose/addon/templates/components/rose/form/textarea.hbs index 9ff48348d..1d01effa4 100644 --- a/addons/rose/addon/templates/components/rose/form/textarea.hbs +++ b/addons/rose/addon/templates/components/rose/form/textarea.hbs @@ -18,8 +18,10 @@ id={{this.id}} disabled={{@disabled}} @value={{@value}} - aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}}" /> + aria-describedby="{{if @helperText (concat 'helper-text-' this.id)}} {{if @error (concat 'errors-' this.id)}}" /> - {{yield}} + {{yield (hash + errors=(component 'rose/form/errors' id=(concat 'errors-' this.id)) + )}} diff --git a/addons/rose/app/components/rose/form/errors.js b/addons/rose/app/components/rose/form/errors.js index 519fba0ae..7acb47a9d 100644 --- a/addons/rose/app/components/rose/form/errors.js +++ b/addons/rose/app/components/rose/form/errors.js @@ -1 +1 @@ -export { default } from 'rose/components/rose/form/errors'; \ No newline at end of file +export { default } from 'rose/components/rose/form/errors'; diff --git a/addons/rose/app/components/rose/form/errors/message.js b/addons/rose/app/components/rose/form/errors/message.js index 6e518bea4..4a4ac97dd 100644 --- a/addons/rose/app/components/rose/form/errors/message.js +++ b/addons/rose/app/components/rose/form/errors/message.js @@ -1 +1 @@ -export { default } from 'rose/components/rose/form/errors/message'; \ No newline at end of file +export { default } from 'rose/components/rose/form/errors/message'; diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs index 60cd33e50..70c9d1747 100644 --- a/addons/rose/tests/dummy/app/templates/index.hbs +++ b/addons/rose/tests/dummy/app/templates/index.hbs @@ -107,25 +107,25 @@ Dummy app index template @label="Label" @helperText="Helper text" @onChange={{fn this.actionHandler}} - as |Field| + as |field| > - + Choose an option Value 1 Value 2 Value 3 - + - + Choose an option - + diff --git a/addons/rose/tests/integration/components/rose/form-test.js b/addons/rose/tests/integration/components/rose/form-test.js index 12e30877b..3469cc830 100644 --- a/addons/rose/tests/integration/components/rose/form-test.js +++ b/addons/rose/tests/integration/components/rose/form-test.js @@ -45,9 +45,9 @@ module('Integration | Component | rose/form', function (hooks) { @label="Select" @name="select-field" @onChange={{this.select}} - as |Field| + as |field| > - + diff --git a/addons/rose/tests/integration/components/rose/form/checkbox-test.js b/addons/rose/tests/integration/components/rose/form/checkbox-test.js index 9c2d4e479..6ceb4abf9 100644 --- a/addons/rose/tests/integration/components/rose/form/checkbox-test.js +++ b/addons/rose/tests/integration/components/rose/form/checkbox-test.js @@ -12,6 +12,23 @@ module('Integration | Component | rose/form/checkbox', function (hooks) { assert.ok(await find('input')); }); + test('it displays optional errors', async function (assert) { + await render(hbs` + + + An error occurred. + + + `); + const fieldEl = find('input'); + const id = fieldEl.id; + const errorsId = `errors-${id}`; + const errorMessageEl = find('.rose-form-errors'); + assert.equal(errorMessageEl.id, errorsId); + assert.equal(errorMessageEl.textContent.trim(), 'An error occurred.'); + assert.equal(fieldEl.getAttribute('aria-describedby').trim(), errorsId); + }); + test('it is not checked by default', async function (assert) { await render(hbs``); assert.equal(await find('input').checked, false); diff --git a/addons/rose/tests/integration/components/rose/form/errors-test.js b/addons/rose/tests/integration/components/rose/form/errors-test.js index db9bbe8c3..b01cd74d1 100644 --- a/addons/rose/tests/integration/components/rose/form/errors-test.js +++ b/addons/rose/tests/integration/components/rose/form/errors-test.js @@ -3,10 +3,10 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, find } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; -module('Integration | Component | rose/form/errors', function(hooks) { +module('Integration | Component | rose/form/errors', function (hooks) { setupRenderingTest(hooks); - test('it renders', async function(assert) { + test('it renders', async function (assert) { assert.expect(3); await render(hbs` @@ -15,7 +15,13 @@ module('Integration | Component | rose/form/errors', function(hooks) { `); assert.ok(find('.rose-form-errors')); - assert.equal(find('.rose-form-error-message:first-child').textContent.trim(), 'An error occurred.'); - assert.equal(find('.rose-form-error-message:last-child').textContent.trim(), 'Another error occurred.'); + assert.equal( + find('.rose-form-error-message:first-child').textContent.trim(), + 'An error occurred.' + ); + assert.equal( + find('.rose-form-error-message:last-child').textContent.trim(), + 'Another error occurred.' + ); }); }); diff --git a/addons/rose/tests/integration/components/rose/form/errors/message-test.js b/addons/rose/tests/integration/components/rose/form/errors/message-test.js index a88fcd78d..66662d440 100644 --- a/addons/rose/tests/integration/components/rose/form/errors/message-test.js +++ b/addons/rose/tests/integration/components/rose/form/errors/message-test.js @@ -3,10 +3,10 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; -module('Integration | Component | rose/form/errors/message', function(hooks) { +module('Integration | Component | rose/form/errors/message', function (hooks) { setupRenderingTest(hooks); - test('it renders', async function(assert) { + test('it renders', async function (assert) { await render(hbs` An error occurred. diff --git a/addons/rose/tests/integration/components/rose/form/input-test.js b/addons/rose/tests/integration/components/rose/form/input-test.js index 9f2b2da3a..424a9cba8 100644 --- a/addons/rose/tests/integration/components/rose/form/input-test.js +++ b/addons/rose/tests/integration/components/rose/form/input-test.js @@ -20,7 +20,27 @@ module('Integration | Component | rose/form/input', function (hooks) { const helperTextEl = find('.rose-form-input-helper-text'); assert.equal(helperTextEl.textContent.trim(), 'Help me'); assert.equal(helperTextEl.id, helperId); - assert.equal(fieldEl.getAttribute('aria-describedby'), helperId); + assert.equal(fieldEl.getAttribute('aria-describedby').trim(), helperId); + }); + + test('it displays optional errors', async function (assert) { + await render(hbs` + + + An error occurred. + + + `); + const fieldEl = find('input'); + const id = fieldEl.id; + const errorsId = `errors-${id}`; + const errorMessageEl = find('.rose-form-errors'); + assert.equal(errorMessageEl.id, errorsId); + assert.equal(errorMessageEl.textContent.trim(), 'An error occurred.'); + assert.equal( + fieldEl.getAttribute('aria-describedby').split(' ')[1], + errorsId + ); }); test('it is type="text" by default', async function (assert) { diff --git a/addons/rose/tests/integration/components/rose/form/select-test.js b/addons/rose/tests/integration/components/rose/form/select-test.js index a789fa5aa..1f30200fb 100644 --- a/addons/rose/tests/integration/components/rose/form/select-test.js +++ b/addons/rose/tests/integration/components/rose/form/select-test.js @@ -12,14 +12,14 @@ module('Integration | Component | rose/form/select', function (hooks) { - + Choose an option Value 1 Value 2 Value 3 - + `); assert.equal(find('label').textContent.trim(), 'Label'); @@ -34,11 +34,11 @@ module('Integration | Component | rose/form/select', function (hooks) { @label="Label" @helperText="Help me" @onChange={{fn this.onChange}} - as |Field| + as |field| > - + Choose an option - + `); const fieldEl = find('select'); @@ -47,7 +47,37 @@ module('Integration | Component | rose/form/select', function (hooks) { const helperTextEl = find('.rose-form-select-helper-text'); assert.equal(helperTextEl.textContent.trim(), 'Help me'); assert.equal(helperTextEl.id, helperId); - assert.equal(fieldEl.getAttribute('aria-describedby'), helperId); + assert.equal(fieldEl.getAttribute('aria-describedby').trim(), helperId); + }); + + test('it displays optional errors', async function (assert) { + this.onChange = () => {}; + await render(hbs` + + + Choose an option + + + An error occurred. + + + `); + const fieldEl = find('select'); + const id = fieldEl.id; + const errorsId = `errors-${id}`; + const errorMessageEl = find('.rose-form-errors'); + assert.equal(errorMessageEl.id, errorsId); + assert.equal(errorMessageEl.textContent.trim(), 'An error occurred.'); + assert.equal( + fieldEl.getAttribute('aria-describedby').split(' ')[1], + errorsId + ); }); test('it supports disabled attribute', async function (assert) { @@ -56,11 +86,11 @@ module('Integration | Component | rose/form/select', function (hooks) { - + Choose an option - + `); assert.ok(find('[disabled]')); @@ -73,12 +103,12 @@ module('Integration | Component | rose/form/select', function (hooks) { - + value-1 value-2 - + `); assert.equal(find('select').value, 'value-1'); @@ -104,12 +134,12 @@ module('Integration | Component | rose/form/select', function (hooks) { - + Choose an option value-1 - + `); await fillIn('[name="my-select"]', 'value-1'); diff --git a/addons/rose/tests/integration/components/rose/form/textarea-test.js b/addons/rose/tests/integration/components/rose/form/textarea-test.js index 53d5cacf2..f1e5f2a6c 100644 --- a/addons/rose/tests/integration/components/rose/form/textarea-test.js +++ b/addons/rose/tests/integration/components/rose/form/textarea-test.js @@ -20,7 +20,27 @@ module('Integration | Component | rose/form/textarea', function (hooks) { const helperTextEl = find('.rose-form-input-helper-text'); assert.equal(helperTextEl.textContent.trim(), 'Help me'); assert.equal(helperTextEl.id, helperId); - assert.equal(fieldEl.getAttribute('aria-describedby'), helperId); + assert.equal(fieldEl.getAttribute('aria-describedby').trim(), helperId); + }); + + test('it displays optional errors', async function (assert) { + await render(hbs` + + + An error occurred. + + + `); + const fieldEl = find('textarea'); + const id = fieldEl.id; + const errorsId = `errors-${id}`; + const errorMessageEl = find('.rose-form-errors'); + assert.equal(errorMessageEl.id, errorsId); + assert.equal(errorMessageEl.textContent.trim(), 'An error occurred.'); + assert.equal( + fieldEl.getAttribute('aria-describedby').split(' ')[1], + errorsId + ); }); test('it supports disabled attribute', async function (assert) {