chore: 🤖 add form actions component and tweak form spacing

This commit is contained in:
Randall Morey committed 2020-05-27 11:15:31 -04:00
1 parent 9249bf6332
commit ce4135ea79
13 files changed
+190 -48

No files matched your search

@@ -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
* <Rose::Form::Actions
* @submitText="Save"
* @cancelText="Cancel"
* @showCancel={{true}}
* @cancel=(fn @cancel) />
*/
export default Component.extend({
layout,
tagName: '',
/**
* @type {boolean}
*/
showCancel: true,
});
@@ -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';
<Meta title="Rose|Form/Actions" component="Form/Actions" />
# 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
<Rose::Form::Actions
@submitText="Save"
@cancelText="Cancel"
@showCancel={{true}}
@disabled={{false}}
@cancel=(fn @cancel) />
```
<Preview>
<Story name="Basic Form Actions">{{
template: hbs`
<Rose::Form::Actions
@submitText={{submitText}}
@cancelText={{cancelText}}
@showCancel={{showCancel}}
@disabled={{disabled}}
@cancel={{onCancel}} />
`,
context: {
submitText: text('submitText', 'Save'),
cancelText: text('cancelText', 'Cancel'),
showCancel: boolean('showCancel', true),
disabled: boolean('disabled', false),
onCancel: action('cancel'),
},
}}</Story>
</Preview>
@@ -0,0 +1,3 @@
.rose-form-actions {
//display: flex;
}
@@ -2,3 +2,4 @@
@import 'checkbox';
@import 'select';
@import 'radio';
@import 'actions';
@@ -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);
@@ -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);
@@ -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;
}
}
}
}
@@ -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}}
<Rose::Icon
@name={{@iconLeft}}
@size="medium"
/>
{{/if}}
<span class="rose-button-wrapper">
{{#if @iconLeft}}
<Rose::Icon
@name={{@iconLeft}}
@size="medium"
/>
{{/if}}
<span class="rose-button-body">
{{yield}}
<span class="rose-button-body">
{{yield}}
</span>
{{#if @iconOnly}}
<Rose::Icon
@name={{@iconOnly}}
@size="medium"
/>
{{/if}}
{{#if @iconRight}}
<Rose::Icon
@name={{@iconRight}}
@size="medium"
/>
{{/if}}
</span>
{{#if @iconOnly}}
<Rose::Icon
@name={{@iconOnly}}
@size="medium"
/>
{{/if}}
{{#if @iconRight}}
<Rose::Icon
@name={{@iconRight}}
@size="medium"
/>
{{/if}}
</button>
@@ -0,0 +1,19 @@
<div class="rose-form-actions" ...attributes>
<Rose::Button
@disabled={{@disabled}}
@submit={{true}}
@style="primary"
>
{{@submitText}}
</Rose::Button>
{{#if this.showCancel}}
<Rose::Button
@disabled={{@disabled}}
@style="secondary"
{{on "click" @cancel}}
>
{{@cancelText}}
</Rose::Button>
{{/if}}
</div>
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/form/actions';
@@ -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);
}
@@ -50,7 +50,7 @@ Dummy app index template
<Rose::Form::Select
@label="Label"
@helperText="Helper text"
@onChange={{fn this.selectChange}}
@onChange={{fn this.actionHandler}}
as |select|
>
<select.option>Choose an option</select.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|
>
<select.option>Choose an option</select.option>
</Rose::Form::Select>
<h3>Form Actions</h3>
<Rose::Form::Actions
@submitText="Save"
@cancelText="Cancel"
@cancel={{this.actionHandler}} />
<Rose::Form::Input
@label="Label"
@value="Read only"
@@ -0,0 +1,38 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | rose/form/actions', function (hooks) {
setupRenderingTest(hooks);
test('it renders a submit button and a regular button', async function (assert) {
assert.expect(2);
this.cancel = () => {};
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"])');
});
});