+ {{
+ 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 @@
-
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) {
/Rose::Form::Errors>
`);
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) {