fix(menu-button): hide menu button when auto hide or split pane (#18702)

- updates menu-button to use the host element
- moves menu-toggle logic to a utils file for menu button to share
- removes the dependency on menu-toggle
- adds an e2e test for an auto-hidden menu button

fixes #18666
This commit is contained in:
Brandy Carney
2019-07-03 11:51:30 -04:00
committed by GitHub
parent 876ab41ba8
commit 24840d4d99
8 changed files with 96 additions and 60 deletions

View File

@ -77,6 +77,13 @@ ion-icon {
pointer-events: none; pointer-events: none;
} }
// Menu Button: Hidden
// --------------------------------------------------
:host(.menu-button-hidden) {
display: none;
}
// Menu Button: Disabled // Menu Button: Disabled
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,10 +1,11 @@
import { Component, ComponentInterface, Prop, h } from '@stencil/core'; import { Component, ComponentInterface, Host, Listen, Prop, State, h } from '@stencil/core';
import { config } from '../../global/config'; import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global'; import { getIonMode } from '../../global/ionic-global';
import { Color } from '../../interface'; import { Color } from '../../interface';
import { ButtonInterface } from '../../utils/element-interface'; import { ButtonInterface } from '../../utils/element-interface';
import { createColorClasses } from '../../utils/theme'; import { createColorClasses } from '../../utils/theme';
import { toggleMenu, updateVisibility } from '../menu-toggle/menu-toggle-util';
@Component({ @Component({
tag: 'ion-menu-button', tag: 'ion-menu-button',
@ -16,6 +17,8 @@ import { createColorClasses } from '../../utils/theme';
}) })
export class MenuButton implements ComponentInterface, ButtonInterface { export class MenuButton implements ComponentInterface, ButtonInterface {
@State() visible = false;
/** /**
* The color to use from your application's color palette. * The color to use from your application's color palette.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. * Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
@ -43,35 +46,51 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
*/ */
@Prop() type: 'submit' | 'reset' | 'button' = 'button'; @Prop() type: 'submit' | 'reset' | 'button' = 'button';
hostData() { async componentDidLoad() {
const mode = getIonMode(this); await this.setVisibility();
const { color, disabled } = this; }
return { @Listen('ionMenuChange', { target: 'body' })
'aria-disabled': disabled ? 'true' : null, @Listen('ionSplitPaneVisible', { target: 'body' })
class: { async visibilityChanged() {
...createColorClasses(color), await this.setVisibility();
}
[mode]: true, private setVisibility = async () => {
this.visible = await updateVisibility(this.menu);
}
'button': true, // ion-buttons target .button private onClick = async () => {
'menu-button-disabled': disabled, await toggleMenu(this.menu);
'ion-activatable': true,
'ion-focusable': true
}
};
} }
render() { render() {
const { color, disabled } = this;
const mode = getIonMode(this); const mode = getIonMode(this);
const menuIcon = config.get('menuIcon', 'menu'); const menuIcon = config.get('menuIcon', 'menu');
const hidden = this.autoHide && !this.visible;
const attrs = { const attrs = {
type: this.type type: this.type
}; };
return ( return (
<ion-menu-toggle menu={this.menu} autoHide={this.autoHide}> <Host
onClick={this.onClick}
aria-disabled={disabled ? 'true' : null}
aria-hidden={hidden ? 'true' : null}
class={{
[mode]: true,
...createColorClasses(color),
'button': true, // ion-buttons target .button
'menu-button-hidden': hidden,
'menu-button-disabled': disabled,
'ion-activatable': true,
'ion-focusable': true
}}
>
<button <button
{...attrs} {...attrs}
disabled={this.disabled} disabled={this.disabled}
@ -82,7 +101,7 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
</slot> </slot>
{mode === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>} {mode === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>}
</button> </button>
</ion-menu-toggle> </Host>
); );
} }
} }

View File

@ -38,14 +38,12 @@ Menu Button is component that automatically creates the icon and functionality t
### Depends on ### Depends on
- [ion-menu-toggle](../menu-toggle)
- ion-icon - ion-icon
- [ion-ripple-effect](../ripple-effect) - [ion-ripple-effect](../ripple-effect)
### Graph ### Graph
```mermaid ```mermaid
graph TD; graph TD;
ion-menu-button --> ion-menu-toggle
ion-menu-button --> ion-icon ion-menu-button --> ion-icon
ion-menu-button --> ion-ripple-effect ion-menu-button --> ion-ripple-effect
style ion-menu-button fill:#f9f,stroke:#333,stroke-width:4px style ion-menu-button fill:#f9f,stroke:#333,stroke-width:4px

View File

@ -84,6 +84,13 @@
</ion-buttons> </ion-buttons>
<ion-title>Success</ion-title> <ion-title>Success</ion-title>
</ion-toolbar> </ion-toolbar>
<ion-toolbar color="dark">
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Hidden</ion-title>
</ion-toolbar>
</ion-content> </ion-content>
</ion-app> </ion-app>
@ -92,7 +99,7 @@
padding-left: 16px; padding-left: 16px;
} }
ion-menu-button { ion-menu-button[auto-hide="false"] {
display: inline-block; display: inline-block;
} }

View File

@ -0,0 +1,32 @@
// Get the menu controller element
export const getMenuController = (doc: Document): Promise<HTMLIonMenuControllerElement | undefined> => {
const menuControllerElement = doc.querySelector('ion-menu-controller');
if (!menuControllerElement) {
return Promise.resolve(undefined);
}
return menuControllerElement.componentOnReady();
};
// Given a menu, toggle it
export const toggleMenu = async (menu: string | undefined) => {
const menuCtrl = await getMenuController(document);
if (menuCtrl) {
const menuEl = await menuCtrl.get(menu);
if (menuEl) {
menuCtrl.toggle(menu);
}
}
};
// Given a menu, return whether or not the menu toggle should be visible
export const updateVisibility = async (menu: string | undefined) => {
const menuCtrl = await getMenuController(document);
if (menuCtrl) {
const menuEl = await menuCtrl.get(menu);
if (menuEl && await menuEl.isActive()) {
return true;
}
}
return false;
};

View File

@ -2,6 +2,8 @@ import { Component, ComponentInterface, Host, Listen, Prop, State, h } from '@st
import { getIonMode } from '../../global/ionic-global'; import { getIonMode } from '../../global/ionic-global';
import { toggleMenu, updateVisibility } from './menu-toggle-util';
@Component({ @Component({
tag: 'ion-menu-toggle', tag: 'ion-menu-toggle',
styleUrl: 'menu-toggle.scss', styleUrl: 'menu-toggle.scss',
@ -29,33 +31,24 @@ export class MenuToggle implements ComponentInterface {
*/ */
@Prop() autoHide = true; @Prop() autoHide = true;
componentDidLoad() { async componentDidLoad() {
return this.updateVisibility(); await this.setVisibility();
} }
@Listen('ionMenuChange', { target: 'body' }) @Listen('ionMenuChange', { target: 'body' })
@Listen('ionSplitPaneVisible', { target: 'body' }) @Listen('ionSplitPaneVisible', { target: 'body' })
async updateVisibility() { async visibilityChanged() {
const menuCtrl = await getMenuController(document); await this.setVisibility();
if (menuCtrl) { }
const menu = await menuCtrl.get(this.menu);
if (menu && await menu.isActive()) { private setVisibility = async () => {
this.visible = true; this.visible = await updateVisibility(this.menu);
return;
}
}
this.visible = false;
} }
private onClick = async () => { private onClick = async () => {
const menuCtrl = await getMenuController(document); await toggleMenu(this.menu);
if (menuCtrl) {
const menu = await menuCtrl.get(this.menu);
if (menu) {
menuCtrl.toggle(this.menu);
}
}
} }
render() { render() {
const mode = getIonMode(this); const mode = getIonMode(this);
const hidden = this.autoHide && !this.visible; const hidden = this.autoHide && !this.visible;
@ -74,11 +67,3 @@ export class MenuToggle implements ComponentInterface {
); );
} }
} }
function getMenuController(doc: Document): Promise<HTMLIonMenuControllerElement | undefined> {
const menuControllerElement = doc.querySelector('ion-menu-controller');
if (!menuControllerElement) {
return Promise.resolve(undefined);
}
return menuControllerElement.componentOnReady();
}

View File

@ -17,19 +17,6 @@ In case it's desired to keep `ion-menu-toggle` always visible, the `autoHide` pr
| `menu` | `menu` | Optional property that maps to a Menu's `menuId` prop. Can also be `start` or `end` for the menu side. This is used to find the correct menu to toggle. If this property is not used, `ion-menu-toggle` will toggle the first menu that is active. | `string \| undefined` | `undefined` | | `menu` | `menu` | Optional property that maps to a Menu's `menuId` prop. Can also be `start` or `end` for the menu side. This is used to find the correct menu to toggle. If this property is not used, `ion-menu-toggle` will toggle the first menu that is active. | `string \| undefined` | `undefined` |
## Dependencies
### Used by
- [ion-menu-button](../menu-button)
### Graph
```mermaid
graph TD;
ion-menu-button --> ion-menu-toggle
style ion-menu-toggle fill:#f9f,stroke:#333,stroke-width:4px
```
---------------------------------------------- ----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)* *Built with [StencilJS](https://stenciljs.com/)*

View File

@ -55,6 +55,7 @@
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-menu-button></ion-menu-button> <ion-menu-button></ion-menu-button>
<ion-menu-button auto-hide="false"></ion-menu-button>
</ion-buttons> </ion-buttons>
<ion-title>Navigation</ion-title> <ion-title>Navigation</ion-title>