diff --git a/addons/rose/addon/components/rose/form/fieldset/index.hbs b/addons/rose/addon/components/rose/form/fieldset/index.hbs new file mode 100644 index 000000000..c610023dd --- /dev/null +++ b/addons/rose/addon/components/rose/form/fieldset/index.hbs @@ -0,0 +1,21 @@ +
+ + {{yield to='title'}} + + + {{#if (has-block "description")}} +
+ {{yield to='description'}} +
+ {{/if}} + +
+ {{yield to='body'}} +
+
\ No newline at end of file diff --git a/addons/rose/addon/components/rose/form/fieldset/index.js b/addons/rose/addon/components/rose/form/fieldset/index.js new file mode 100644 index 000000000..4e2a7790e --- /dev/null +++ b/addons/rose/addon/components/rose/form/fieldset/index.js @@ -0,0 +1,11 @@ +import Component from '@glimmer/component'; +import { generateComponentID } from '../../../../utilities/component-auto-id'; + +export default class RoseFormFieldsetComponent extends Component { + // =attributes + + /** + * @type {string} + */ + id = generateComponentID(); +} diff --git a/addons/rose/addon/components/rose/form/fieldset/index.stories.mdx b/addons/rose/addon/components/rose/form/fieldset/index.stories.mdx new file mode 100644 index 000000000..9dd61a4c6 --- /dev/null +++ b/addons/rose/addon/components/rose/form/fieldset/index.stories.mdx @@ -0,0 +1,29 @@ +import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@storybook/addon-actions'; +import { text, boolean, select } from '@storybook/addon-knobs'; + + + +# Form Fieldset + +Renders a simple fieldset with title, description and body. + +```html + + <:title>Title + <:description>Description + <:body>Rest of the form + +``` + + + {{ + template: hbs` + + <:title>Title + <:description>Description + <:body>Rest of the form + ` + }} + \ No newline at end of file diff --git a/addons/rose/addon/styles/rose/components/form/_fieldset.scss b/addons/rose/addon/styles/rose/components/form/_fieldset.scss new file mode 100644 index 000000000..8696ad562 --- /dev/null +++ b/addons/rose/addon/styles/rose/components/form/_fieldset.scss @@ -0,0 +1,14 @@ +@use '../../variables/sizing'; +@use '../../utilities/type'; + +.rose-form-fieldset { + .rose-form-fieldset-title { + @include type.type(l, bold); + } + + .rose-form-fieldset-description { + margin: sizing.rems(xxs) 0; + @include type.type(xs); + color: var(--ui-gray); + } +} diff --git a/addons/rose/addon/styles/rose/components/form/_index.scss b/addons/rose/addon/styles/rose/components/form/_index.scss index 815a1aaf5..73137ac8d 100644 --- a/addons/rose/addon/styles/rose/components/form/_index.scss +++ b/addons/rose/addon/styles/rose/components/form/_index.scss @@ -5,3 +5,4 @@ @import 'checkbox'; @import 'select'; @import 'radio'; +@import 'fieldset'; diff --git a/addons/rose/app/components/rose/form/fieldset.js b/addons/rose/app/components/rose/form/fieldset.js new file mode 100644 index 000000000..9619ad95c --- /dev/null +++ b/addons/rose/app/components/rose/form/fieldset.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/form/fieldset'; diff --git a/addons/rose/tests/integration/components/rose/form/fieldset-test.js b/addons/rose/tests/integration/components/rose/form/fieldset-test.js new file mode 100644 index 000000000..a75efce1a --- /dev/null +++ b/addons/rose/tests/integration/components/rose/form/fieldset-test.js @@ -0,0 +1,89 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, find } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | rose/form/fieldset', function (hooks) { + setupRenderingTest(hooks); + + // Global values for mocking + this.mockTitle = 'Title'; + this.mockDescription = 'Description'; + this.mockBody = 'Rest of the form'; + + // Wrap the rendering in a function to avoid repeat code + async function renderComponent(noDescription) { + if (noDescription === true) { + return render(hbs` + + <:title>{{this.mockTitle}} + <:body>{{this.mockBody}} + + `); + } + + return render(hbs` + + <:title>{{this.mockTitle}} + <:description>{{this.mockDescription}} + <:body>{{this.mockBody}} + + `); + } + + test('it renders the full component', async function (assert) { + assert.expect(4); + await renderComponent(false); + assert.ok(find('.rose-form-fieldset')); + assert.equal( + find('.rose-form-fieldset-title').textContent.trim(), + this.mockTitle + ); + assert.equal( + find('.rose-form-fieldset-description').textContent.trim(), + this.mockDescription + ); + assert.equal( + find('.rose-form-fieldset-body').textContent.trim(), + this.mockBody + ); + }); + + test('it renders with no description', async function (assert) { + assert.expect(4); + await renderComponent(true); + assert.ok(find('.rose-form-fieldset')); + assert.equal( + find('.rose-form-fieldset-title').textContent.trim(), + this.mockTitle + ); + assert.notOk(find('.rose-form-fieldset-description')); + assert.equal( + find('.rose-form-fieldset-body').textContent.trim(), + this.mockBody + ); + }); + + test('it supports aria description for fieldset with description provided', async function (assert) { + assert.expect(1); + await renderComponent(false); + const descriptionElement = find('.rose-form-fieldset-description'); + const fieldsetElement = find('.rose-form-fieldset'); + + assert.equal( + fieldsetElement.getAttribute('aria-describedby'), + descriptionElement.id, + 'Fieldset is described by description' + ); + }); + + test('it does not support aria description for fieldset with no description provided', async function (assert) { + assert.expect(3); + await renderComponent(true); + const fieldsetElement = find('.rose-form-fieldset'); + + assert.ok(find('.rose-form-fieldset')); + assert.notOk(find('.rose-form-fieldset-description')); + assert.notOk(fieldsetElement.getAttribute('aria-describedby')); + }); +});