chore: 🤖 add select field component
This commit is contained in:
12 files changed
+335
-2
No files matched your search
@@ -0,0 +1,27 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../../templates/components/rose/form/select';
|
||||
|
||||
/**
|
||||
* A `<select>` element context component which yields option components.
|
||||
*
|
||||
* @example
|
||||
* <Rose::Form::Select
|
||||
* @id="select-id-1"
|
||||
* name="select-field-name"
|
||||
* @value="current-value"
|
||||
* @label="Label"
|
||||
* @helperText="Helper text"
|
||||
* @error={{true}}
|
||||
* @onChange={{fn this.selectChange}}
|
||||
* disabled={{true}}
|
||||
* as |select|
|
||||
* >
|
||||
* <select.option>Choose an option</select.option>
|
||||
* <select.option @value="current-value">Curret value</select.option>
|
||||
* </Rose::Form::Select>
|
||||
*/
|
||||
export default Component.extend({
|
||||
layout,
|
||||
|
||||
tagName: '',
|
||||
});
|
||||
@@ -1,2 +1,3 @@
|
||||
@import 'input';
|
||||
@import 'checkbox';
|
||||
@import 'select';
|
||||
@@ -0,0 +1,69 @@
|
||||
@use '../../variables/sizing';
|
||||
@use '../../utilities/type';
|
||||
|
||||
.rose-form-select {
|
||||
margin-bottom: sizing.rems(m);
|
||||
|
||||
.rose-form-select-label {
|
||||
@include type.type(s, semibold);
|
||||
display: block;
|
||||
margin-bottom: sizing.rems(xxs);
|
||||
}
|
||||
|
||||
.rose-form-select-helper-text {
|
||||
@include type.type(xs);
|
||||
display: block;
|
||||
color: var(--ui-gray);
|
||||
margin-bottom: sizing.rems(xxs);
|
||||
}
|
||||
|
||||
.rose-form-select-field {
|
||||
@include type.type(s);
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
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);
|
||||
box-shadow: 0 $size-xxs $size-xxs
|
||||
rgba(var(--stark-components), var(--opacity-2));
|
||||
background: var(--ui-gray-subtler-5)
|
||||
url('data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M12%205.83L15.17%209l1.41-1.41L12%203%207.41%207.59%208.83%209%2012%205.83zm0%2012.34L8.83%2015l-1.41%201.41L12%2021l4.59-4.59L15.17%2015%2012%2018.17z%22%20fill%3D%22%23525761%22%2F%3E%3C%2Fsvg%3E')
|
||||
no-repeat right sizing.rems(xs) center;
|
||||
background-size: sizing.rems(l);
|
||||
|
||||
&:not(:disabled) {
|
||||
&:hover {
|
||||
border-color: var(--ui-gray-subtler-1);
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--info);
|
||||
background-color: var(--ui-gray-subtler-5);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--ui-gray-subtler-1);
|
||||
background-image: url('data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M12%205.83L15.17%209l1.41-1.41L12%203%207.41%207.59%208.83%209%2012%205.83zm0%2012.34L8.83%2015l-1.41%201.41L12%2021l4.59-4.59L15.17%2015%2012%2018.17z%22%20fill%3D%22%238E96A3%22%2F%3E%3C%2Fsvg%3E');
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: var(--failure);
|
||||
|
||||
.rose-form-select-helper-text {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.rose-form-select-field {
|
||||
border-color: var(--failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<div class="rose-form-select{{if @error " error"}}">
|
||||
|
||||
<label class="rose-form-select-label" for={{@id}}>
|
||||
{{@label}}
|
||||
</label>
|
||||
|
||||
{{#if @helperText}}
|
||||
<span class="rose-form-select-helper-text">{{@helperText}}</span>
|
||||
{{/if}}
|
||||
|
||||
<XSelect
|
||||
...attributes
|
||||
class="rose-form-select-field"
|
||||
id={{@id}}
|
||||
@value={{@value}}
|
||||
@onChange={{@onChange}}
|
||||
as |xs|
|
||||
>
|
||||
{{yield xs}}
|
||||
</XSelect>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/form/select';
|
||||
@@ -0,0 +1,10 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class IndexController extends Controller {
|
||||
@action
|
||||
selectChange(value) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(value);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,28 @@ Dummy app index template
|
||||
<br>
|
||||
|
||||
<h2>Form</h2>
|
||||
<Rose::Form::Select
|
||||
@label="Label"
|
||||
@helperText="Helper text"
|
||||
@onChange={{fn this.selectChange}}
|
||||
as |select|
|
||||
>
|
||||
<select.option>Choose an option</select.option>
|
||||
<select.option @value="value-1">Value 1</select.option>
|
||||
<select.option @value="value-2">Value 2</select.option>
|
||||
<select.option @value="value-3">Value 3</select.option>
|
||||
</Rose::Form::Select>
|
||||
|
||||
<Rose::Form::Select
|
||||
disabled={{true}}
|
||||
@label="Label"
|
||||
@helperText="Helper text"
|
||||
@onChange={{fn this.selectChange}}
|
||||
as |select|
|
||||
>
|
||||
<select.option>Choose an option</select.option>
|
||||
</Rose::Form::Select>
|
||||
|
||||
<Rose::Form::Input
|
||||
@label="Label"
|
||||
@value="Read only"
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"ember-source": "~3.12.0",
|
||||
"ember-source-channel-url": "^2.0.1",
|
||||
"ember-try": "^1.4.0",
|
||||
"emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774",
|
||||
"eslint-plugin-ember": "^7.7.2",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"loader.js": "^4.7.0",
|
||||
|
||||
@@ -12,7 +12,7 @@ export const Normal = () => ({
|
||||
@submit={{submit}}
|
||||
@disabled={{disabled}}
|
||||
@style={{style}}
|
||||
{{action onClick}}
|
||||
{{on "click" onClick}}
|
||||
>
|
||||
{{text}}
|
||||
</Rose::Button>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { text, boolean, select } from '@storybook/addon-knobs';
|
||||
|
||||
export default {
|
||||
title: 'Rose::Form::Select',
|
||||
};
|
||||
|
||||
export const Normal = () => ({
|
||||
template: hbs`
|
||||
<Rose::Form::Select
|
||||
@id={{id}}
|
||||
@value={{value}}
|
||||
@label={{label}}
|
||||
@helperText={{helperText}}
|
||||
disabled={{disabled}}
|
||||
@error={{error}}
|
||||
@onChange={{fn onChange}}
|
||||
as |select|
|
||||
>
|
||||
<select.option>Choose an option</select.option>
|
||||
<select.option @value="value-1">Value 1</select.option>
|
||||
<select.option @value="value-2">Value 2</select.option>
|
||||
<select.option @value="value-3">Value 3</select.option>
|
||||
</Rose::Form::Select>
|
||||
`,
|
||||
context: {
|
||||
value: select(
|
||||
'value',
|
||||
{
|
||||
null: null,
|
||||
'value-1': 'value-1',
|
||||
'value-2': 'value-2',
|
||||
'value-3': 'value-3',
|
||||
},
|
||||
null
|
||||
),
|
||||
id: text('id', 'field-id'),
|
||||
label: text('label', 'Label'),
|
||||
helperText: text('helperText', 'Helper text'),
|
||||
disabled: boolean('disabled', false),
|
||||
error: boolean('error', false),
|
||||
onChange: action('changed'),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, find, findAll, fillIn } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | rose/form/select', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
this.onChange = () => {};
|
||||
await render(hbs`
|
||||
<Rose::Form::Select
|
||||
@label="Label"
|
||||
@onChange={{fn this.onChange}}
|
||||
as |select|
|
||||
>
|
||||
<select.option>Choose an option</select.option>
|
||||
<select.option @value="value-1">Value 1</select.option>
|
||||
<select.option @value="value-2">Value 2</select.option>
|
||||
<select.option @value="value-3">Value 3</select.option>
|
||||
</Rose::Form::Select>
|
||||
`);
|
||||
assert.equal(find('label').textContent.trim(), 'Label');
|
||||
assert.ok(find('select'));
|
||||
assert.equal(findAll('option').length, 4);
|
||||
});
|
||||
|
||||
test('it displays optional helper text', async function (assert) {
|
||||
this.onChange = () => {};
|
||||
await render(hbs`
|
||||
<Rose::Form::Select
|
||||
@label="Label"
|
||||
@helperText="Helper text"
|
||||
@onChange={{fn this.onChange}}
|
||||
as |select|
|
||||
>
|
||||
<select.option>Choose an option</select.option>
|
||||
</Rose::Form::Select>
|
||||
`);
|
||||
assert.equal(
|
||||
find('.rose-form-select-helper-text').textContent.trim(),
|
||||
'Helper text'
|
||||
);
|
||||
});
|
||||
|
||||
test('it supports disabled attribute', async function (assert) {
|
||||
this.onChange = () => {};
|
||||
await render(hbs`
|
||||
<Rose::Form::Select
|
||||
@onChange={{fn this.onChange}}
|
||||
disabled={{true}}
|
||||
as |select|
|
||||
>
|
||||
<select.option>Choose an option</select.option>
|
||||
</Rose::Form::Select>
|
||||
`);
|
||||
assert.ok(find('[disabled]'));
|
||||
});
|
||||
|
||||
test('its value is bound to and udpated by @value', async function (assert) {
|
||||
this.onChange = () => {};
|
||||
this.value = 'value-1';
|
||||
await render(hbs`
|
||||
<Rose::Form::Select
|
||||
@onChange={{fn this.onChange}}
|
||||
@value={{value}}
|
||||
as |select|
|
||||
>
|
||||
<select.option @value="value-1">value-1</select.option>
|
||||
<select.option @value="value-2">value-2</select.option>
|
||||
</Rose::Form::Select>
|
||||
`);
|
||||
assert.equal(find('select').value, 'value-1');
|
||||
this.set('value', 'value-2');
|
||||
assert.equal(find('select').value, 'value-2');
|
||||
});
|
||||
|
||||
test('it triggers an action on change', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.onChange1 = (value) => {
|
||||
assert.equal(
|
||||
value,
|
||||
null,
|
||||
'On initial render, onChange is triggered with null'
|
||||
);
|
||||
this.set('onChange', this.onChange2);
|
||||
};
|
||||
this.onChange2 = (value) => {
|
||||
assert.equal(value, 'value-1', 'On change, value is passed in.');
|
||||
};
|
||||
this.onChange = this.onChange1;
|
||||
await render(hbs`
|
||||
<Rose::Form::Select
|
||||
name="my-select"
|
||||
@onChange={{fn this.onChange}}
|
||||
as |select|
|
||||
>
|
||||
<select.option>Choose an option</select.option>
|
||||
<select.option @value="value-1">value-1</select.option>
|
||||
</Rose::Form::Select>
|
||||
`);
|
||||
await fillIn('[name="my-select"]', 'value-1');
|
||||
});
|
||||
});
|
||||
@@ -844,6 +844,18 @@
|
||||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@bigtest/convergence@^1.1.1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@bigtest/convergence/-/convergence-1.1.2.tgz#1028f28378aae6876b3c07e577a2d4a320ad0acf"
|
||||
integrity sha512-f/tpOfdd5p40Omejmc+jwrNL0nZ0RDV2LEQQdIWYHtMdKfQQRQpdAhyfNVPLuLeKHw5D9dUX8v9WOrwGsMBTAw==
|
||||
|
||||
"@bigtest/interactor@^0.9.0":
|
||||
version "0.9.3"
|
||||
resolved "https://registry.yarnpkg.com/@bigtest/interactor/-/interactor-0.9.3.tgz#f83fa868fc5742a482114af15d22f50677d3a81f"
|
||||
integrity sha512-MKbhp/j1TtlSp1kIM1btvPrMbxn1eW2Notd5oUChBtEpnm8FDAiZIg6LED1aTsUxyRWf2qb5RlJyoy9HZjRgzQ==
|
||||
dependencies:
|
||||
"@bigtest/convergence" "^1.1.1"
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
|
||||
@@ -6163,7 +6175,7 @@ ember-assign-polyfill@^2.6.0:
|
||||
ember-cli-babel "^6.16.0"
|
||||
ember-cli-version-checker "^2.0.0"
|
||||
|
||||
ember-auto-import@^1.5.3:
|
||||
ember-auto-import@^1.2.15, ember-auto-import@^1.5.3:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-1.5.3.tgz#b32936f874d1ed7057ad2ed3f6116357820be44b"
|
||||
integrity sha512-7JfdunM1BmLy/lyUXu7uEoi0Gi4+dxkGM23FgIEyW5g7z4MidhP53Fc61t49oPSnq7+J4lLpbH1f6C+mDMgb4A==
|
||||
@@ -6302,6 +6314,16 @@ ember-cli-htmlbars-inline-precompile@^2.1.0:
|
||||
heimdalljs-logger "^0.1.9"
|
||||
silent-error "^1.1.0"
|
||||
|
||||
ember-cli-htmlbars@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-3.1.0.tgz#87806c2a0bca2ab52d4fb8af8e2215c1ca718a99"
|
||||
integrity sha512-cgvRJM73IT0aePUG7oQ/afB7vSRBV3N0wu9BrWhHX2zkR7A7cUBI7KC9VPk6tbctCXoM7BRGsCC4aIjF7yrfXA==
|
||||
dependencies:
|
||||
broccoli-persistent-filter "^2.3.1"
|
||||
hash-for-dep "^1.5.1"
|
||||
json-stable-stringify "^1.0.1"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
ember-cli-htmlbars@^4.0.0, ember-cli-htmlbars@^4.2.2:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-4.3.1.tgz#4af8adc21ab3c4953f768956b7f7d207782cb175"
|
||||
@@ -6749,6 +6771,15 @@ ember-try@^1.4.0:
|
||||
rsvp "^4.7.0"
|
||||
walk-sync "^1.1.3"
|
||||
|
||||
"emberx-select@github:adopted-ember-addons/emberx-select#bc5f774":
|
||||
version "4.0.0-beta.2"
|
||||
resolved "https://codeload.github.com/adopted-ember-addons/emberx-select/tar.gz/bc5f7746ae67d13e17fe2bd118cbed99b0e10aa2"
|
||||
dependencies:
|
||||
"@bigtest/interactor" "^0.9.0"
|
||||
ember-auto-import "^1.2.15"
|
||||
ember-cli-babel "^6.16.0"
|
||||
ember-cli-htmlbars "^3.0.0"
|
||||
|
||||
emit-function@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/emit-function/-/emit-function-0.0.2.tgz#e3a50b3d61be1bf8ca88b924bf713157a5bec124"
|
||||
|
||||
Reference in new issue
Block a user