ui: Add `<State /> and `{{state-matches}}` ember component/helper (#7556)
This commit adds 2 ember component/helpers and a service to contain the shared functionality for matching/rendering content dependent on state identifiers. Currently a `service.state` method has been added to easily make manual state objects, but these are built towards using `xstate` to manage UI state in some of our future components. We've added some tests here, and we aren't currently using these components anywhere in this commit.
This commit is contained in:
parent
f0062f5cbe
commit
87ab332bce
|
@ -0,0 +1,38 @@
|
|||
## State
|
||||
|
||||
`<State @state={{matchableStateObject}} @matches="idle">Currently Idle</State>`
|
||||
|
||||
`<State />` is a renderless component that eases rendering of different states
|
||||
from within templates. State objects could be manually made state objects and
|
||||
xstate state objects. It's very similar to a normal conditional in that if the
|
||||
state identifier matches the current state, the contents of the component will
|
||||
be shown.
|
||||
|
||||
### Arguments
|
||||
|
||||
| Argument/Attribute | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `state` | `object` | | An object that implements a `match` method |
|
||||
| `matches` | `String\|Array` | | A state identifier (or array of state identifiers) to match on |
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
```handlebars
|
||||
<State @state={{state}} @matches="idle">
|
||||
Currently Idle
|
||||
</State>
|
||||
<State @state={{state}} @matches="loading">
|
||||
Currently Loading
|
||||
</State>
|
||||
<State @state={{state}} @matches={{array 'loading' 'idle'}}>
|
||||
Idle and loading
|
||||
</State>
|
||||
```
|
||||
|
||||
### See
|
||||
|
||||
- [Component Source Code](./index.js)
|
||||
- [Template Source Code](./index.hbs)
|
||||
|
||||
---
|
|
@ -0,0 +1,3 @@
|
|||
{{#if rendering}}
|
||||
{{yield}}
|
||||
{{/if}}
|
|
@ -0,0 +1,18 @@
|
|||
import Component from '@ember/component';
|
||||
import { set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Component.extend({
|
||||
service: service('state'),
|
||||
tagName: '',
|
||||
didReceiveAttrs: function() {
|
||||
if (typeof this.state === 'undefined') {
|
||||
return;
|
||||
}
|
||||
let match = true;
|
||||
if (typeof this.matches !== 'undefined') {
|
||||
match = this.service.matches(this.state, this.matches);
|
||||
}
|
||||
set(this, 'rendering', match);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Helper.extend({
|
||||
state: service('state'),
|
||||
compute([state, values], hash) {
|
||||
return this.state.matches(state, values);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import Service from '@ember/service';
|
||||
export default Service.extend({
|
||||
matches: function(state, matches) {
|
||||
const values = Array.isArray(matches) ? matches : [matches];
|
||||
return values.some(item => {
|
||||
return state.matches(item);
|
||||
});
|
||||
},
|
||||
state: function(cb) {
|
||||
return {
|
||||
matches: cb,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | state', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
this.set('state', {
|
||||
matches: function(id) {
|
||||
return id === 'idle';
|
||||
},
|
||||
});
|
||||
await render(hbs`
|
||||
<State @state={{state}} @matches="idle">
|
||||
Currently Idle
|
||||
</State>
|
||||
`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), 'Currently Idle');
|
||||
await render(hbs`
|
||||
<State @state={{state}} @matches="loading">
|
||||
Currently Idle
|
||||
</State>
|
||||
`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Helper | state-matches', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it returns true/false when the state or state in an array matches', async function(assert) {
|
||||
this.set('state', {
|
||||
matches: function(id) {
|
||||
return id === 'idle';
|
||||
},
|
||||
});
|
||||
|
||||
await render(hbs`{{state-matches state 'idle'}}`);
|
||||
assert.equal(this.element.textContent.trim(), 'true');
|
||||
|
||||
await render(hbs`{{state-matches state 'loading'}}`);
|
||||
assert.equal(this.element.textContent.trim(), 'false');
|
||||
|
||||
await render(hbs`{{state-matches state (array 'idle' 'loading')}}`);
|
||||
assert.equal(this.element.textContent.trim(), 'true');
|
||||
|
||||
await render(hbs`{{state-matches state (array 'loading' 'idle')}}`);
|
||||
assert.equal(this.element.textContent.trim(), 'true');
|
||||
|
||||
await render(hbs`{{state-matches state (array 'loading' 'deleting')}}`);
|
||||
assert.equal(this.element.textContent.trim(), 'false');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Service | state', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('.state creates a state matchable object', function(assert) {
|
||||
const service = this.owner.lookup('service:state');
|
||||
const actual = service.state(id => id === 'idle');
|
||||
assert.equal(typeof actual, 'object');
|
||||
assert.equal(typeof actual.matches, 'function');
|
||||
});
|
||||
test('.matches performs a match correctly', function(assert) {
|
||||
const service = this.owner.lookup('service:state');
|
||||
const state = service.state(id => id === 'idle');
|
||||
assert.equal(service.matches(state, 'idle'), true);
|
||||
assert.equal(service.matches(state, 'loading'), false);
|
||||
});
|
||||
test('.matches performs a match correctly when passed an array', function(assert) {
|
||||
const service = this.owner.lookup('service:state');
|
||||
const state = service.state(id => id === 'idle');
|
||||
assert.equal(service.matches(state, ['idle']), true);
|
||||
assert.equal(service.matches(state, ['loading', 'idle']), true);
|
||||
assert.equal(service.matches(state, ['loading', 'deleting']), false);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue