ui: Exposes the <ToggleButton /> 'click' action (#7479)
This exposes an api for <ToggleButton /> so you can call it from elsewhere, specifically here we use the api.click to close the dropdown menus which is required is the DOM containing the toggle button isn't redrawn as is the case with external links in a dropdown menu
This commit is contained in:
parent
0a47a95588
commit
f0062f5cbe
|
@ -94,16 +94,16 @@
|
||||||
<BlockSlot @name="trigger">
|
<BlockSlot @name="trigger">
|
||||||
Help
|
Help
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="menu">
|
<BlockSlot @name="menu" as |id send keypressClick change|>
|
||||||
<li role="none" class="docs-link">
|
<li role="none" class="docs-link">
|
||||||
<a tabindex="-1" role="menuitem" href={{env 'CONSUL_DOCS_URL'}} rel="noopener noreferrer" target="_blank">Documentation</a>
|
<a tabindex="-1" role="menuitem" href={{env 'CONSUL_DOCS_URL'}} rel="noopener noreferrer" target="_blank" onclick={{change}}>Documentation</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="none" class="learn-link">
|
<li role="none" class="learn-link">
|
||||||
<a tabindex="-1" role="menuitem" href={{env 'CONSUL_DOCS_LEARN_URL'}} rel="noopener noreferrer" target="_blank">HashiCorp Learn</a>
|
<a tabindex="-1" role="menuitem" href={{env 'CONSUL_DOCS_LEARN_URL'}} rel="noopener noreferrer" target="_blank" onclick={{change}}>HashiCorp Learn</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator"></li>
|
<li role="separator"></li>
|
||||||
<li role="none" class="feedback-link">
|
<li role="none" class="feedback-link">
|
||||||
<a tabindex="-1" role="menuitem" href={{env 'CONSUL_REPO_ISSUES_URL'}} target="_blank" rel="noopener noreferrer">Provide Feedback</a>
|
<a tabindex="-1" role="menuitem" href={{env 'CONSUL_REPO_ISSUES_URL'}} target="_blank" rel="noopener noreferrer" onclick={{change}}>Provide Feedback</a>
|
||||||
</li>
|
</li>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
</PopoverMenu>
|
</PopoverMenu>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
{{yield (concat 'popover-menu-' guid)}}
|
{{yield (concat 'popover-menu-' guid)}}
|
||||||
<AriaMenu @keyboardAccess={{keyboardAccess}} as |change keypress ariaLabelledBy ariaControls ariaExpanded keypressClick|>
|
<AriaMenu @keyboardAccess={{keyboardAccess}} as |change keypress ariaLabelledBy ariaControls ariaExpanded keypressClick|>
|
||||||
<ToggleButton @checked={{ariaExpanded}} @onchange={{queue change (action "change")}} as |click|>
|
<ToggleButton
|
||||||
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{click}} id={{ariaLabelledBy}} aria-controls={{ariaControls}}>
|
@checked={{if keyboardAccess ariaExpanded expanded}}
|
||||||
|
@onchange={{queue change (action "change")}}
|
||||||
|
as |api|>
|
||||||
|
<Ref @target={{this}} @name="toggle" @value={{api}} />
|
||||||
|
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{this.toggle.click}} id={{ariaLabelledBy}} aria-controls={{ariaControls}}>
|
||||||
<YieldSlot @name="trigger">
|
<YieldSlot @name="trigger">
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</YieldSlot>
|
</YieldSlot>
|
||||||
|
@ -19,7 +23,7 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{/yield-slot}}
|
{{/yield-slot}}
|
||||||
<ul role="menu" id={{ariaControls}} aria-labelledby={{ariaLabelledBy}} aria-expanded={{ariaExpanded}}>
|
<ul role="menu" id={{ariaControls}} aria-labelledby={{ariaLabelledBy}} aria-expanded={{ariaExpanded}}>
|
||||||
<YieldSlot @name="menu" @params={{block-params (concat "popover-menu-" guid "-") send keypressClick}}>
|
<YieldSlot @name="menu" @params={{block-params (concat "popover-menu-" guid "-") send keypressClick this.toggle.click}}>
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</YieldSlot>
|
</YieldSlot>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
## ToggleButton
|
||||||
|
|
||||||
|
`<ToggleButton checked="checked" @onchange={{action 'change'}} as |api|>Toggle</ToggleButton>`
|
||||||
|
|
||||||
|
`<ToggleButton />` is a straightforward combination of a `<label>` and `<input type="checkbox" />` to allow you to easily setup CSS based (`input:checked ~ *`) visual toggling. The body of the component ends up inside the `<label>`.
|
||||||
|
|
||||||
|
Additionally, a `clickoutside` is currently included, so if the toggle is in an 'on' state, clicking outside the `<ToggleButton>` itself will un-toggle the component. This could be changed in a future version for this to be configurable/preventable and/or use/rely on a modifer instead.
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
| Argument/Attribute | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `checked` | `Boolean` | false | The default value of the toggle on/off (true/false) |
|
||||||
|
| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `target` that is a reference to the underlying standard DOM input element. |
|
||||||
|
|
||||||
|
### Methods/Actions/api
|
||||||
|
|
||||||
|
| Method/Action | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `click` | Fire a click event on the ToggleButton/input which reverse the state of the toggle. |
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
Here is an example of a simple CSS based dropdown menu. Note: The general sibling selector (`~`) should be used as the label/button itself is the adjacent sibling (`+`).
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
<div class="menu">
|
||||||
|
<ToggleButton>
|
||||||
|
Open Menu
|
||||||
|
</ToggleButton>
|
||||||
|
<ul>
|
||||||
|
<li><a href="">Link 1</a></li>
|
||||||
|
<li><a href="">Link 2</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.menu input ~ ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.menu input:checked ~ ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### See
|
||||||
|
|
||||||
|
- [Component Source Code](./index.js)
|
||||||
|
- [TemplateSource Code](./index.hbs)
|
||||||
|
|
||||||
|
---
|
|
@ -1,4 +1,13 @@
|
||||||
<input {{ref this "input"}} type="checkbox" checked={{if checked 'checked' undefined}} id={{concat 'toggle-button-' guid}} onchange={{action 'change'}} />
|
<input
|
||||||
|
...attributes
|
||||||
|
{{ref this "input"}}
|
||||||
|
type="checkbox"
|
||||||
|
checked={{if checked 'checked' undefined}}
|
||||||
|
id={{concat 'toggle-button-' guid}}
|
||||||
|
onchange={{action 'change'}}
|
||||||
|
/>
|
||||||
<label {{ref this "label"}} for={{concat 'toggle-button-' guid}}>
|
<label {{ref this "label"}} for={{concat 'toggle-button-' guid}}>
|
||||||
{{yield (action 'click')}}
|
{{yield (hash
|
||||||
|
click=(action 'click')
|
||||||
|
)}}
|
||||||
</label>
|
</label>
|
|
@ -3,16 +3,11 @@ import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
// TODO(octane): Remove when we can move to glimmer components
|
|
||||||
// so we aren't using ember-test-selectors
|
|
||||||
// supportsDataTestProperties: true,
|
|
||||||
// the above doesn't seem to do anything so still need to find a way
|
|
||||||
// to pass through data-test-properties
|
|
||||||
tagName: '',
|
tagName: '',
|
||||||
// TODO: reserved for the moment but we don't need it yet
|
|
||||||
onblur: null,
|
|
||||||
checked: false,
|
checked: false,
|
||||||
onchange: function() {},
|
onchange: function() {},
|
||||||
|
// TODO: reserved for the moment but we don't need it yet
|
||||||
|
onblur: function() {},
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.guid = this.dom.guid(this);
|
this.guid = this.dom.guid(this);
|
||||||
|
@ -22,9 +17,32 @@ export default Component.extend({
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this._listeners.remove();
|
this._listeners.remove();
|
||||||
},
|
},
|
||||||
|
didReceiveAttrs: function() {
|
||||||
|
this._super(...arguments);
|
||||||
|
if (this.checked) {
|
||||||
|
this.addClickOutsideListener();
|
||||||
|
} else {
|
||||||
|
this._listeners.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addClickOutsideListener: function() {
|
||||||
|
// default onblur event
|
||||||
|
this._listeners.remove();
|
||||||
|
this._listeners.add(this.dom.document(), 'click', e => {
|
||||||
|
if (this.dom.isOutside(this.label, e.target)) {
|
||||||
|
if (this.dom.isOutside(this.label.nextElementSibling, e.target)) {
|
||||||
|
if (this.input.checked) {
|
||||||
|
this.input.checked = false;
|
||||||
|
// TODO: This should be an event
|
||||||
|
this.onchange({ target: this.input });
|
||||||
|
}
|
||||||
|
this._listeners.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
click: function(e) {
|
click: function(e) {
|
||||||
e.preventDefault();
|
|
||||||
this.input.checked = !this.input.checked;
|
this.input.checked = !this.input.checked;
|
||||||
// manually dispatched mouse events have a detail = 0
|
// manually dispatched mouse events have a detail = 0
|
||||||
// real mouse events have the number of click counts
|
// real mouse events have the number of click counts
|
||||||
|
@ -35,20 +53,7 @@ export default Component.extend({
|
||||||
},
|
},
|
||||||
change: function(e) {
|
change: function(e) {
|
||||||
if (this.input.checked) {
|
if (this.input.checked) {
|
||||||
// default onblur event
|
this.addClickOutsideListener();
|
||||||
this._listeners.remove();
|
|
||||||
this._listeners.add(this.dom.document(), 'click', e => {
|
|
||||||
if (this.dom.isOutside(this.label, e.target)) {
|
|
||||||
if (this.dom.isOutside(this.label.nextElementSibling, e.target)) {
|
|
||||||
if (this.input.checked) {
|
|
||||||
this.input.checked = false;
|
|
||||||
// TODO: This should be an event
|
|
||||||
this.onchange({ target: this.input });
|
|
||||||
}
|
|
||||||
this._listeners.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// TODO: This should be an event
|
// TODO: This should be an event
|
||||||
this.onchange({ target: this.input });
|
this.onchange({ target: this.input });
|
||||||
|
|
Loading…
Reference in New Issue