refactor(tabs): ion-tabbar can be used in standalone mode

This commit is contained in:
Manu Mtz.-Almeida
2018-10-09 14:27:02 -05:00
parent 25f6e28e9c
commit e3bbfd0b05
7 changed files with 54 additions and 141 deletions

View File

@ -824,7 +824,7 @@ export class Tab {
} }
export declare interface Tabs extends StencilComponents<'IonTabs'> {} export declare interface Tabs extends StencilComponents<'IonTabs'> {}
@Component({ selector: 'ion-tabs', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['mode', 'color', 'name', 'tabbarHidden', 'tabbarHighlight', 'tabbarLayout', 'tabbarPlacement', 'translucent', 'useRouter'] }) @Component({ selector: 'ion-tabs', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['name', 'tabbarHidden', 'useRouter'] })
export class Tabs { export class Tabs {
ionChange: EventEmitter<CustomEvent>; ionChange: EventEmitter<CustomEvent>;
ionNavWillLoad: EventEmitter<CustomEvent>; ionNavWillLoad: EventEmitter<CustomEvent>;
@ -834,7 +834,7 @@ export class Tabs {
constructor(r: ElementRef) { constructor(r: ElementRef) {
const el = r.nativeElement; const el = r.nativeElement;
proxyMethods(this, el, ['select', 'setRouteId', 'getRouteId', 'getTab', 'getSelected']); proxyMethods(this, el, ['select', 'setRouteId', 'getRouteId', 'getTab', 'getSelected']);
proxyInputs(this, el, ['mode', 'color', 'name', 'tabbarHidden', 'tabbarHighlight', 'tabbarLayout', 'tabbarPlacement', 'translucent', 'useRouter']); proxyInputs(this, el, ['name', 'tabbarHidden', 'useRouter']);
proxyOutputs(this, el, ['ionChange', 'ionNavWillLoad', 'ionNavWillChange', 'ionNavDidChange']); proxyOutputs(this, el, ['ionChange', 'ionNavWillLoad', 'ionNavWillChange', 'ionNavDidChange']);
} }
} }

View File

@ -4596,10 +4596,6 @@ export namespace Components {
} }
interface IonTabs { interface IonTabs {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
*/
'color'?: Color;
'getRouteId': () => Promise<RouteID | undefined>; 'getRouteId': () => Promise<RouteID | undefined>;
/** /**
* Get the currently selected tab * Get the currently selected tab
@ -4610,10 +4606,6 @@ export namespace Components {
*/ */
'getTab': (tabOrIndex: string | number | HTMLIonTabElement) => Promise<HTMLIonTabElement | undefined>; 'getTab': (tabOrIndex: string | number | HTMLIonTabElement) => Promise<HTMLIonTabElement | undefined>;
/** /**
* The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`.
*/
'mode': Mode;
/**
* A unique name for the tabs. * A unique name for the tabs.
*/ */
'name'?: string; 'name'?: string;
@ -4627,35 +4619,11 @@ export namespace Components {
*/ */
'tabbarHidden': boolean; 'tabbarHidden': boolean;
/** /**
* If true, show the tab highlight bar under the selected tab.
*/
'tabbarHighlight'?: boolean;
/**
* Set the layout of the text and icon in the tabbar. Available options: `"icon-top"`, `"icon-start"`, `"icon-end"`, `"icon-bottom"`, `"icon-hide"`, `"label-hide"`.
*/
'tabbarLayout'?: TabbarLayout;
/**
* Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`.
*/
'tabbarPlacement'?: TabbarPlacement;
/**
* If true, the tabs will be translucent. Note: In order to scroll content behind the tabs, the `fullscreen` attribute needs to be set on the content. Defaults to `false`.
*/
'translucent': boolean;
/**
* If true, the tabs will use the router and `selectedTab` will not do anything. * If true, the tabs will use the router and `selectedTab` will not do anything.
*/ */
'useRouter': boolean; 'useRouter': boolean;
} }
interface IonTabsAttributes extends StencilHTMLAttributes { interface IonTabsAttributes extends StencilHTMLAttributes {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
*/
'color'?: Color;
/**
* The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`.
*/
'mode'?: Mode;
/** /**
* A unique name for the tabs. * A unique name for the tabs.
*/ */
@ -4681,22 +4649,6 @@ export namespace Components {
*/ */
'tabbarHidden'?: boolean; 'tabbarHidden'?: boolean;
/** /**
* If true, show the tab highlight bar under the selected tab.
*/
'tabbarHighlight'?: boolean;
/**
* Set the layout of the text and icon in the tabbar. Available options: `"icon-top"`, `"icon-start"`, `"icon-end"`, `"icon-bottom"`, `"icon-hide"`, `"label-hide"`.
*/
'tabbarLayout'?: TabbarLayout;
/**
* Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`.
*/
'tabbarPlacement'?: TabbarPlacement;
/**
* If true, the tabs will be translucent. Note: In order to scroll content behind the tabs, the `fullscreen` attribute needs to be set on the content. Defaults to `false`.
*/
'translucent'?: boolean;
/**
* If true, the tabs will use the router and `selectedTab` will not do anything. * If true, the tabs will use the router and `selectedTab` will not do anything.
*/ */
'useRouter'?: boolean; 'useRouter'?: boolean;

View File

@ -7,7 +7,6 @@
); );
display: flex; display: flex;
position: relative;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -21,6 +20,7 @@
user-select: none; user-select: none;
z-index: $z-index-toolbar; z-index: $z-index-toolbar;
box-sizing: content-box;
} }
:host(.ion-color) { :host(.ion-color) {

View File

@ -35,10 +35,14 @@ export class Tabbar implements ComponentInterface {
*/ */
@Prop() placement: TabbarPlacement = 'bottom'; @Prop() placement: TabbarPlacement = 'bottom';
/** The selected tab component */ /**
* The selected tab component
*/
@Prop() selectedTab?: HTMLIonTabElement; @Prop() selectedTab?: HTMLIonTabElement;
/** The tabs to render */ /**
* The tabs to render
*/
@Prop() tabs: HTMLIonTabElement[] = []; @Prop() tabs: HTMLIonTabElement[] = [];
/** /**
@ -51,7 +55,9 @@ export class Tabbar implements ComponentInterface {
*/ */
@Prop() translucent = false; @Prop() translucent = false;
/** Emitted when the tab bar is clicked */ /**
* Emitted when the tab bar is clicked
*/
@Event() ionTabbarClick!: EventEmitter<HTMLIonTabElement>; @Event() ionTabbarClick!: EventEmitter<HTMLIonTabElement>;
@Listen('body:keyboardWillHide') @Listen('body:keyboardWillHide')
@ -70,10 +76,6 @@ export class Tabbar implements ComponentInterface {
this.updateHighlight(); this.updateHighlight();
} }
private getSelectedButton(): HTMLElement | null {
return this.el.shadowRoot!.querySelector('.tab-btn-selected');
}
@Watch('selectedTab') @Watch('selectedTab')
@Listen('window:resize') @Listen('window:resize')
private updateHighlight() { private updateHighlight() {
@ -81,7 +83,7 @@ export class Tabbar implements ComponentInterface {
return; return;
} }
this.queue.read(() => { this.queue.read(() => {
const btn = this.getSelectedButton(); const btn = this.el.shadowRoot!.querySelector('.tab-btn-selected') as HTMLElement | null;
const highlight = this.el.shadowRoot!.querySelector('.tabbar-highlight') as HTMLElement; const highlight = this.el.shadowRoot!.querySelector('.tabbar-highlight') as HTMLElement;
if (btn && highlight) { if (btn && highlight) {
highlight.style.transform = `translate3d(${btn.offsetLeft}px,0,0) scaleX(${btn.offsetWidth})`; highlight.style.transform = `translate3d(${btn.offsetLeft}px,0,0) scaleX(${btn.offsetWidth})`;
@ -94,6 +96,7 @@ export class Tabbar implements ComponentInterface {
return { return {
role: 'tablist', role: 'tablist',
'aria-hidden': keyboardVisible ? 'true' : null, 'aria-hidden': keyboardVisible ? 'true' : null,
'slot': 'tabbar',
class: { class: {
...createColorClasses(color), ...createColorClasses(color),
'tabbar-translucent': translucent, 'tabbar-translucent': translucent,

View File

@ -15,21 +15,15 @@
z-index: $z-index-page-container; z-index: $z-index-page-container;
} }
ion-tabbar { .tabs-inner {
@include position(null, 0, 0, 0); position: relative;
flex: 1;
contain: layout size style;
}
:host(.tabbar-hidden) ion-tabbar,
:host(.tabbar-hidden)::slotted(ion-tabbar) {
display: none; display: none;
position: absolute;
}
:host(.tabbar-visible.tabs-md)::slotted(ion-tab) {
bottom: 56px; // tabbar height (it's fixed!)
}
:host(.tabbar-visible.tabs-ios)::slotted(ion-tab) {
bottom: 50px; // tabbar height (it's fixed!)
}
:host(.tabbar-visible) ion-tabbar {
display: flex;
} }

View File

@ -1,12 +1,11 @@
import { Build, Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core'; import { Build, Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
import { Color, Config, IonicConfig, Mode, NavOutlet, RouteID, RouteWrite, TabbarLayout, TabbarPlacement } from '../../interface'; import { Config, NavOutlet, RouteID, RouteWrite } from '../../interface';
import { createThemedClasses } from '../../utils/theme';
@Component({ @Component({
tag: 'ion-tabs', tag: 'ion-tabs',
styleUrl: 'tabs.scss', styleUrl: 'tabs.scss',
scoped: true shadow: true
}) })
export class Tabs implements NavOutlet { export class Tabs implements NavOutlet {
@ -14,6 +13,7 @@ export class Tabs implements NavOutlet {
private transitioning = false; private transitioning = false;
private tabsId = (++tabIds); private tabsId = (++tabIds);
private leavingTab?: HTMLIonTabElement; private leavingTab?: HTMLIonTabElement;
private userTabbarEl?: HTMLIonTabbarElement;
@Element() el!: HTMLStencilElement; @Element() el!: HTMLStencilElement;
@ -23,19 +23,6 @@ export class Tabs implements NavOutlet {
@Prop({ context: 'config' }) config!: Config; @Prop({ context: 'config' }) config!: Config;
@Prop({ context: 'document' }) doc!: Document; @Prop({ context: 'document' }) doc!: Document;
/**
* The mode determines which platform styles to use.
* Possible values are: `"ios"` or `"md"`.
*/
@Prop() mode!: Mode;
/**
* The color to use from your application's color palette.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
* For more information on colors, see [theming](/docs/theming/basics).
*/
@Prop() color?: Color;
/** /**
* A unique name for the tabs. * A unique name for the tabs.
*/ */
@ -46,29 +33,6 @@ export class Tabs implements NavOutlet {
*/ */
@Prop() tabbarHidden = false; @Prop() tabbarHidden = false;
/**
* If true, show the tab highlight bar under the selected tab.
*/
@Prop({ mutable: true }) tabbarHighlight?: boolean;
/**
* Set the layout of the text and icon in the tabbar. Available options: `"icon-top"`, `"icon-start"`, `"icon-end"`, `"icon-bottom"`, `"icon-hide"`, `"label-hide"`.
*/
@Prop({ mutable: true }) tabbarLayout?: TabbarLayout;
/**
* Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`.
*/
@Prop({ mutable: true }) tabbarPlacement?: TabbarPlacement;
/**
* If true, the tabs will be translucent.
* Note: In order to scroll content behind the tabs, the `fullscreen`
* attribute needs to be set on the content.
* Defaults to `false`.
*/
@Prop() translucent = false;
/** /**
* If true, the tabs will use the router and `selectedTab` will not do anything. * If true, the tabs will use the router and `selectedTab` will not do anything.
*/ */
@ -94,18 +58,16 @@ export class Tabs implements NavOutlet {
*/ */
@Event() ionNavDidChange!: EventEmitter<void>; @Event() ionNavDidChange!: EventEmitter<void>;
componentWillLoad() { async componentWillLoad() {
if (!this.useRouter) { if (!this.useRouter) {
this.useRouter = !!this.doc.querySelector('ion-router') && !this.el.closest('[no-router]'); this.useRouter = !!this.doc.querySelector('ion-router') && !this.el.closest('[no-router]');
} }
this.userTabbarEl = this.el.querySelector('ion-tabbar') || undefined;
this.loadConfig('tabbarPlacement', 'bottom'); await this.initTabs();
this.loadConfig('tabbarLayout', 'icon-top');
this.loadConfig('tabbarHighlight', false);
this.initTabs();
this.ionNavWillLoad.emit(); this.ionNavWillLoad.emit();
this.componentWillUpdate();
} }
componentDidLoad() { componentDidLoad() {
@ -117,6 +79,14 @@ export class Tabs implements NavOutlet {
this.selectedTab = this.leavingTab = undefined; this.selectedTab = this.leavingTab = undefined;
} }
componentWillUpdate() {
const tabbarEl = this.userTabbarEl;
if (tabbarEl) {
tabbarEl.tabs = this.tabs.slice();
tabbarEl.selectedTab = this.selectedTab;
}
}
@Listen('ionTabMutated') @Listen('ionTabMutated')
protected onTabMutated() { protected onTabMutated() {
this.el.forceUpdate(); this.el.forceUpdate();
@ -202,6 +172,7 @@ export class Tabs implements NavOutlet {
tab.btnId = 'tab-' + id; tab.btnId = 'tab-' + id;
tab.id = 'tabpanel-' + id; tab.id = 'tabpanel-' + id;
}); });
return Promise.all(tabs.map(tab => tab.componentOnReady()));
} }
private async initSelect(): Promise<void> { private async initSelect(): Promise<void> {
@ -236,13 +207,6 @@ export class Tabs implements NavOutlet {
} }
} }
private loadConfig(attrKey: keyof IonicConfig, fallback: any) {
const val = (this as any)[attrKey];
if (typeof val === 'undefined') {
(this as any)[attrKey] = this.config.get(attrKey, fallback);
}
}
private setActive(selectedTab: HTMLIonTabElement): Promise<void> { private setActive(selectedTab: HTMLIonTabElement): Promise<void> {
if (this.transitioning) { if (this.transitioning) {
return Promise.reject('transitioning already happening'); return Promise.reject('transitioning already happening');
@ -300,26 +264,24 @@ export class Tabs implements NavOutlet {
hostData() { hostData() {
return { return {
class: { class: {
...createThemedClasses(this.mode, 'tabs'), 'tabbar-hidden': this.tabbarHidden
'tabbar-visible': !this.tabbarHidden
} }
}; };
} }
render() { render() {
return ( return [
<ion-tabbar <div class="tabs-inner">
mode={this.mode} <slot></slot>
tabs={this.tabs.slice()} </div>,
color={this.color} <slot name="tabbar">
selectedTab={this.selectedTab} <ion-tabbar
highlight={this.tabbarHighlight} tabs={this.tabs.slice()}
placement={this.tabbarPlacement} selectedTab={this.selectedTab}
layout={this.tabbarLayout} >
translucent={this.translucent} </ion-tabbar>
> </slot>
</ion-tabbar> ];
);
} }
} }

View File

@ -63,6 +63,8 @@
<ion-tab disabled label="Messages" icon="chatboxes" component="page-one"> <ion-tab disabled label="Messages" icon="chatboxes" component="page-one">
</ion-tab> </ion-tab>
<ion-tabbar></ion-tabbar>
</ion-tabs> </ion-tabs>
</ion-app> </ion-app>