docs(): update examples and usage

This commit is contained in:
mhartington
2018-05-31 10:56:02 -04:00
parent 89a7d169e9
commit 5ad35ccc00
25 changed files with 370 additions and 225 deletions

View File

@ -2980,7 +2980,7 @@ declare global {
*/ */
'button': boolean; 'button': boolean;
/** /**
* The color to use from your Sass `$colors` map. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information, see [Theming your App](/docs/theming/theming-your-app). * The color to use for the background of the item.
*/ */
'color': Color; 'color': Color;
/** /**
@ -3000,7 +3000,7 @@ declare global {
*/ */
'lines': 'full' | 'inset' | 'none'; 'lines': 'full' | 'inset' | 'none';
/** /**
* The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`. For more information, see [Platform Styles](/docs/theming/platform-specific-styles). * The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`.
*/ */
'mode': Mode; 'mode': Mode;
/** /**
@ -3034,7 +3034,7 @@ declare global {
*/ */
'button'?: boolean; 'button'?: boolean;
/** /**
* The color to use from your Sass `$colors` map. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information, see [Theming your App](/docs/theming/theming-your-app). * The color to use for the background of the item.
*/ */
'color'?: Color; 'color'?: Color;
/** /**
@ -3054,7 +3054,7 @@ declare global {
*/ */
'lines'?: 'full' | 'inset' | 'none'; 'lines'?: 'full' | 'inset' | 'none';
/** /**
* The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`. For more information, see [Platform Styles](/docs/theming/platform-specific-styles). * The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`.
*/ */
'mode'?: Mode; 'mode'?: Mode;
/** /**

View File

@ -1,7 +1,10 @@
import { Component, Element, Listen, Prop } from '@stencil/core'; import { Component, Element, Listen, Prop } from '@stencil/core';
import { Color, CssClassMap, Mode, RouterDirection } from '../../interface'; import { Color, CssClassMap, Mode, RouterDirection } from '../../interface';
import { createThemedClasses, getElementClassMap, openURL } from '../../utils/theme'; import {
createThemedClasses,
getElementClassMap,
openURL
} from '../../utils/theme';
@Component({ @Component({
tag: 'ion-item', tag: 'ion-item',
@ -11,24 +14,21 @@ import { createThemedClasses, getElementClassMap, openURL } from '../../utils/th
} }
}) })
export class Item { export class Item {
private itemStyles: { [key: string]: CssClassMap } = {}; private itemStyles: { [key: string]: CssClassMap } = {};
@Element() el!: HTMLStencilElement; @Element() el!: HTMLStencilElement;
@Prop({ context: 'window' }) win!: Window; @Prop({ context: 'window' })
win!: Window;
/** /**
* The color to use from your Sass `$colors` map. * The color to use for the background of the item.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
* For more information, see [Theming your App](/docs/theming/theming-your-app).
*/ */
@Prop() color?: Color; @Prop() color?: Color;
/** /**
* The mode determines which platform styles to use. * The mode determines which platform styles to use.
* Possible values are: `"ios"` or `"md"`. * Possible values are: `"ios"` or `"md"`.
* For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
*/ */
@Prop() mode!: Mode; @Prop() mode!: Mode;
@ -65,7 +65,6 @@ export class Item {
*/ */
@Prop() routerDirection?: RouterDirection; @Prop() routerDirection?: RouterDirection;
@Listen('ionStyle') @Listen('ionStyle')
itemStyle(ev: UIEvent) { itemStyle(ev: UIEvent) {
ev.stopPropagation(); ev.stopPropagation();
@ -110,15 +109,13 @@ export class Item {
const clickable = !!(this.href || this.el.onclick || this.button); const clickable = !!(this.href || this.el.onclick || this.button);
const TagType = clickable const TagType = clickable ? (this.href ? 'a' : 'button') : 'div';
? this.href ? 'a' : 'button'
: 'div';
const attrs = (TagType === 'button') const attrs =
? { type: 'button' } TagType === 'button' ? { type: 'button' } : { href: this.href };
: { href: this.href };
const showDetail = this.detail != null ? this.detail : (this.mode === 'ios' && clickable); const showDetail =
this.detail != null ? this.detail : this.mode === 'ios' && clickable;
const themedClasses = { const themedClasses = {
...childStyles, ...childStyles,
@ -134,17 +131,18 @@ export class Item {
<TagType <TagType
{...attrs} {...attrs}
class={themedClasses} class={themedClasses}
onClick={(ev) => openURL(this.win, this.href, ev, this.routerDirection)}> onClick={ev => openURL(this.win, this.href, ev, this.routerDirection)}
<slot name="start"></slot> >
<slot name="start" />
<div class="item-inner"> <div class="item-inner">
<div class="input-wrapper"> <div class="input-wrapper">
<slot></slot> <slot />
</div> </div>
<slot name="end"></slot> <slot name="end" />
</div> </div>
{ clickable && this.mode === 'md' && <ion-ripple-effect tapClick={true}/> } {clickable &&
this.mode === 'md' && <ion-ripple-effect tapClick={true} />}
</TagType> </TagType>
); );
} }
} }

View File

@ -7,20 +7,20 @@ Items are elements that can contain text, icons, avatars, images, inputs, and an
By default, clickable items will display a right arrow icon on `ios` mode. To hide the right arrow icon on clickable elements, set the `detail` property to `false`. To show the right arrow icon on an item that doesn't display it naturally, add the `detail` attribute to the item. By default, clickable items will display a right arrow icon on `ios` mode. To hide the right arrow icon on clickable elements, set the `detail` property to `false`. To show the right arrow icon on an item that doesn't display it naturally, add the `detail` attribute to the item.
This feature is not enabled by default on clickable items for the `md` mode, but it can be enabled by setting the following Sass variable: <!-- This feature is not enabled by default on clickable items for the `md` mode, but it can be enabled by setting the following Sass variable: -->
<!-- -->
<!-- ```scss -->
<!-- $item-md-detail-push-show: true; -->
<!-- ``` -->
```scss <!-- It can also be turned off by default on clickable items for ios by setting the following Sass variable: -->
$item-md-detail-push-show: true; <!-- -->
``` <!-- ```scss -->
<!-- $item-ios-detail-push-show: false; -->
It can also be turned off by default on clickable items for ios by setting the following Sass variable: <!-- ``` -->
```scss
$item-ios-detail-push-show: false;
```
See the [theming documentation](http://ionicframework.com/docs/theming/overriding-ionic-variables/) for more information on overriding Sass variables.
<!-- See the [theming documentation](http://ionicframework.com/docs/theming/overriding-ionic-variables/) for more information on overriding Sass variables. -->
<!-- -->
## Item Placement ## Item Placement
@ -28,11 +28,11 @@ Item uses named [slots](https://developer.mozilla.org/en-US/docs/Web/HTML/Elemen
The below chart details the item slots and where it will place the element inside of the item: The below chart details the item slots and where it will place the element inside of the item:
| Slot | Description | | Slot | Description |
|---------|-----------------------------------------------------------------------------------| |---------|-----------------------------------------------------------------------------|
| `start` | Placed to the left of all other content in LTR, and to the `right` in RTL. | | `start` | Placed to the left of all other content in LTR, and to the `right` in RTL. |
| `end` | Placed to the right of all other content in LTR, and to the `right` in RTL. | | `end` | Placed to the right of all other content in LTR, and to the `right` in RTL. |
| none | Placed inside of the input wrapper. | | none | Placed inside of the input wrapper. |
### Text Alignment ### Text Alignment
@ -56,9 +56,7 @@ If true, a button tag will be rendered and the item will be tappable. Defaults t
string string
The color to use from your Sass `$colors` map. The color to use for the background of the item.
Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
For more information, see [Theming your App](/docs/theming/theming-your-app).
#### detail #### detail
@ -97,7 +95,6 @@ string
The mode determines which platform styles to use. The mode determines which platform styles to use.
Possible values are: `"ios"` or `"md"`. Possible values are: `"ios"` or `"md"`.
For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
#### routerDirection #### routerDirection
@ -121,9 +118,7 @@ If true, a button tag will be rendered and the item will be tappable. Defaults t
string string
The color to use from your Sass `$colors` map. The color to use for the background of the item.
Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
For more information, see [Theming your App](/docs/theming/theming-your-app).
#### detail #### detail
@ -162,7 +157,6 @@ string
The mode determines which platform styles to use. The mode determines which platform styles to use.
Possible values are: `"ios"` or `"md"`. Possible values are: `"ios"` or `"md"`.
For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
#### router-direction #### router-direction

View File

@ -1,7 +1,14 @@
import { Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core'; import {
Component,
Element,
Event,
EventEmitter,
Method,
Prop,
Watch
} from '@stencil/core';
import { Color, Mode, StyleEvent } from '../../interface'; import { Color, Mode, StyleEvent } from '../../interface';
@Component({ @Component({
tag: 'ion-label', tag: 'ion-label',
styleUrls: { styleUrls: {
@ -13,20 +20,16 @@ import { Color, Mode, StyleEvent } from '../../interface';
} }
}) })
export class Label { export class Label {
@Element() el!: HTMLElement; @Element() el!: HTMLElement;
/** /**
* The color to use from your Sass `$colors` map. * The color to use for the label's text
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
* For more information, see [Theming your App](/docs/theming/theming-your-app).
*/ */
@Prop() color?: Color; @Prop() color?: Color;
/** /**
* The mode determines which platform styles to use. * The mode determines which platform styles to use.
* Possible values are: `"ios"` or `"md"`. * Possible values are: `"ios"` or `"md"`.
* For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
*/ */
@Prop() mode!: Mode; @Prop() mode!: Mode;
@ -54,7 +57,7 @@ export class Label {
positionChanged() { positionChanged() {
const position = this.position; const position = this.position;
return this.ionStyle.emit({ return this.ionStyle.emit({
[`label-${position}`]: !!position, [`label-${position}`]: !!position
}); });
} }

View File

@ -1,5 +1,5 @@
# ion-label # ion-label
Label is a wrapper element that can be used in combination with `ion-item`.
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -0,0 +1,24 @@
```html
<ion-list>
<ion-item>
<ion-label>Default</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item text-wrap>
<ion-label>Wrap label this label just goes on and on and on</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
<ion-label position="fixed">Fixed</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">Floating</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Stacked</ion-label>
<ion-input></ion-input>
</ion-item>
</ion-list>
```

View File

@ -1,7 +1,6 @@
import { Component, Prop } from '@stencil/core'; import { Component, Prop } from '@stencil/core';
import { Color, Mode } from '../../interface'; import { Color, Mode } from '../../interface';
@Component({ @Component({
tag: 'ion-list-header', tag: 'ion-list-header',
styleUrls: { styleUrls: {
@ -13,19 +12,14 @@ import { Color, Mode } from '../../interface';
} }
}) })
export class ListHeader { export class ListHeader {
/** /**
* The color to use from your Sass `$colors` map. * The color to use for the background.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
* For more information, see [Theming your App](/docs/theming/theming-your-app).
*/ */
@Prop() color?: Color; @Prop() color?: Color;
/** /**
* The mode determines which platform styles to use. * The mode determines which platform styles to use.
* Possible values are: `"ios"` or `"md"`. * Possible values are: `"ios"` or `"md"`.
* For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
*/ */
@Prop() mode!: Mode; @Prop() mode!: Mode;
} }

View File

@ -1,5 +1,5 @@
# ion-list-header # ion-list-header
List header a header component for a list.
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -0,0 +1,12 @@
```html
<ion-list>
<ion-list-header>
<ion-label>Items</ion-label>
</ion-list-header>
<ion-item>Item 1</ion-item>
<ion-item>Item 2</ion-item>
<ion-item>Item 3</ion-item>
<ion-item>Item 4</ion-item>
<ion-item>Item 5</ion-item>
</ion-list>
```

View File

@ -58,5 +58,4 @@ export class List {
} }
}; };
} }
} }

View File

@ -28,7 +28,7 @@ export class LoadingController implements OverlayController {
removeLastOverlay(this.loadings); removeLastOverlay(this.loadings);
} }
/* /**
* Create a loading overlay with loading options. * Create a loading overlay with loading options.
*/ */
@Method() @Method()
@ -36,7 +36,7 @@ export class LoadingController implements OverlayController {
return createOverlay(this.doc.createElement('ion-loading'), opts); return createOverlay(this.doc.createElement('ion-loading'), opts);
} }
/* /**
* Dismiss the open loading overlay. * Dismiss the open loading overlay.
*/ */
@Method() @Method()
@ -44,7 +44,7 @@ export class LoadingController implements OverlayController {
return dismissOverlay(data, role, this.loadings, loadingId); return dismissOverlay(data, role, this.loadings, loadingId);
} }
/* /**
* Get the most recently opened loading overlay. * Get the most recently opened loading overlay.
*/ */
@Method() @Method()

View File

@ -3,19 +3,6 @@
Loading controllers programmatically control the loading component. Loadings can be created and dismissed from the loading controller. View the [Loading](../../loading/Loading) documentation for a full list of options to pass upon creation. Loading controllers programmatically control the loading component. Loadings can be created and dismissed from the loading controller. View the [Loading](../../loading/Loading) documentation for a full list of options to pass upon creation.
```javascript
async function presentLoading() {
const loadingController = document.querySelector('ion-loading-controller');
await loadingController.componentOnReady();
const loadingElement = await loadingController.create({
content: 'Please wait...',
spinner: 'crescent',
duration: 2000
});
return await loadingElement.present();
}
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -0,0 +1,13 @@
```javascript
async function presentLoading() {
const loadingController = document.querySelector('ion-loading-controller');
await loadingController.componentOnReady();
const loadingElement = await loadingController.create({
content: 'Please wait...',
spinner: 'crescent',
duration: 2000
});
return await loadingElement.present();
}
```

View File

@ -1,6 +1,27 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core'; import {
import { Animation, AnimationBuilder, Color, Config, Mode } from '../../interface'; Component,
import { BACKDROP, OverlayEventDetail, OverlayInterface, dismiss, eventMethod, present } from '../../utils/overlays'; Element,
Event,
EventEmitter,
Listen,
Method,
Prop
} from '@stencil/core';
import {
Animation,
AnimationBuilder,
Color,
Config,
Mode
} from '../../interface';
import {
BACKDROP,
OverlayEventDetail,
OverlayInterface,
dismiss,
eventMethod,
present
} from '../../utils/overlays';
import { createThemedClasses, getClassMap } from '../../utils/theme'; import { createThemedClasses, getClassMap } from '../../utils/theme';
import { iosEnterAnimation } from './animations/ios.enter'; import { iosEnterAnimation } from './animations/ios.enter';
@ -20,7 +41,6 @@ import { mdLeaveAnimation } from './animations/md.leave';
} }
}) })
export class Loading implements OverlayInterface { export class Loading implements OverlayInterface {
private durationTimeout: any; private durationTimeout: any;
presented = false; presented = false;
@ -30,8 +50,10 @@ export class Loading implements OverlayInterface {
@Element() el!: HTMLElement; @Element() el!: HTMLElement;
@Prop({ connect: 'ion-animation-controller' }) animationCtrl!: HTMLIonAnimationControllerElement; @Prop({ connect: 'ion-animation-controller' })
@Prop({ context: 'config' }) config!: Config; animationCtrl!: HTMLIonAnimationControllerElement;
@Prop({ context: 'config' })
config!: Config;
@Prop() overlayId!: number; @Prop() overlayId!: number;
@Prop() keyboardClose = true; @Prop() keyboardClose = true;
@ -105,28 +127,36 @@ export class Loading implements OverlayInterface {
/** /**
* Emitted after the loading has presented. * Emitted after the loading has presented.
*/ */
@Event({eventName: 'ionLoadingDidPresent'}) didPresent!: EventEmitter<void>; @Event({ eventName: 'ionLoadingDidPresent' })
didPresent!: EventEmitter<void>;
/** /**
* Emitted before the loading has presented. * Emitted before the loading has presented.
*/ */
@Event({eventName: 'ionLoadingWillPresent'}) willPresent!: EventEmitter<void>; @Event({ eventName: 'ionLoadingWillPresent' })
willPresent!: EventEmitter<void>;
/** /**
* Emitted before the loading has dismissed. * Emitted before the loading has dismissed.
*/ */
@Event({eventName: 'ionLoadingWillDismiss'}) willDismiss!: EventEmitter<OverlayEventDetail>; @Event({ eventName: 'ionLoadingWillDismiss' })
willDismiss!: EventEmitter<OverlayEventDetail>;
/** /**
* Emitted after the loading has dismissed. * Emitted after the loading has dismissed.
*/ */
@Event({eventName: 'ionLoadingDidDismiss'}) didDismiss!: EventEmitter<OverlayEventDetail>; @Event({ eventName: 'ionLoadingDidDismiss' })
didDismiss!: EventEmitter<OverlayEventDetail>;
componentWillLoad() { componentWillLoad() {
if (!this.spinner) { if (!this.spinner) {
this.spinner = this.config.get('loadingSpinner', this.mode === 'ios' ? 'lines' : 'crescent'); this.spinner = this.config.get(
'loadingSpinner',
this.mode === 'ios' ? 'lines' : 'crescent'
);
} }
} }
componentDidLoad() { componentDidLoad() {
this.ionLoadingDidLoad.emit(); this.ionLoadingDidLoad.emit();
} }
@ -145,10 +175,19 @@ export class Loading implements OverlayInterface {
*/ */
@Method() @Method()
async present(): Promise<void> { async present(): Promise<void> {
await present(this, 'loadingEnter', iosEnterAnimation, mdEnterAnimation, undefined); await present(
this,
'loadingEnter',
iosEnterAnimation,
mdEnterAnimation,
undefined
);
if (this.duration) { if (this.duration) {
this.durationTimeout = setTimeout(() => this.dismiss(), this.duration + 10); this.durationTimeout = setTimeout(
() => this.dismiss(),
this.duration + 10
);
} }
} }
@ -160,19 +199,25 @@ export class Loading implements OverlayInterface {
if (this.durationTimeout) { if (this.durationTimeout) {
clearTimeout(this.durationTimeout); clearTimeout(this.durationTimeout);
} }
return dismiss(this, data, role, 'loadingLeave', iosLeaveAnimation, mdLeaveAnimation); return dismiss(
this,
data,
role,
'loadingLeave',
iosLeaveAnimation,
mdLeaveAnimation
);
} }
/** /**
* Returns a promise that resolves when the loading did dismiss. It also accepts a callback * Returns a promise that resolves when the loading did dismiss. It also accepts a callback
* that is called in the same circustances. * that is called in the same circustances.
* *
* ```
* const {data, role} = await loading.onDidDismiss();
* ```
*/ */
@Method() @Method()
onDidDismiss(callback?: (detail: OverlayEventDetail) => void): Promise<OverlayEventDetail> { onDidDismiss(
callback?: (detail: OverlayEventDetail) => void
): Promise<OverlayEventDetail> {
return eventMethod(this.el, 'ionLoadingDidDismiss', callback); return eventMethod(this.el, 'ionLoadingDidDismiss', callback);
} }
@ -185,16 +230,20 @@ export class Loading implements OverlayInterface {
* ``` * ```
*/ */
@Method() @Method()
onWillDismiss(callback?: (detail: OverlayEventDetail) => void): Promise<OverlayEventDetail> { onWillDismiss(
callback?: (detail: OverlayEventDetail) => void
): Promise<OverlayEventDetail> {
return eventMethod(this.el, 'ionLoadingWillDismiss', callback); return eventMethod(this.el, 'ionLoadingWillDismiss', callback);
} }
hostData() { hostData() {
const themedClasses = this.translucent ? createThemedClasses(this.mode, this.color, 'loading-translucent') : {}; const themedClasses = this.translucent
? createThemedClasses(this.mode, this.color, 'loading-translucent')
: {};
return { return {
style: { style: {
zIndex: 20000 + this.overlayId, zIndex: 20000 + this.overlayId
}, },
class: { class: {
...themedClasses, ...themedClasses,
@ -207,13 +256,13 @@ export class Loading implements OverlayInterface {
return [ return [
<ion-backdrop visible={this.showBackdrop} tappable={false} />, <ion-backdrop visible={this.showBackdrop} tappable={false} />,
<div class="loading-wrapper" role="dialog"> <div class="loading-wrapper" role="dialog">
{this.spinner !== 'hide' && (
<div class="loading-spinner">
<ion-spinner name={this.spinner} />
</div>
)}
{ this.spinner !== 'hide' && {this.content && <div class="loading-content">{this.content}</div>}
<div class="loading-spinner">
<ion-spinner name={this.spinner}></ion-spinner>
</div>}
{ this.content && <div class="loading-content">{this.content}</div>}
</div> </div>
]; ];
} }

View File

@ -18,7 +18,7 @@ export class LoadingExample {
return await loading.present(); return await loading.present();
} }
presentLoadingWithOptions() { async presentLoadingWithOptions() {
const loading = await this.loadingController.create({ const loading = await this.loadingController.create({
spinner: 'hide', spinner: 'hide',
duration: 5000, duration: 5000,

View File

@ -12,8 +12,8 @@ import { Config } from '../../interface';
} }
}) })
export class MenuButton { export class MenuButton {
@Prop({ context: 'config' })
@Prop({ context: 'config' }) config!: Config; config!: Config;
/** /**
* Optional property that maps to a Menu's `menuId` prop. Can also be `left` or `right` for the menu side. This is used to find the correct menu to toggle * Optional property that maps to a Menu's `menuId` prop. Can also be `left` or `right` for the menu side. This is used to find the correct menu to toggle

View File

@ -1,5 +1,6 @@
# ion-menu-button # ion-menu-button
Menu Button is component that automatically creates the icon and functionality to open a menu on a page.
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -9,11 +9,11 @@ import { menuRevealAnimation } from './animations/reveal';
tag: 'ion-menu-controller' tag: 'ion-menu-controller'
}) })
export class MenuController { export class MenuController {
private menus: Menu[] = []; private menus: Menu[] = [];
private menuAnimations = new Map<string, AnimationBuilder>(); private menuAnimations = new Map<string, AnimationBuilder>();
@Prop({ connect: 'ion-animation-controller' }) animationCtrl!: HTMLIonAnimationControllerElement; @Prop({ connect: 'ion-animation-controller' })
animationCtrl!: HTMLIonAnimationControllerElement;
constructor() { constructor() {
this.registerAnimation('reveal', menuRevealAnimation); this.registerAnimation('reveal', menuRevealAnimation);
@ -22,9 +22,7 @@ export class MenuController {
} }
/** /**
* Programatically open the Menu. * Open the Menu.
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {Promise} returns a promise when the menu is fully opened
*/ */
@Method() @Method()
open(menuId?: string): Promise<boolean> { open(menuId?: string): Promise<boolean> {
@ -36,17 +34,13 @@ export class MenuController {
} }
/** /**
* Programatically close the Menu. If no `menuId` is given as the first * Close the Menu. If no `menuId` is given as the first
* argument then it'll close any menu which is open. If a `menuId` * argument then it'll close any menu which is open. If a `menuId`
* is given then it'll close that exact menu. * is given then it'll close that exact menu.
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {Promise} returns a promise when the menu is fully closed
*/ */
@Method() @Method()
close(menuId?: string): Promise<boolean> { close(menuId?: string): Promise<boolean> {
const menu = (menuId) const menu = menuId ? this.get(menuId) : this.getOpen();
? this.get(menuId)
: this.getOpen();
if (menu) { if (menu) {
return menu.close(); return menu.close();
@ -57,8 +51,6 @@ export class MenuController {
/** /**
* Toggle the menu. If it's closed, it will open, and if opened, it * Toggle the menu. If it's closed, it will open, and if opened, it
* will close. * will close.
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {Promise} returns a promise when the menu has been toggled
*/ */
@Method() @Method()
toggle(menuId?: string): Promise<boolean> { toggle(menuId?: string): Promise<boolean> {
@ -74,11 +66,9 @@ export class MenuController {
* left menus, but only one of them should be able to be opened at the same * left menus, but only one of them should be able to be opened at the same
* time. If there are multiple menus on the same side, then enabling one menu * time. If there are multiple menus on the same side, then enabling one menu
* will also automatically disable all the others that are on the same side. * will also automatically disable all the others that are on the same side.
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {HTMLIonMenuElement} Returns the instance of the menu, which is useful for chaining.
*/ */
@Method() @Method()
enable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement|null { enable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement | null {
const menu = this.get(menuId); const menu = this.get(menuId);
if (menu) { if (menu) {
menu.disabled = !shouldEnable; menu.disabled = !shouldEnable;
@ -88,12 +78,12 @@ export class MenuController {
/** /**
* Used to enable or disable the ability to swipe open the menu. * Used to enable or disable the ability to swipe open the menu.
* @param {boolean} shouldEnable True if it should be swipe-able, false if not.
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {HTMLIonMenuElement} Returns the instance of the menu, which is useful for chaining.
*/ */
@Method() @Method()
swipeEnable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement|null { swipeEnable(
shouldEnable: boolean,
menuId?: string
): HTMLIonMenuElement | null {
const menu = this.get(menuId); const menu = this.get(menuId);
if (menu) { if (menu) {
menu.swipeEnabled = shouldEnable; menu.swipeEnabled = shouldEnable;
@ -102,22 +92,19 @@ export class MenuController {
} }
/** /**
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {boolean} Returns true if the specified menu is currently open, otherwise false.
* If the menuId is not specified, it returns true if ANY menu is currenly open. * If the menuId is not specified, it returns true if ANY menu is currenly open.
*/ */
@Method() @Method()
isOpen(menuId?: string): boolean { isOpen(menuId?: string): boolean {
if (menuId) { if (menuId) {
const menu = this.get(menuId); const menu = this.get(menuId);
return menu && menu.isOpen() || false; return (menu && menu.isOpen()) || false;
} }
return !!this.getOpen(); return !!this.getOpen();
} }
/** /**
* @param {string} [menuId] Optionally get the menu by its id, or side. * Returns true or false if the menuId is enabled or not
* @return {boolean} Returns true if the menu is currently enabled, otherwise false.
*/ */
@Method() @Method()
isEnabled(menuId?: string): boolean { isEnabled(menuId?: string): boolean {
@ -134,8 +121,6 @@ export class MenuController {
* it'll return the enabled menu on that side. Otherwise, if a `menuId` is * it'll return the enabled menu on that side. Otherwise, if a `menuId` is
* provided, then it'll try to find the menu using the menu's `id` * provided, then it'll try to find the menu using the menu's `id`
* property. If a menu is not found then it'll return `null`. * property. If a menu is not found then it'll return `null`.
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {HTMLIonMenuElement} Returns the instance of the menu if found, otherwise `null`.
*/ */
@Method() @Method()
get(menuId?: string): HTMLIonMenuElement | null { get(menuId?: string): HTMLIonMenuElement | null {
@ -147,7 +132,7 @@ export class MenuController {
console.error('menu.side=right is deprecated, use "end" instead'); console.error('menu.side=right is deprecated, use "end" instead');
return null; return null;
} }
if (menuId === 'start' || menuId === 'end' ) { if (menuId === 'start' || menuId === 'end') {
// there could be more than one menu on the same side // there could be more than one menu on the same side
// so first try to get the enabled one // so first try to get the enabled one
const menu = this.find(m => m.side === menuId && !m.disabled); const menu = this.find(m => m.side === menuId && !m.disabled);
@ -158,7 +143,6 @@ export class MenuController {
// didn't find a menu side that is enabled // didn't find a menu side that is enabled
// so try to get the first menu side found // so try to get the first menu side found
return this.find(m => m.side === menuId) || null; return this.find(m => m.side === menuId) || null;
} else if (menuId) { } else if (menuId) {
// the menuId was not left or right // the menuId was not left or right
// so try to get the menu by its "id" // so try to get the menu by its "id"
@ -172,19 +156,19 @@ export class MenuController {
} }
// get the first menu in the array, if one exists // get the first menu in the array, if one exists
return (this.menus.length > 0 ? this.menus[0].el : null); return this.menus.length > 0 ? this.menus[0].el : null;
} }
/** /**
* @return {Menu} Returns the instance of the menu already opened, otherwise `null`. * Returns the instance of the menu already opened, otherwise `null`.
*/ */
@Method() @Method()
getOpen(): HTMLIonMenuElement|null { getOpen(): HTMLIonMenuElement | null {
return this.find(m => m.isOpen()); return this.find(m => m.isOpen());
} }
/** /**
* @return {Array<HTMLIonMenuElement>} Returns an array of all menu instances. * Returns an array of all menu instances.
*/ */
@Method() @Method()
getMenus(): HTMLIonMenuElement[] { getMenus(): HTMLIonMenuElement[] {
@ -192,17 +176,13 @@ export class MenuController {
} }
/** /**
* @hidden * If any menu is currently animating
* @return {boolean} if any menu is currently animating
*/ */
@Method() @Method()
isAnimating(): boolean { isAnimating(): boolean {
return this.menus.some(menu => menu.isAnimating); return this.menus.some(menu => menu.isAnimating);
} }
/**
* @hidden
*/
@Method() @Method()
_register(menu: Menu) { _register(menu: Menu) {
if (this.menus.indexOf(menu) < 0) { if (this.menus.indexOf(menu) < 0) {
@ -210,9 +190,6 @@ export class MenuController {
} }
} }
/**
* @hidden
*/
@Method() @Method()
_unregister(menu: Menu) { _unregister(menu: Menu) {
const index = this.menus.indexOf(menu); const index = this.menus.indexOf(menu);
@ -221,9 +198,6 @@ export class MenuController {
} }
} }
/**
* @hidden
*/
@Method() @Method()
_setActiveMenu(menu: Menu) { _setActiveMenu(menu: Menu) {
// if this menu should be enabled // if this menu should be enabled
@ -232,14 +206,15 @@ export class MenuController {
const side = menu.side; const side = menu.side;
this.menus this.menus
.filter(m => m.side === side && m !== menu) .filter(m => m.side === side && m !== menu)
.forEach(m => m.disabled = true); .forEach(m => (m.disabled = true));
} }
/**
* @hidden
*/
@Method() @Method()
_setOpen(menu: Menu, shouldOpen: boolean, animated: boolean): Promise<boolean> { _setOpen(
menu: Menu,
shouldOpen: boolean,
animated: boolean
): Promise<boolean> {
if (this.isAnimating()) { if (this.isAnimating()) {
return Promise.resolve(false); return Promise.resolve(false);
} }
@ -252,9 +227,6 @@ export class MenuController {
return menu._setOpen(shouldOpen, animated); return menu._setOpen(shouldOpen, animated);
} }
/**
* @hidden
*/
@Method() @Method()
createAnimation(type: string, menuCmp: Menu): Promise<Animation> { createAnimation(type: string, menuCmp: Menu): Promise<Animation> {
const animationBuilder = this.menuAnimations.get(type); const animationBuilder = this.menuAnimations.get(type);
@ -269,14 +241,13 @@ export class MenuController {
this.menuAnimations.set(name, animation); this.menuAnimations.set(name, animation);
} }
private find(predicate: (menu: Menu) => boolean): HTMLIonMenuElement|null { private find(predicate: (menu: Menu) => boolean): HTMLIonMenuElement | null {
const instance = this.menus.find(predicate); const instance = this.menus.find(predicate);
if (instance) { if (instance) {
return instance.el; return instance.el;
} }
return null; return null;
} }
} }
export { menuOverlayAnimation, menuPushAnimation, menuRevealAnimation }; export { menuOverlayAnimation, menuPushAnimation, menuRevealAnimation };

View File

@ -1,5 +1,6 @@
# ion-menu-controller # ion-menu-controller
The MenuController makes it easy to control a Menu. Its methods can be used to display the menu, enable the menu, toggle the menu, and more. The controller will grab a reference to the menu by the side, id, or, if neither of these are passed to it, it will grab the first menu it finds.
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -5,19 +5,20 @@ import { Component, Listen, Prop, State } from '@stencil/core';
styleUrl: 'menu-toggle.scss' styleUrl: 'menu-toggle.scss'
}) })
export class MenuToggle { export class MenuToggle {
@Prop({ context: 'document' })
@Prop({ context: 'document' }) doc!: Document; doc!: Document;
@State() visible = false; @State() visible = false;
/** /**
* Optional property that maps to a Menu's `menuId` prop. Can also be `left` or `right` for the menu side. This is used to find the correct menu to toggle * Optional property that maps to a Menu's `menuId` prop.
* Can also be `left` or `right` for the menu side.
* This is used to find the correct menu to toggle
*/ */
@Prop() menu?: string; @Prop() menu?: string;
/** /**
* Automatically hides the content when the corresponding menu is not * Automatically hides the content when the corresponding menu is not active
* active
*/ */
@Prop() autoHide = true; @Prop() autoHide = true;
@ -54,15 +55,16 @@ export class MenuToggle {
hostData() { hostData() {
const hidden = this.autoHide && !this.visible; const hidden = this.autoHide && !this.visible;
return { return {
class: { class: {
'menu-toggle-hidden': hidden 'menu-toggle-hidden': hidden
} }
}; };
} }
} }
function getMenuController(doc: Document): Promise<HTMLIonMenuControllerElement|null> { function getMenuController(
doc: Document
): Promise<HTMLIonMenuControllerElement | null> {
const menuControllerElement = doc.querySelector('ion-menu-controller'); const menuControllerElement = doc.querySelector('ion-menu-controller');
if (!menuControllerElement) { if (!menuControllerElement) {
return Promise.resolve(null); return Promise.resolve(null);

View File

@ -1,5 +1,5 @@
# ion-menu-toggle # ion-menu-toggle
The MenuToggle component can be used to toggle a menu open or closed.
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -1,5 +1,23 @@
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop, State, Watch } from '@stencil/core'; import {
import { Animation, Color, Config, GestureDetail, MenuChangeEventDetail, Mode } from '../../interface'; Component,
Element,
Event,
EventEmitter,
EventListenerEnable,
Listen,
Method,
Prop,
State,
Watch
} from '@stencil/core';
import {
Animation,
Color,
Config,
GestureDetail,
MenuChangeEventDetail,
Mode
} from '../../interface';
import { Side, assert, isEndSide } from '../../utils/helpers'; import { Side, assert, isEndSide } from '../../utils/helpers';
@Component({ @Component({
@ -13,7 +31,6 @@ import { Side, assert, isEndSide } from '../../utils/helpers';
} }
}) })
export class Menu { export class Menu {
private animation?: Animation; private animation?: Animation;
private isPane = false; private isPane = false;
private _isOpen = false; private _isOpen = false;
@ -33,11 +50,20 @@ export class Menu {
@State() isEndSide = false; @State() isEndSide = false;
@Prop({ context: 'config' }) config!: Config; @Prop({ context: 'config' })
@Prop({ context: 'isServer' }) isServer!: boolean; config!: Config;
@Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl!: HTMLIonMenuControllerElement;
@Prop({ context: 'enableListener' }) enableListener!: EventListenerEnable; @Prop({ context: 'isServer' })
@Prop({ context: 'window' }) win!: Window; isServer!: boolean;
@Prop({ connect: 'ion-menu-controller' })
lazyMenuCtrl!: HTMLIonMenuControllerElement;
@Prop({ context: 'enableListener' })
enableListener!: EventListenerEnable;
@Prop({ context: 'window' })
win!: Window;
/** /**
* The content's id the menu should use. * The content's id the menu should use.
@ -50,11 +76,11 @@ export class Menu {
@Prop() menuId?: string; @Prop() menuId?: string;
/** /**
* The display type of the menu. Default varies based on the mode, * The display type of the menu.
* see the `menuType` in the [config](../../config/Config). Available options: * Available options: `"overlay"`, `"reveal"`, `"push"`.
* `"overlay"`, `"reveal"`, `"push"`.
*/ */
@Prop({ mutable: true }) type!: string; @Prop({ mutable: true })
type!: string;
@Watch('type') @Watch('type')
typeChanged(type: string, oldType: string | null) { typeChanged(type: string, oldType: string | null) {
@ -74,12 +100,13 @@ export class Menu {
/** /**
* If true, the menu is disabled. Default `false`. * If true, the menu is disabled. Default `false`.
*/ */
@Prop({ mutable: true }) disabled = false; @Prop({ mutable: true })
disabled = false;
@Watch('disabled') @Watch('disabled')
protected disabledChanged(disabled: boolean) { protected disabledChanged(disabled: boolean) {
this.updateState(); this.updateState();
this.ionMenuChange.emit({ disabled: disabled, open: this._isOpen}); this.ionMenuChange.emit({ disabled: disabled, open: this._isOpen });
} }
/** /**
@ -107,6 +134,10 @@ export class Menu {
*/ */
@Prop() persistent = false; @Prop() persistent = false;
/**
* The edge threshold for dragging the menu open.
* If a drag/swipe happens over this value, the menu is not triggered.
*/
@Prop() maxEdgeStart = 50; @Prop() maxEdgeStart = 50;
/** /**
@ -119,7 +150,6 @@ export class Menu {
*/ */
@Event() ionClose!: EventEmitter<void>; @Event() ionClose!: EventEmitter<void>;
@Event() protected ionMenuChange!: EventEmitter<MenuChangeEventDetail>; @Event() protected ionMenuChange!: EventEmitter<MenuChangeEventDetail>;
async componentWillLoad() { async componentWillLoad() {
@ -139,13 +169,15 @@ export class Menu {
} }
const el = this.el; const el = this.el;
const parent = el.parentNode as any; const parent = el.parentNode as any;
const content = (this.contentId) const content = this.contentId
? document.getElementById(this.contentId) ? document.getElementById(this.contentId)
: parent && parent.querySelector && parent.querySelector('[main]'); : parent && parent.querySelector && parent.querySelector('[main]');
if (!content || !content.tagName) { if (!content || !content.tagName) {
// requires content element // requires content element
console.error('Menu: must have a "content" element to listen for drag events on.'); console.error(
'Menu: must have a "content" element to listen for drag events on.'
);
return; return;
} }
this.contentEl = content as HTMLElement; this.contentEl = content as HTMLElement;
@ -165,7 +197,7 @@ export class Menu {
} }
// register this menu with the app's menu controller // register this menu with the app's menu controller
this.menuCtrl!._register(this); this.menuCtrl!._register(this);
this.ionMenuChange.emit({ disabled: !isEnabled, open: this._isOpen}); this.ionMenuChange.emit({ disabled: !isEnabled, open: this._isOpen });
// mask it as enabled / disabled // mask it as enabled / disabled
this.disabled = !isEnabled; this.disabled = !isEnabled;
@ -188,7 +220,7 @@ export class Menu {
@Listen('body:click', { enabled: false, capture: true }) @Listen('body:click', { enabled: false, capture: true })
onBackdropClick(ev: UIEvent) { onBackdropClick(ev: UIEvent) {
const el = ev.target as HTMLElement; const el = ev.target as HTMLElement;
if (!el.closest('.menu-inner') && this.lastOnEnd < (ev.timeStamp - 100)) { if (!el.closest('.menu-inner') && this.lastOnEnd < ev.timeStamp - 100) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.close(); this.close();
@ -222,7 +254,7 @@ export class Menu {
async _setOpen(shouldOpen: boolean, animated = true): Promise<boolean> { async _setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
// If the menu is disabled or it is currenly being animated, let's do nothing // If the menu is disabled or it is currenly being animated, let's do nothing
if (!this.isActive() || this.isAnimating || (shouldOpen === this._isOpen)) { if (!this.isActive() || this.isAnimating || shouldOpen === this._isOpen) {
return this._isOpen; return this._isOpen;
} }
@ -257,7 +289,10 @@ export class Menu {
this.animation = await this.menuCtrl!.createAnimation(this.type, this); this.animation = await this.menuCtrl!.createAnimation(this.type, this);
} }
private async startAnimation(shouldOpen: boolean, animated: boolean): Promise<void> { private async startAnimation(
shouldOpen: boolean,
animated: boolean
): Promise<void> {
const ani = this.animation!.reverse(!shouldOpen); const ani = this.animation!.reverse(!shouldOpen);
if (animated) { if (animated) {
await ani.playAsync(); await ani.playAsync();
@ -267,9 +302,7 @@ export class Menu {
} }
private canSwipe(): boolean { private canSwipe(): boolean {
return this.swipeEnabled && return this.swipeEnabled && !this.isAnimating && this.isActive();
!this.isAnimating &&
this.isActive();
} }
private canStart(detail: GestureDetail): boolean { private canStart(detail: GestureDetail): boolean {
@ -281,7 +314,12 @@ export class Menu {
} else if (this.menuCtrl!.getOpen()) { } else if (this.menuCtrl!.getOpen()) {
return false; return false;
} }
return checkEdgeSide(this.win, detail.currentX, this.isEndSide, this.maxEdgeStart); return checkEdgeSide(
this.win,
detail.currentX,
this.isEndSide,
this.maxEdgeStart
);
} }
private onWillStart(): Promise<void> { private onWillStart(): Promise<void> {
@ -296,9 +334,7 @@ export class Menu {
} }
// the cloned animation should not use an easing curve during seek // the cloned animation should not use an easing curve during seek
this.animation this.animation.reverse(this._isOpen).progressStart();
.reverse(this._isOpen)
.progressStart();
} }
private onDragMove(detail: GestureDetail) { private onDragMove(detail: GestureDetail) {
@ -324,17 +360,17 @@ export class Menu {
const stepValue = delta / width; const stepValue = delta / width;
const velocity = detail.velocityX; const velocity = detail.velocityX;
const z = width / 2.0; const z = width / 2.0;
const shouldCompleteRight = (velocity >= 0) const shouldCompleteRight =
&& (velocity > 0.2 || detail.deltaX > z); velocity >= 0 && (velocity > 0.2 || detail.deltaX > z);
const shouldCompleteLeft = (velocity <= 0) const shouldCompleteLeft =
&& (velocity < -0.2 || detail.deltaX < -z); velocity <= 0 && (velocity < -0.2 || detail.deltaX < -z);
const shouldComplete = (isOpen) const shouldComplete = isOpen
? isEndSide ? shouldCompleteRight : shouldCompleteLeft ? isEndSide ? shouldCompleteRight : shouldCompleteLeft
: isEndSide ? shouldCompleteLeft : shouldCompleteRight; : isEndSide ? shouldCompleteLeft : shouldCompleteRight;
let shouldOpen = (!isOpen && shouldComplete); let shouldOpen = !isOpen && shouldComplete;
if (isOpen && !shouldComplete) { if (isOpen && !shouldComplete) {
shouldOpen = true; shouldOpen = true;
} }
@ -349,7 +385,9 @@ export class Menu {
this.lastOnEnd = detail.timeStamp; this.lastOnEnd = detail.timeStamp;
this.animation this.animation
.onFinish(() => this.afterAnimation(shouldOpen), { clearExistingCallacks: true }) .onFinish(() => this.afterAnimation(shouldOpen), {
clearExistingCallacks: true
})
.progressEnd(shouldComplete, stepValue, realDur); .progressEnd(shouldComplete, stepValue, realDur);
} }
@ -382,7 +420,6 @@ export class Menu {
// emit open event // emit open event
this.ionOpen.emit(); this.ionOpen.emit();
} else { } else {
// remove css classes // remove css classes
this.el.classList.remove(SHOW_MENU); this.el.classList.remove(SHOW_MENU);
@ -425,22 +462,23 @@ export class Menu {
[`menu-type-${this.type}`]: true, [`menu-type-${this.type}`]: true,
'menu-enabled': !this.disabled, 'menu-enabled': !this.disabled,
'menu-side-right': isEndSide, 'menu-side-right': isEndSide,
'menu-side-left': !isEndSide, 'menu-side-left': !isEndSide
} }
}; };
} }
render() { render() {
return ([ return [
<div class="menu-inner" ref={el => this.menuInnerEl = el}> <div class="menu-inner" ref={el => (this.menuInnerEl = el)}>
<slot></slot> <slot />
</div>, </div>,
<ion-backdrop <ion-backdrop
ref={el => this.backdropEl = el} ref={el => (this.backdropEl = el)}
class="menu-backdrop" class="menu-backdrop"
tappable={false} tappable={false}
stopPropagation={false}/>, stopPropagation={false}
/>,
<ion-gesture <ion-gesture
canStart={this.canStart.bind(this)} canStart={this.canStart.bind(this)}
@ -454,16 +492,26 @@ export class Menu {
direction="x" direction="x"
threshold={10} threshold={10}
attachTo="window" attachTo="window"
disableScroll={true} /> disableScroll={true}
]); />
];
} }
} }
function computeDelta(deltaX: number, isOpen: boolean, isEndSide: boolean): number { function computeDelta(
return Math.max(0, (isOpen !== isEndSide) ? -deltaX : deltaX); deltaX: number,
isOpen: boolean,
isEndSide: boolean
): number {
return Math.max(0, isOpen !== isEndSide ? -deltaX : deltaX);
} }
function checkEdgeSide(win: Window, posX: number, isEndSide: boolean, maxEdgeStart: number): boolean { function checkEdgeSide(
win: Window,
posX: number,
isEndSide: boolean,
maxEdgeStart: number
): boolean {
if (isEndSide) { if (isEndSide) {
return posX >= win.innerWidth - maxEdgeStart; return posX >= win.innerWidth - maxEdgeStart;
} else { } else {

View File

@ -1,5 +1,11 @@
# ion-menu # ion-menu
The Menu component is a navigation drawer that slides in from the side of the current view.
By default, it slides in from the left, but the side can be overridden.
The menu will be displayed differently based on the mode, however the display type can be changed to any of the available menu types.
The menu element should be a sibling to the root content element.
There can be any number of menus attached to the content.
These can be controlled from the templates, or programmatically using the MenuController.
<!-- Auto Generated Below --> <!-- Auto Generated Below -->

View File

@ -0,0 +1,10 @@
```html
<ion-menu>
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
</ion-menu>
<ion-router-outlet main></ion-router-outlet>
```

View File

@ -0,0 +1,33 @@
```html
<ion-app>
<ion-menu side="start">
<ion-header>
<ion-toolbar color="secondary">
<ion-title>Left Menu</ion-title>
</ion-toolbar>
</ion-header>
</ion-menu>
<ion-menu side="end">
<ion-header>
<ion-toolbar>
<ion-title>Hola</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
hola macho
</ion-content>
</ion-menu>
<div class="ion-page" main>
<ion-header>
<ion-toolbar>
<ion-title>Menu - Basic</ion-title>
</ion-toolbar>
</ion-header>
</div>
</ion-app>
<ion-menu-controller></ion-menu-controller>
```