diff --git a/addons/rose/addon/components/rose/form/actions.js b/addons/rose/addon/components/rose/form/actions.js new file mode 100644 index 000000000..4bfcbea85 --- /dev/null +++ b/addons/rose/addon/components/rose/form/actions.js @@ -0,0 +1,24 @@ +import Component from '@ember/component'; +import layout from '../../../templates/components/rose/form/actions'; + +/** + * A set of two form action buttons for submit and cancel. + * To handle submit, place a submit handler on the parent form element. + * To handle cancel button click, pass in a function to `@cancel`. + * + * @example + * + */ +export default Component.extend({ + layout, + tagName: '', + + /** + * @type {boolean} + */ + showCancel: true, +}); diff --git a/addons/rose/addon/components/rose/form/actions.stories.mdx b/addons/rose/addon/components/rose/form/actions.stories.mdx new file mode 100644 index 000000000..7517bddb6 --- /dev/null +++ b/addons/rose/addon/components/rose/form/actions.stories.mdx @@ -0,0 +1,42 @@ +import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; +import { hbs } from 'ember-cli-htmlbars'; +import { action } from '@storybook/addon-actions'; +import { text, boolean } from '@storybook/addon-knobs'; + + + +# Form Actions + +A simple collection of buttons for performing common actions like submit/save +and cancel. Form actions should always be used in the context of a form element +where the form has a submit action handler. The submit button does not directly +support actions. The cancel button supports a cancel action. + +```html + +``` + + + {{ + template: hbs` + + `, + context: { + submitText: text('submitText', 'Save'), + cancelText: text('cancelText', 'Cancel'), + showCancel: boolean('showCancel', true), + disabled: boolean('disabled', false), + onCancel: action('cancel'), + }, + }} + diff --git a/addons/rose/addon/styles/rose/components/form/_actions.scss b/addons/rose/addon/styles/rose/components/form/_actions.scss new file mode 100644 index 000000000..4c2d62a0a --- /dev/null +++ b/addons/rose/addon/styles/rose/components/form/_actions.scss @@ -0,0 +1,3 @@ +.rose-form-actions { + //display: flex; +} diff --git a/addons/rose/addon/styles/rose/components/form/_index.scss b/addons/rose/addon/styles/rose/components/form/_index.scss index c8c516f8c..29fa5e995 100644 --- a/addons/rose/addon/styles/rose/components/form/_index.scss +++ b/addons/rose/addon/styles/rose/components/form/_index.scss @@ -2,3 +2,4 @@ @import 'checkbox'; @import 'select'; @import 'radio'; +@import 'actions'; diff --git a/addons/rose/addon/styles/rose/components/form/_input.scss b/addons/rose/addon/styles/rose/components/form/_input.scss index 7116c6a76..0895f7f17 100644 --- a/addons/rose/addon/styles/rose/components/form/_input.scss +++ b/addons/rose/addon/styles/rose/components/form/_input.scss @@ -2,7 +2,7 @@ @use '../../utilities/type'; .rose-form-input { - margin-bottom: sizing.rems(m); + margin-bottom: sizing.rems(l); .rose-form-input-label { @include type.type(s, semibold); diff --git a/addons/rose/addon/styles/rose/components/form/_select.scss b/addons/rose/addon/styles/rose/components/form/_select.scss index 63cb16911..9effb0b80 100644 --- a/addons/rose/addon/styles/rose/components/form/_select.scss +++ b/addons/rose/addon/styles/rose/components/form/_select.scss @@ -2,7 +2,7 @@ @use '../../utilities/type'; .rose-form-select { - margin-bottom: sizing.rems(m); + margin-bottom: sizing.rems(l); .rose-form-select-label { @include type.type(s, semibold); diff --git a/addons/rose/addon/styles/rose/utilities/_button.scss b/addons/rose/addon/styles/rose/utilities/_button.scss index 3bb506024..f286ef5be 100644 --- a/addons/rose/addon/styles/rose/utilities/_button.scss +++ b/addons/rose/addon/styles/rose/utilities/_button.scss @@ -4,32 +4,39 @@ @mixin button { @include typography.type(button, semibold); - display: flex; - flex-direction: row; + display: inline-block; + margin-bottom: sizing.rems(l); + margin-right: sizing.rems(xs); padding: sizing.rems(off-xs) sizing.rems(l); border-radius: sizing.rems(xxxs); border: sizing.rems(xxxxs) solid; + background: none; - &.has-icon-left > .rose-icon:first-child { - margin-left: -(sizing.rems(xs)); - margin-right: sizing.rems(xxs); - } + .rose-button-wrapper { + display: flex; + flex-direction: row; - &.has-icon-right > .rose-icon:last-child { - margin-left: sizing.rems(xxs); - margin-right: -(sizing.rems(xs)); - } + &.has-icon-left > .rose-icon:first-child { + margin-left: -(sizing.rems(xs)); + margin-right: sizing.rems(xxs); + } - &.has-icon-only { - padding-right: sizing.rems(s); - padding-left: sizing.rems(s); - .rose-button-body { - position: absolute; - top: -1000px; - left: -1000px; - width: 1px; - height: 1px; - opacity: 0; + &.has-icon-right > .rose-icon:last-child { + margin-left: sizing.rems(xxs); + margin-right: -(sizing.rems(xs)); + } + + &.has-icon-only { + padding-right: sizing.rems(s); + padding-left: sizing.rems(s); + .rose-button-body { + position: absolute; + top: -1000px; + left: -1000px; + width: 1px; + height: 1px; + opacity: 0; + } } } } diff --git a/addons/rose/addon/templates/components/rose/button.hbs b/addons/rose/addon/templates/components/rose/button.hbs index f109c1407..a8ac2acbd 100644 --- a/addons/rose/addon/templates/components/rose/button.hbs +++ b/addons/rose/addon/templates/components/rose/button.hbs @@ -5,29 +5,30 @@ class="{{if @style (concat "rose-button-" @style)}} {{if @iconLeft "has-icon-left"}} {{if @iconRight "has-icon-right"}} {{if @iconOnly "has-icon-only"}}" > - {{#if @iconLeft}} - - {{/if}} + + {{#if @iconLeft}} + + {{/if}} - - {{yield}} + + {{yield}} + + + {{#if @iconOnly}} + + {{/if}} + + {{#if @iconRight}} + + {{/if}} - - {{#if @iconOnly}} - - {{/if}} - - {{#if @iconRight}} - - {{/if}} - diff --git a/addons/rose/addon/templates/components/rose/form/actions.hbs b/addons/rose/addon/templates/components/rose/form/actions.hbs new file mode 100644 index 000000000..47557fb7e --- /dev/null +++ b/addons/rose/addon/templates/components/rose/form/actions.hbs @@ -0,0 +1,19 @@ +
+ + {{@submitText}} + + + {{#if this.showCancel}} + + {{@cancelText}} + + {{/if}} +
diff --git a/addons/rose/app/components/rose/form/actions.js b/addons/rose/app/components/rose/form/actions.js new file mode 100644 index 000000000..debd30e78 --- /dev/null +++ b/addons/rose/app/components/rose/form/actions.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/form/actions'; diff --git a/addons/rose/tests/dummy/app/controllers/index.js b/addons/rose/tests/dummy/app/controllers/index.js index 3b56b102a..ff0929bb6 100644 --- a/addons/rose/tests/dummy/app/controllers/index.js +++ b/addons/rose/tests/dummy/app/controllers/index.js @@ -3,7 +3,7 @@ import { action } from '@ember/object'; export default class IndexController extends Controller { @action - selectChange(/*value*/) { + actionHandler(/*value*/) { // eslint-disable-next-line no-console //console.log(value); } diff --git a/addons/rose/tests/dummy/app/templates/index.hbs b/addons/rose/tests/dummy/app/templates/index.hbs index 400f1d6f1..40586ca08 100644 --- a/addons/rose/tests/dummy/app/templates/index.hbs +++ b/addons/rose/tests/dummy/app/templates/index.hbs @@ -50,7 +50,7 @@ Dummy app index template Choose an option @@ -63,12 +63,18 @@ Dummy app index template disabled={{true}} @label="Label" @helperText="Helper text" - @onChange={{fn this.selectChange}} + @onChange={{fn this.actionHandler}} as |select| > Choose an option +

Form Actions

+ + {}; + await render(hbs`{{rose/form/actions cancel=this.cancel}}`); + assert.ok(find('[type="submit"]')); + assert.ok(find('button:not([type="submit"])')); + }); + + test('it can disabled the buttons', async function (assert) { + assert.expect(2); + this.cancel = () => {}; + await render(hbs`{{rose/form/actions cancel=this.cancel disabled=true}}`); + assert.ok(find('[type="submit"]:disabled')); + assert.ok(find('button:not([type="submit"]):disabled')); + }); + + test('it optionally does not render the cancel button', async function (assert) { + assert.expect(2); + await render(hbs`{{rose/form/actions showCancel=false}}`); + assert.ok(find('[type="submit"]')); + assert.notOk(find('button:not([type="submit"])')); + }); + + test('it executes a function on cancel click', async function (assert) { + assert.expect(1); + this.cancel = () => assert.ok(true, 'cancel was clicked'); + await render(hbs`{{rose/form/actions cancel=this.cancel}}`); + await click('button:not([type="submit"])'); + }); +});