chore: 🤖 add form actions component and tweak form spacing
This commit is contained in:
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"])');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user