mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 22:17:40 +08:00
feat(tabs): adds tabs
This commit is contained in:
103
packages/core/src/components.d.ts
vendored
103
packages/core/src/components.d.ts
vendored
@ -1751,6 +1751,7 @@ declare global {
|
||||
canGoBack?: any,
|
||||
canSwipeBack?: any,
|
||||
getFirstView?: any,
|
||||
resize?: any,
|
||||
root?: any,
|
||||
delegate?: any
|
||||
}
|
||||
@ -2782,40 +2783,70 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
import { TabBar as IonTabBar } from './components/tabs/tab-bar';
|
||||
import { PageTab as PageTab } from './components/tabs/page-tab';
|
||||
|
||||
interface HTMLIonTabBarElement extends IonTabBar, HTMLElement {
|
||||
interface HTMLPageTabElement extends PageTab, HTMLElement {
|
||||
}
|
||||
declare var HTMLIonTabBarElement: {
|
||||
prototype: HTMLIonTabBarElement;
|
||||
new (): HTMLIonTabBarElement;
|
||||
declare var HTMLPageTabElement: {
|
||||
prototype: HTMLPageTabElement;
|
||||
new (): HTMLPageTabElement;
|
||||
};
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ion-tab-bar": HTMLIonTabBarElement;
|
||||
"page-tab": HTMLPageTabElement;
|
||||
}
|
||||
interface ElementTagNameMap {
|
||||
"ion-tab-bar": HTMLIonTabBarElement;
|
||||
"page-tab": HTMLPageTabElement;
|
||||
}
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"ion-tab-bar": JSXElements.IonTabBarAttributes;
|
||||
"page-tab": JSXElements.PageTabAttributes;
|
||||
}
|
||||
}
|
||||
namespace JSXElements {
|
||||
export interface IonTabBarAttributes extends HTMLAttributes {
|
||||
export interface PageTabAttributes extends HTMLAttributes {
|
||||
mode?: string,
|
||||
color?: string,
|
||||
|
||||
tabs?: any,
|
||||
onTabSelected?: any,
|
||||
selectedIndex?: number,
|
||||
tabsLayout?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import { TabButton as IonTabButton } from './components/tabs/tab-button';
|
||||
import { TabBar as IonTabbar } from './components/tabs/tab-bar';
|
||||
|
||||
interface HTMLIonTabbarElement extends IonTabbar, HTMLElement {
|
||||
}
|
||||
declare var HTMLIonTabbarElement: {
|
||||
prototype: HTMLIonTabbarElement;
|
||||
new (): HTMLIonTabbarElement;
|
||||
};
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ion-tabbar": HTMLIonTabbarElement;
|
||||
}
|
||||
interface ElementTagNameMap {
|
||||
"ion-tabbar": HTMLIonTabbarElement;
|
||||
}
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"ion-tabbar": JSXElements.IonTabbarAttributes;
|
||||
}
|
||||
}
|
||||
namespace JSXElements {
|
||||
export interface IonTabbarAttributes extends HTMLAttributes {
|
||||
mode?: string,
|
||||
color?: string,
|
||||
|
||||
placement?: string,
|
||||
tabs?: any,
|
||||
selectedTab?: any,
|
||||
layout?: string,
|
||||
highlight?: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import { TabbarButton as IonTabButton } from './components/tabs/tab-button';
|
||||
|
||||
interface HTMLIonTabButtonElement extends IonTabButton, HTMLElement {
|
||||
}
|
||||
@ -2840,10 +2871,8 @@ declare global {
|
||||
mode?: string,
|
||||
color?: string,
|
||||
|
||||
tab?: any,
|
||||
layout?: string,
|
||||
selectedIndex?: number,
|
||||
index?: number
|
||||
selected?: boolean,
|
||||
tab?: any
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2873,6 +2902,7 @@ declare global {
|
||||
mode?: string,
|
||||
color?: string,
|
||||
|
||||
selectedTab?: any
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2902,16 +2932,23 @@ declare global {
|
||||
mode?: string,
|
||||
color?: string,
|
||||
|
||||
root?: string,
|
||||
_setActive?: any,
|
||||
resize?: any,
|
||||
goToRoot?: any,
|
||||
getActive?: any,
|
||||
getNav?: any,
|
||||
btnId?: string,
|
||||
root?: any,
|
||||
rootParams?: any,
|
||||
tabTitle?: string,
|
||||
tabIcon?: string,
|
||||
tabBadge?: string,
|
||||
tabBadgeStyle?: string,
|
||||
urlPath?: string,
|
||||
title?: string,
|
||||
icon?: string,
|
||||
badge?: string,
|
||||
badgeStyle?: string,
|
||||
enabled?: boolean,
|
||||
shown?: boolean,
|
||||
show?: boolean,
|
||||
tabsHideOnSubPages?: boolean,
|
||||
onSelected?: any
|
||||
selected?: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2941,10 +2978,18 @@ declare global {
|
||||
mode?: string,
|
||||
color?: string,
|
||||
|
||||
tabsLayout?: string,
|
||||
tabsPlacement?: string,
|
||||
tabsHighlight?: boolean,
|
||||
ionChange?: any
|
||||
select?: any,
|
||||
getByIndex?: any,
|
||||
getSelected?: any,
|
||||
getIndex?: any,
|
||||
getTabs?: any,
|
||||
previousTab?: any,
|
||||
resize?: any,
|
||||
name?: string,
|
||||
tabbarHidden?: boolean,
|
||||
tabbarLayout?: string,
|
||||
tabbarPlacement?: string,
|
||||
tabbarHighlight?: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -224,7 +224,6 @@ sub {
|
||||
|
||||
ion-app,
|
||||
ion-nav,
|
||||
ion-tab,
|
||||
ion-tabs,
|
||||
ion-page,
|
||||
.ion-page {
|
||||
@ -240,21 +239,37 @@ ion-page,
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
ion-nav,
|
||||
ion-tab,
|
||||
ion-tabs {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tabs-inner {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
ion-tab {
|
||||
@include position(0, null, null, 0);
|
||||
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
display: none;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
ion-tab.show-tab {
|
||||
z-index: $z-index-page-container;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
// Page Container Structure
|
||||
// --------------------------------------------------
|
||||
|
||||
|
@ -19,6 +19,8 @@ ion-backdrop {
|
||||
background-color: $backdrop-color;
|
||||
opacity: .01;
|
||||
transform: translateZ(0);
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
ion-backdrop.backdrop-no-tappable {
|
||||
|
@ -428,7 +428,7 @@ export class Menu {
|
||||
protected hostData() {
|
||||
const typeClass = 'menu-type-' + this.type;
|
||||
return {
|
||||
role: 'navigation',
|
||||
role: 'complementary',
|
||||
class: {
|
||||
'menu-enabled': this.isActive(),
|
||||
'menu-side-right': this.isRightSide,
|
||||
|
@ -36,7 +36,6 @@ export class IonNav implements Nav {
|
||||
|
||||
componentDidLoad() {
|
||||
componentDidLoadImpl(this);
|
||||
|
||||
}
|
||||
|
||||
getViews(): ViewController[] {
|
||||
@ -118,6 +117,11 @@ export class IonNav implements Nav {
|
||||
return getFirstView(this);
|
||||
}
|
||||
|
||||
@Method()
|
||||
resize() {
|
||||
console.log('resize content');
|
||||
}
|
||||
|
||||
@Listen('navInit')
|
||||
navInitialized(event: CustomEvent) {
|
||||
navInitializedImpl(this, event);
|
||||
@ -134,7 +138,6 @@ export function componentDidLoadImpl(nav: Nav) {
|
||||
if (nav.root) {
|
||||
nav.setRoot(nav.root);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function pushImpl(nav: Nav, component: any, data: any, opts: NavOptions) {
|
||||
|
@ -17,17 +17,30 @@ export class PageOne {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return [<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Page One</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>,
|
||||
<ion-content>
|
||||
Page One Content
|
||||
<div>
|
||||
<ion-button onClick={() => this.nextPage()}>Go to Page Two</ion-button>
|
||||
</div>
|
||||
</ion-content>
|
||||
return [
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Page One</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>,
|
||||
<ion-content>
|
||||
<div>
|
||||
Page One Content
|
||||
<ion-button onClick={() => this.nextPage()}>Go to Page Two</ion-button>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
</div>
|
||||
</ion-content>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ export class PageTwo {
|
||||
<ion-title>Page Two</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>,
|
||||
<ion-content>
|
||||
<ion-content fullscreen={true}>
|
||||
Page Two Content
|
||||
<div>
|
||||
<ion-button onClick={() => this.nextPage()}>Go to Page Three</ion-button>
|
||||
|
@ -12,5 +12,18 @@
|
||||
<ion-app>
|
||||
<ion-nav root="page-one"></ion-nav>
|
||||
</ion-app>
|
||||
<style>
|
||||
f {
|
||||
display: block;
|
||||
margin: 15px auto;
|
||||
max-width: 150px;
|
||||
height: 150px;
|
||||
background: blue;
|
||||
}
|
||||
|
||||
f:last-of-type {
|
||||
background: yellow;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
@ -396,7 +396,7 @@ export class PickerColumnCmp {
|
||||
}
|
||||
}
|
||||
|
||||
hostData() {
|
||||
protected hostData() {
|
||||
return {
|
||||
class: {
|
||||
'picker-opts-left': this.col.align === 'left',
|
||||
@ -405,7 +405,7 @@ export class PickerColumnCmp {
|
||||
style: {
|
||||
'max-width': this.col.columnWidth
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@ -424,7 +424,7 @@ export class PickerColumnCmp {
|
||||
|
||||
if (col.prefix) {
|
||||
results.push(
|
||||
<div class="picker-prefix" style={{width: col.prefixWidth}}>
|
||||
<div class='picker-prefix' style={{width: col.prefixWidth}}>
|
||||
{col.prefix}
|
||||
</div>
|
||||
);
|
||||
@ -445,7 +445,7 @@ export class PickerColumnCmp {
|
||||
'attachTo': 'parent',
|
||||
'block': this.activeBlock
|
||||
}}></ion-gesture>,
|
||||
<div class="picker-opts" style={{maxWidth: col.optionsWidth}}>
|
||||
<div class='picker-opts' style={{maxWidth: col.optionsWidth}}>
|
||||
{options.map((o, index) =>
|
||||
<button
|
||||
class={{'picker-opt': true, 'picker-opt-disabled': o.disabled}}
|
||||
@ -459,7 +459,7 @@ export class PickerColumnCmp {
|
||||
|
||||
if (col.suffix) {
|
||||
results.push(
|
||||
<div class="picker-suffix" style={{width: col.suffixWidth}}>
|
||||
<div class='picker-suffix' style={{width: col.suffixWidth}}>
|
||||
{col.suffix}
|
||||
</div>
|
||||
);
|
||||
@ -472,4 +472,4 @@ export class PickerColumnCmp {
|
||||
export const PICKER_OPT_SELECTED = 'picker-opt-selected';
|
||||
export const DECELERATION_FRICTION = 0.97;
|
||||
export const FRAME_MS = (1000 / 60);
|
||||
export const MAX_PICKER_SPEED = 60;
|
||||
export const MAX_PICKER_SPEED = 60;
|
||||
|
@ -51,4 +51,4 @@
|
||||
</ion-page>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
71
packages/core/src/components/tabs/page-tab.tsx
Normal file
71
packages/core/src/components/tabs/page-tab.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { Component, Element } from '@stencil/core';
|
||||
import { HTMLIonTabsElement } from '../../index';
|
||||
|
||||
@Component({
|
||||
tag: 'page-tab'
|
||||
})
|
||||
export class PageTab {
|
||||
|
||||
@Element() element: HTMLElement;
|
||||
|
||||
getTabs() {
|
||||
return this.element.closest('ion-tabs') as HTMLIonTabsElement;
|
||||
}
|
||||
|
||||
setLayout(value: string) {
|
||||
this.getTabs().tabbarLayout = value;
|
||||
}
|
||||
|
||||
setPlacement(value: string) {
|
||||
this.getTabs().tabbarPlacement = value;
|
||||
}
|
||||
|
||||
setHidden(value: boolean) {
|
||||
this.getTabs().tabbarHidden = value;
|
||||
}
|
||||
|
||||
setHighlight(value: boolean) {
|
||||
this.getTabs().tabbarHighlight = value;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return [
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Tab page</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>,
|
||||
<ion-content>
|
||||
<p>
|
||||
<h2>Set tabbar layout</h2>
|
||||
<ion-button onClick={() => this.setLayout('icon-top')}>icon-top</ion-button>
|
||||
<ion-button onClick={() => this.setLayout('icon-start')}>icon-start</ion-button>
|
||||
<ion-button onClick={() => this.setLayout('icon-end')}>icon-end</ion-button>
|
||||
<ion-button onClick={() => this.setLayout('icon-bottom')}>icon-bottom</ion-button>
|
||||
<ion-button onClick={() => this.setLayout('icon-hide')}>icon-hide</ion-button>
|
||||
<ion-button onClick={() => this.setLayout('title-hide')}>title-hide</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<h2>Set tabbar placement</h2>
|
||||
<ion-button onClick={() => this.setPlacement('top')}>top</ion-button>
|
||||
<ion-button onClick={() => this.setPlacement('bottom')}>bottom</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<h2>Set tabbar hidden</h2>
|
||||
<ion-button onClick={() => this.setHidden(true)}>hidden</ion-button>
|
||||
<ion-button onClick={() => this.setHidden(false)}>visible</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<h2>Set tabbar highlight</h2>
|
||||
<ion-button onClick={() => this.setHighlight(true)}>enabled</ion-button>
|
||||
<ion-button onClick={() => this.setHighlight(false)}>disabled</ion-button>
|
||||
</p>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
<f></f>
|
||||
</ion-content>
|
||||
];
|
||||
}
|
||||
}
|
@ -1,69 +1,58 @@
|
||||
import { Component, Prop } from '@stencil/core';
|
||||
|
||||
|
||||
import { Component, Listen, Prop, State } from '@stencil/core';
|
||||
import { HTMLIonTabElement } from '../../index';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-tab-bar',
|
||||
tag: 'ion-tabbar',
|
||||
host: {
|
||||
theme: 'tabbar'
|
||||
}
|
||||
})
|
||||
export class TabBar {
|
||||
|
||||
/**
|
||||
* @input {string} The color to use from your Sass `$colors` map.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"danger"`, `"light"`, and `"dark"`.
|
||||
* For more information, see [Theming your App](/docs/theming/theming-your-app).
|
||||
*/
|
||||
@Prop() color: string;
|
||||
@State() hidden = false;
|
||||
|
||||
/**
|
||||
* @input {string} The mode determines which platform styles to use.
|
||||
* Possible values are: `"ios"`, `"md"`, or `"wp"`.
|
||||
* For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
|
||||
*/
|
||||
@Prop() mode: 'ios' | 'md' | 'wp';
|
||||
@Prop() placement = 'bottom';
|
||||
@Prop() tabs: HTMLIonTabElement[];
|
||||
@Prop() selectedTab: HTMLIonTabElement;
|
||||
@Prop() layout: string = 'icon-top';
|
||||
@Prop() highlight: boolean = false;
|
||||
|
||||
@Prop() tabs: any;
|
||||
@Listen('body:keyboardWillHide')
|
||||
protected onKeyboardWillHide() {
|
||||
setTimeout(() => this.hidden = false, 50);
|
||||
}
|
||||
|
||||
@Prop() onTabSelected: Function;
|
||||
|
||||
@Prop() selectedIndex: number = 0;
|
||||
|
||||
/**
|
||||
* @input {string} Set the tabbar layout: `icon-top`, `icon-start`, `icon-end`, `icon-bottom`, `icon-hide`, `title-hide`.
|
||||
*/
|
||||
@Prop() tabsLayout: string = 'icon-top';
|
||||
/*
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
'role': 'tablist'
|
||||
class: {
|
||||
'tabbar': true
|
||||
}
|
||||
@Listen('body:keyboardWillShow')
|
||||
protected onKeyboardWillShow() {
|
||||
if (this.placement === 'bottom') {
|
||||
this.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleTabButtonClick(tab, index) {
|
||||
this.onTabSelected && this.onTabSelected(tab, index);
|
||||
protected hostData() {
|
||||
const layoutClass = `layout-${this.layout}`;
|
||||
const placementClass = `placement-${this.placement}`;
|
||||
return {
|
||||
'role': 'tablist',
|
||||
'class': {
|
||||
'tabbar-hidden': this.hidden,
|
||||
[layoutClass]: true,
|
||||
[placementClass]: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return (
|
||||
<div class="tabbar" role="tablist">
|
||||
{this.tabs.map((tab, index) => {
|
||||
return (
|
||||
<ion-tab-button role="tab"
|
||||
tab={tab}
|
||||
selectedIndex={this.selectedIndex}
|
||||
index={index}
|
||||
onClick={this.handleTabButtonClick.bind(this, tab, index)}
|
||||
layout={this.tabsLayout}></ion-tab-button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
const selectedTab = this.selectedTab;
|
||||
const dom = this.tabs.map(tab => (
|
||||
<ion-tab-button
|
||||
tab={tab}
|
||||
selected={selectedTab === tab}>
|
||||
</ion-tab-button>
|
||||
));
|
||||
if (this.highlight) {
|
||||
dom.push(<ion-tab-highlight selectedTab={selectedTab}></ion-tab-highlight>);
|
||||
}
|
||||
return dom;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -1,78 +1,62 @@
|
||||
import { Component, Prop } from '@stencil/core';
|
||||
|
||||
import { Component, Event, EventEmitter, Listen, Prop } from '@stencil/core';
|
||||
import { HTMLIonTabElement } from '../../index';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-tab-button',
|
||||
host: {
|
||||
theme: 'tab-button'
|
||||
}
|
||||
tag: 'ion-tab-button'
|
||||
})
|
||||
export class TabButton {
|
||||
@Prop() tab: any;
|
||||
export class TabbarButton {
|
||||
|
||||
@Prop() layout: string;
|
||||
@Prop() selected: boolean = false;
|
||||
@Prop() tab: HTMLIonTabElement;
|
||||
|
||||
@Prop() selectedIndex: number;
|
||||
@Event() ionTabbarClick: EventEmitter;
|
||||
|
||||
@Prop() index: number;
|
||||
@Listen('click')
|
||||
protected onClick(ev: UIEvent) {
|
||||
this.ionTabbarClick.emit(this.tab);
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
protected hostData() {
|
||||
const selected = this.selected;
|
||||
const tab = this.tab;
|
||||
if (!tab) return {};
|
||||
|
||||
// attr.id
|
||||
// attr.aria-controls
|
||||
|
||||
const hasTitle = !!tab.tabTitle;
|
||||
const hasIcon = !!tab.tabIcon && this.layout !== 'icon-hide';
|
||||
const hasTitle = !!tab.title;
|
||||
const hasIcon = !! tab.icon;
|
||||
const hasTitleOnly = (hasTitle && !hasIcon);
|
||||
const hasIconOnly = (hasIcon && !hasTitle);
|
||||
const hasBadge = !!tab.tabBadge;
|
||||
// class.disable-hover
|
||||
// class.tab-disabled
|
||||
// class.tab-hidden
|
||||
|
||||
const hasBadge = !!tab.badge;
|
||||
return {
|
||||
'aria-selected': this.selectedIndex === this.index,
|
||||
'role': 'tab',
|
||||
'id': tab.btnId,
|
||||
'aria-selected': selected,
|
||||
class: {
|
||||
'tab-selected': selected,
|
||||
'has-title': hasTitle,
|
||||
'has-icon': hasIcon,
|
||||
'has-title-only': hasTitleOnly,
|
||||
'has-icon-only': hasIconOnly,
|
||||
'has-badge': hasBadge
|
||||
'has-badge': hasBadge,
|
||||
'tab-disabled': !tab.enabled,
|
||||
'tab-hidden': tab.hidden,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.tab) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = [];
|
||||
const tab = this.tab;
|
||||
|
||||
// TODO: Apply these on host?
|
||||
/*
|
||||
let { id, ariaControls, ariaSelected, hasTitle, hasIcon, hasTitleOnly,
|
||||
iconOnly, hasBadge, disableHover, tabDisabled, tabHidden } = {};
|
||||
*/
|
||||
|
||||
const items = [];
|
||||
|
||||
if (tab.tabIcon) {
|
||||
items.push(<ion-icon class='tab-button-icon' name={tab.tabIcon}></ion-icon>);
|
||||
if (tab.icon) {
|
||||
items.push(<ion-icon class='tab-button-icon' name={tab.icon}></ion-icon>);
|
||||
}
|
||||
|
||||
if (tab.tabTitle) {
|
||||
items.push(<span class='tab-button-text'>{tab.tabTitle}</span>);
|
||||
if (tab.title) {
|
||||
items.push(<span class='tab-button-text'>{tab.title}</span>);
|
||||
}
|
||||
|
||||
if (tab.tabBadge) {
|
||||
items.push(<ion-badge class='tab-badge' color={tab.tabBadgeStyle}>{tab.tabBadge}</ion-badge>);
|
||||
if (tab.badge) {
|
||||
items.push(<ion-badge class='tab-badge' color={tab.badgeStyle}>{tab.badge}</ion-badge>);
|
||||
}
|
||||
|
||||
items.push(<div class='button-effect'></div>);
|
||||
|
||||
return (items);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,61 @@
|
||||
import { Component } from '@stencil/core';
|
||||
|
||||
import { Component, Element, Listen, Prop, PropDidChange, State } from '@stencil/core';
|
||||
import { getParentElement } from '../../utils/helpers';
|
||||
import { HTMLIonTabButtonElement, HTMLIonTabElement } from '../../index';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-tab-highlight'
|
||||
})
|
||||
export class TabHighlight {
|
||||
protected render() {
|
||||
return (
|
||||
<div></div>
|
||||
);
|
||||
|
||||
@Element() private el: HTMLElement;
|
||||
|
||||
@State() animated = false;
|
||||
@State() transform = '';
|
||||
|
||||
@Prop() selectedTab: HTMLIonTabElement;
|
||||
@PropDidChange('selectedTab')
|
||||
selectedTabChanged() {
|
||||
this.updateTransform();
|
||||
}
|
||||
|
||||
@Listen('window:resize')
|
||||
onResize() {
|
||||
this.updateTransform();
|
||||
}
|
||||
|
||||
protected ionViewDidLoad() {
|
||||
this.updateTransform();
|
||||
}
|
||||
|
||||
protected updateTransform() {
|
||||
Context.dom.read(() => {
|
||||
const btn = this.getSelectedButton();
|
||||
this.transform = (btn)
|
||||
? `translate3d(${btn.offsetLeft}px,0,0) scaleX(${btn.offsetWidth})`
|
||||
: '';
|
||||
if (!this.animated) {
|
||||
setTimeout(() => this.animated = true, 80);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getSelectedButton(): HTMLIonTabButtonElement {
|
||||
const parent = getParentElement(this.el) as HTMLElement;
|
||||
if (!parent) {
|
||||
return null;
|
||||
}
|
||||
return Array.from(parent.querySelectorAll('ion-tab-button'))
|
||||
.find(btn => btn.selected);
|
||||
}
|
||||
|
||||
protected hostData() {
|
||||
return {
|
||||
style: {
|
||||
'transform': this.transform
|
||||
},
|
||||
class: {
|
||||
'animated': this.animated,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,108 +1,280 @@
|
||||
import { Component, Event, EventEmitter, Prop, State } from '@stencil/core';
|
||||
|
||||
import { Component, Element, Event, EventEmitter, Method, Prop, PropDidChange, State } from '@stencil/core';
|
||||
import { HTMLIonNavElement, StencilElement } from '../../index';
|
||||
import { ViewController } from '../../navigation/nav-interfaces';
|
||||
|
||||
/**
|
||||
* @name Tab
|
||||
* @description
|
||||
* The Tab component, written `<ion-tab>`, is styled based on the mode and should
|
||||
* be used in conjunction with the [Tabs](../Tabs/) component.
|
||||
*
|
||||
* Each `ion-tab` is a declarative component for a [NavController](../../../navigation/NavController/).
|
||||
* Basically, each tab is a `NavController`. For more information on using
|
||||
* navigation controllers take a look at the [NavController API Docs](../../../navigation/NavController/).
|
||||
*
|
||||
* See the [Tabs API Docs](../Tabs/) for more details on configuring Tabs.
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* To add a basic tab, you can use the following markup where the `root` property
|
||||
* is the page you want to load for that tab, `tabTitle` is the optional text to
|
||||
* display on the tab, and `tabIcon` is the optional [icon](../../icon/Icon/).
|
||||
*
|
||||
* ```html
|
||||
* <ion-tabs>
|
||||
* <ion-tab [root]="chatRoot" tabTitle="Chat" tabIcon="chat"></ion-tab>
|
||||
* </ion-tabs>
|
||||
* ```
|
||||
*
|
||||
* Then, in your class you can set `chatRoot` to an imported class:
|
||||
*
|
||||
* ```ts
|
||||
* import { ChatPage } from '../chat/chat';
|
||||
*
|
||||
* export class Tabs {
|
||||
* // here we'll set the property of chatRoot to
|
||||
* // the imported class of ChatPage
|
||||
* chatRoot = ChatPage;
|
||||
*
|
||||
* constructor() {
|
||||
*
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* You can also pass some parameters to the root page of the tab through
|
||||
* `rootParams`. Below we pass `chatParams` to the Chat tab:
|
||||
*
|
||||
* ```html
|
||||
* <ion-tabs>
|
||||
* <ion-tab [root]="chatRoot" [rootParams]="chatParams" tabTitle="Chat" tabIcon="chat"></ion-tab>
|
||||
* </ion-tabs>
|
||||
* ```
|
||||
*
|
||||
* ```ts
|
||||
* export class Tabs {
|
||||
* chatRoot = ChatPage;
|
||||
*
|
||||
* // set some user information on chatParams
|
||||
* chatParams = {
|
||||
* user1: 'admin',
|
||||
* user2: 'ionic'
|
||||
* };
|
||||
*
|
||||
* constructor() {
|
||||
*
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* And in `ChatPage` you can get the data from `NavParams`:
|
||||
*
|
||||
* ```ts
|
||||
* export class ChatPage {
|
||||
* constructor(navParams: NavParams) {
|
||||
* console.log('Passed params', navParams.data);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Sometimes you may want to call a method instead of navigating to a new
|
||||
* page. You can use the `(ionSelect)` event to call a method on your class when
|
||||
* the tab is selected. Below is an example of presenting a modal from one of
|
||||
* the tabs.
|
||||
*
|
||||
* ```html
|
||||
* <ion-tabs>
|
||||
* <ion-tab (ionSelect)="chat()" tabTitle="Show Modal"></ion-tab>
|
||||
* </ion-tabs>pop
|
||||
* ```
|
||||
*
|
||||
* ```ts
|
||||
* export class Tabs {
|
||||
* constructor(public modalCtrl: ModalController) {
|
||||
*
|
||||
* }
|
||||
*
|
||||
* chat() {
|
||||
* let modal = this.modalCtrl.create(ChatPage);
|
||||
* modal.present();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* @demo /docs/demos/src/tabs/
|
||||
* @see {@link /docs/components#tabs Tabs Component Docs}
|
||||
* @see {@link ../../tabs/Tabs Tabs API Docs}
|
||||
* @see {@link ../../nav/Nav Nav API Docs}
|
||||
* @see {@link ../../nav/NavController NavController API Docs}
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-tab',
|
||||
host: {
|
||||
theme: 'tab'
|
||||
}
|
||||
})
|
||||
export class Tab {
|
||||
|
||||
/**
|
||||
* @input {Page} Set the root component for this tab.
|
||||
*/
|
||||
@Prop() root: string;
|
||||
private nav: Promise<HTMLIonNavElement>;
|
||||
private resolveNav: (el: HTMLIonNavElement) => void;
|
||||
|
||||
@Element() el: HTMLElement;
|
||||
|
||||
@State() init = false;
|
||||
@State() active = false;
|
||||
|
||||
/**
|
||||
* @input {object} Any nav-params to pass to the root componentof this tab.
|
||||
* @input {string} Set the root page for this tab.
|
||||
*/
|
||||
@Prop() btnId: string;
|
||||
|
||||
/**
|
||||
* @input {Page} Set the root page for this tab.
|
||||
*/
|
||||
@Prop() root: any;
|
||||
|
||||
/**
|
||||
* @input {object} Any nav-params to pass to the root page of this tab.
|
||||
*/
|
||||
@Prop() rootParams: any;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, the tab is selected
|
||||
* @input {string} The URL path name to represent this tab within the URL.
|
||||
*/
|
||||
@State() isSelected: Boolean = false;
|
||||
@Prop() urlPath: string;
|
||||
|
||||
/**
|
||||
* @input {string} The title of the tab button.
|
||||
*/
|
||||
@Prop() tabTitle: string;
|
||||
@Prop() title: string;
|
||||
|
||||
/**
|
||||
* @input {string} The icon for the tab button.
|
||||
*/
|
||||
@Prop() tabIcon: string;
|
||||
@Prop() icon: string;
|
||||
|
||||
/**
|
||||
* @input {string} The badge for the tab button.
|
||||
*/
|
||||
@Prop() tabBadge: string;
|
||||
@Prop() badge: string;
|
||||
|
||||
/**
|
||||
* @input {string} The badge color for the tab button.
|
||||
*/
|
||||
@Prop() tabBadgeStyle: string;
|
||||
@Prop() badgeStyle: string = 'default';
|
||||
|
||||
/**
|
||||
* TODO why isn't this disabled like other components?
|
||||
* @input {boolean} If true, enable the tab. If false,
|
||||
* the user cannot interact with the tab.
|
||||
* the user cannot interact with this element.
|
||||
* Default: `true`.
|
||||
*/
|
||||
@Prop() enabled: boolean = true;
|
||||
@Prop() enabled = true;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, the tab button is visible within the
|
||||
* tabbar. Default: `true`.
|
||||
*/
|
||||
@Prop() shown: boolean = true;
|
||||
@Prop() show = true;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, hide the tabs on child pages.
|
||||
*/
|
||||
@Prop() tabsHideOnSubPages: boolean = false;
|
||||
@Prop() tabsHideOnSubPages = false;
|
||||
|
||||
/**
|
||||
* @input {Tab} Emitted when the current tab is selected.
|
||||
*/
|
||||
@Prop() onSelected: Function;
|
||||
|
||||
/**
|
||||
* @output {TabEvent} Emitted after the tab has loaded.
|
||||
*/
|
||||
@Event() ionTabDidLoad: EventEmitter;
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
style: {
|
||||
display: !this.isSelected && 'none' || ''
|
||||
},
|
||||
'role': 'tabpanel'
|
||||
};
|
||||
constructor() {
|
||||
this.nav = new Promise((resolve) => this.resolveNav = resolve);
|
||||
}
|
||||
|
||||
@Prop({ mutable: true }) selected = false;
|
||||
@PropDidChange('selected')
|
||||
selectedChanged(selected: boolean) {
|
||||
if (selected) {
|
||||
this.ionSelect.emit(this.el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @output {Tab} Emitted when the current tab is selected.
|
||||
*/
|
||||
@Event() ionSelect: EventEmitter;
|
||||
@Event() ionTabDidLoad: EventEmitter;
|
||||
@Event() ionTabDidUnload: EventEmitter;
|
||||
|
||||
protected ionViewDidLoad() {
|
||||
setTimeout(() => {
|
||||
this.ionTabDidLoad.emit({ tab: this });
|
||||
}, 0);
|
||||
this.ionTabDidLoad.emit(this.el);
|
||||
}
|
||||
|
||||
protected ionViewDidUnload() {
|
||||
this.ionTabDidLoad.emit({ tab: this });
|
||||
this.ionTabDidUnload.emit(this.el);
|
||||
}
|
||||
|
||||
protected componentDidUpdate() {
|
||||
if (this.init && this.resolveNav) {
|
||||
const nav = this.el.querySelector('ion-nav') as any as StencilElement;
|
||||
nav.componentOnReady(this.resolveNav);
|
||||
this.resolveNav = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Method()
|
||||
_setActive(active: boolean): Promise<any> {
|
||||
if (this.active === active) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.active = active;
|
||||
this.selected = active;
|
||||
|
||||
const needLifecycle = this.init;
|
||||
if (active) {
|
||||
this.init = true;
|
||||
if (!needLifecycle) {
|
||||
return this.nav.then(nav => nav.setRoot(this.root, this.rootParams));
|
||||
}
|
||||
}
|
||||
if (needLifecycle) {
|
||||
if (active) {
|
||||
// lifecycle didEnter
|
||||
} else {
|
||||
// lifecycle didLeave
|
||||
}
|
||||
}
|
||||
return this.nav;
|
||||
}
|
||||
|
||||
@Method()
|
||||
resize() {
|
||||
this.nav.then(nav => nav.resize());
|
||||
}
|
||||
|
||||
@Method()
|
||||
goToRoot(opts: any = {}) {
|
||||
return this.nav.then(nav => nav.setRoot(this.root, this.rootParams, opts));
|
||||
}
|
||||
|
||||
@Method()
|
||||
getActive(): Promise<ViewController> {
|
||||
return this.nav.then(nav => nav.getActive());
|
||||
}
|
||||
|
||||
@Method()
|
||||
getNav(): Promise<HTMLIonNavElement> {
|
||||
return this.nav;
|
||||
}
|
||||
|
||||
protected hostData() {
|
||||
const visible = this.active && this.selected;
|
||||
return {
|
||||
'aria-hidden': !visible ? 'true' : null,
|
||||
'aria-labelledby': this.btnId,
|
||||
'role': 'tabpanel',
|
||||
class: {
|
||||
'show-tab': this.active
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const RootComponent = this.root;
|
||||
return [
|
||||
<RootComponent />,
|
||||
<div class='nav-decor'></div>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export interface TabEvent extends Event {
|
||||
detail: {
|
||||
tab: Tab
|
||||
if (this.init) {
|
||||
return <ion-nav></ion-nav>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ $tabs-ios-tab-padding-start: $tabs-ios-tab-padding-end !default;
|
||||
$tabs-ios-tab-max-width: 240px !default;
|
||||
|
||||
/// @prop - Minimum height of the tab button
|
||||
$tabs-ios-tab-min-height: 49px !default;
|
||||
$tabs-ios-tab-height: 50px !default;
|
||||
|
||||
/// @prop - Text color of the inactive tab button
|
||||
$tabs-ios-tab-text-color: $tabs-ios-tab-color-inactive !default;
|
||||
@ -44,120 +44,124 @@ $tabs-ios-tab-font-size: 10px !default;
|
||||
$tabs-ios-tab-icon-size: 30px !default;
|
||||
|
||||
|
||||
.tabs-ios .tabbar {
|
||||
.tabbar-ios {
|
||||
justify-content: center;
|
||||
|
||||
height: $tabs-ios-tab-height;
|
||||
|
||||
border-top: $tabs-ios-border;
|
||||
background: $tabs-ios-background;
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.tabs-ios[tabsPlacement=top] .tabbar {
|
||||
.tabbar-ios.placement-top {
|
||||
border-top: 0;
|
||||
border-bottom: $tabs-ios-border;
|
||||
}
|
||||
|
||||
.tabs-ios .tab-button {
|
||||
.tabbar-ios > ion-tab-highlight {
|
||||
background: $tabs-ios-tab-color-active;
|
||||
}
|
||||
|
||||
.tabbar-ios > ion-tab-button {
|
||||
@include padding($tabs-ios-tab-padding-top, $tabs-ios-tab-padding-end, $tabs-ios-tab-padding-bottom, $tabs-ios-tab-padding-start);
|
||||
|
||||
max-width: $tabs-ios-tab-max-width;
|
||||
min-height: $tabs-ios-tab-min-height;
|
||||
min-height: 100%;
|
||||
|
||||
font-size: $tabs-ios-tab-font-size;
|
||||
color: $tabs-ios-tab-text-color;
|
||||
|
||||
fill: $tabs-ios-tab-icon-color;
|
||||
}
|
||||
|
||||
.tabs-ios .tab-button:hover:not(.disable-hover),
|
||||
.tabs-ios .tab-button[aria-selected=true] {
|
||||
.tabbar-ios ion-tab-button:hover:not(.disable-hover),
|
||||
.tabbar-ios .tab-selected {
|
||||
color: $tabs-ios-tab-text-color-active;
|
||||
|
||||
fill: $tabs-ios-tab-icon-color-active;
|
||||
}
|
||||
|
||||
.tabs-ios .tab-button[aria-selected=true] .tab-button-icon {
|
||||
color: $tabs-ios-tab-icon-color-active;
|
||||
}
|
||||
|
||||
.tabs-ios .tab-button-text {
|
||||
.tabbar-ios .tab-button-text {
|
||||
@include margin(0, null, 1px, null);
|
||||
|
||||
min-height: $tabs-ios-tab-font-size + 1;
|
||||
}
|
||||
|
||||
.tabs-ios .has-title-only .tab-button-text {
|
||||
.tabbar-ios .has-title-only .tab-button-text {
|
||||
font-size: $tabs-ios-tab-font-size + 2;
|
||||
}
|
||||
|
||||
.tabs-ios .tab-button-icon {
|
||||
.tabbar-ios .tab-button-icon {
|
||||
@include margin(4px, null, 1px, null);
|
||||
|
||||
min-width: $tabs-ios-tab-icon-size + 5;
|
||||
height: $tabs-ios-tab-icon-size;
|
||||
|
||||
font-size: $tabs-ios-tab-icon-size;
|
||||
|
||||
color: $tabs-ios-tab-icon-color;
|
||||
}
|
||||
|
||||
.tabs-ios .tab-button-icon::before {
|
||||
.tabbar-ios .tab-button-icon::before {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tabs-ios[tabsLayout=icon-end] .tab-button .tab-button-text,
|
||||
.tabs-ios[tabsLayout=icon-start] .tab-button .tab-button-text {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.tabbar-ios.layout-icon-end .tab-button-text,
|
||||
.tabbar-ios.layout-icon-start .tab-button-text,
|
||||
.tabbar-ios.layout-icon-hide .tab-button-text,
|
||||
.tabbar-ios .has-title-only .tab-button-text {
|
||||
|
||||
.tabs-ios[tabsLayout=icon-end] .tab-button ion-icon,
|
||||
.tabs-ios[tabsLayout=icon-start] .tab-button ion-icon {
|
||||
min-width: 24px;
|
||||
height: 26px;
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.tabs-ios[tabsLayout=icon-hide] .tab-button,
|
||||
.tabs-ios .tab-button.has-title-only {
|
||||
min-height: $tabs-ios-tab-min-height - 8;
|
||||
}
|
||||
|
||||
.tabs-ios[tabsLayout=icon-hide] .tab-button .tab-button-text,
|
||||
.tabs-ios .tab-button.has-title-only .tab-button-text {
|
||||
@include margin(2px, 0);
|
||||
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.tabs-ios[tabsLayout=title-hide] .tab-button,
|
||||
.tabs-ios .tab-button.icon-only {
|
||||
min-height: $tabs-ios-tab-min-height - 8;
|
||||
.tabbar-ios.layout-icon-end ion-icon,
|
||||
.tabbar-ios.layout-icon-start ion-icon {
|
||||
@include margin(2px, null, 1px, null);
|
||||
|
||||
min-width: 24px;
|
||||
height: 26px;
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.tabbar-ios.layout-title-hide ion-icon {
|
||||
@include margin(0);
|
||||
}
|
||||
// .tabs-ios[tabsLayout=icon-hide] ion-tab-button,
|
||||
// .tabs-ios ion-tab-button.has-title-only {
|
||||
// min-height: $tabs-ios-tab-min-height - 8;
|
||||
// }
|
||||
|
||||
// .tabs-ios[tabsLayout=title-hide] ion-tab-button,
|
||||
// .tabs-ios ion-tab-button.icon-only {
|
||||
// min-height: $tabs-ios-tab-min-height - 8;
|
||||
// }
|
||||
|
||||
|
||||
// iOS Tabbar Color Mixin
|
||||
// --------------------------------------------------
|
||||
|
||||
@mixin tabbar-ios($color-name, $color-base, $color-contrast) {
|
||||
|
||||
.tabs-ios-#{$color-name} .tabbar {
|
||||
.tabbar-ios-#{$color-name} {
|
||||
border-color: darken($color-base, 10%);
|
||||
background-color: $color-base;
|
||||
}
|
||||
|
||||
.tabs-ios-#{$color-name} .tab-button,
|
||||
.tabs-ios-#{$color-name} .tab-button-icon,
|
||||
.tabs-ios-#{$color-name} .tab-button:hover:not(.disable-hover),
|
||||
.tabs-ios-#{$color-name} .tab-button:hover:not(.disable-hover) .tab-button-icon {
|
||||
color: rgba($color-contrast, .7);
|
||||
background-color: $color-base;
|
||||
|
||||
fill: rgba($color-contrast, .7);
|
||||
}
|
||||
|
||||
.tabs-ios-#{$color-name} .tab-button[aria-selected=true],
|
||||
.tabs-ios-#{$color-name} .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-ios-#{$color-name} .tab-selected {
|
||||
color: $color-contrast;
|
||||
|
||||
fill: $color-contrast;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// iOS Tabbar Color Generation
|
||||
// --------------------------------------------------
|
||||
|
||||
|
@ -6,19 +6,19 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Padding top on the tab button
|
||||
$tabs-md-tab-padding-top: 0 !default;
|
||||
$tabs-md-tab-padding-top: .8rem !default;
|
||||
|
||||
/// @prop - Padding end on the tab button
|
||||
$tabs-md-tab-padding-end: $tabs-md-tab-padding-top !default;
|
||||
$tabs-md-tab-padding-end: 1.2rem !default;
|
||||
|
||||
/// @prop - Padding bottom on the tab button
|
||||
$tabs-md-tab-padding-bottom: $tabs-md-tab-padding-top !default;
|
||||
$tabs-md-tab-padding-bottom: 1rem !default;
|
||||
|
||||
/// @prop - Padding start on the tab button
|
||||
$tabs-md-tab-padding-start: $tabs-md-tab-padding-end !default;
|
||||
$tabs-md-tab-padding-start: 1.2rem, !default;
|
||||
|
||||
/// @prop - Minimum height of the tab button
|
||||
$tabs-md-tab-min-height: 5.6rem !default;
|
||||
$tabs-md-tab-height: 5.6rem !default;
|
||||
|
||||
/// @prop - Font size of the inactive tab button text
|
||||
$tabs-md-tab-font-size: 1.2rem !default;
|
||||
@ -41,18 +41,6 @@ $tabs-md-tab-icon-color: rgba($tabs-md-tab-color-inactive,
|
||||
/// @prop - Icon color of the active tab button
|
||||
$tabs-md-tab-icon-color-active: $tabs-md-tab-color-active !default;
|
||||
|
||||
/// @prop - Padding top of the active tab button
|
||||
$tabs-md-tab-padding-active-top: 0 !default;
|
||||
|
||||
/// @prop - Padding end of the active tab button
|
||||
$tabs-md-tab-padding-active-end: $tabs-md-tab-padding-active-top !default;
|
||||
|
||||
/// @prop - Padding bottom of the active tab button
|
||||
$tabs-md-tab-padding-active-bottom: $tabs-md-tab-padding-active-top !default;
|
||||
|
||||
/// @prop - Padding start of the active tab button
|
||||
$tabs-md-tab-padding-active-start: $tabs-md-tab-padding-active-end !default;
|
||||
|
||||
/// @prop - Font size of the active tab button text
|
||||
$tabs-md-tab-font-size-active: 1.4rem !default;
|
||||
|
||||
@ -71,12 +59,6 @@ $tabs-md-tab-text-margin-start: $tabs-md-tab-text-margin-end !defa
|
||||
/// @prop - Capitalization of the tab button text
|
||||
$tabs-md-tab-text-capitalization: none !default;
|
||||
|
||||
/// @prop - Transform origin x for the tab button text
|
||||
$tabs-md-tab-text-transform-origin-x: 50% !default;
|
||||
|
||||
/// @prop - Transform origin y for the tab button text
|
||||
$tabs-md-tab-text-transform-origin-y: 80% !default;
|
||||
|
||||
/// @prop - Transform for the active tab button text
|
||||
$tabs-md-tab-text-transform-active: scale3d($tabs-md-tab-font-size-active / $tabs-md-tab-font-size, $tabs-md-tab-font-size-active / $tabs-md-tab-font-size, 1) !default;
|
||||
|
||||
@ -105,13 +87,13 @@ $tabs-md-tab-icon-right-transform-z-active: 0 !default;
|
||||
$tabs-md-tab-icon-bottom-transform-x-active: 0 !default;
|
||||
|
||||
/// @prop - Transform y for the active tab button icon when the layout is icon-bottom
|
||||
$tabs-md-tab-icon-bottom-transform-y-active: 2px !default;
|
||||
$tabs-md-tab-icon-bottom-transform-y-active: 0.2rem !default;
|
||||
|
||||
/// @prop - Transform z for the active tab button icon when the layout is icon-bottom
|
||||
$tabs-md-tab-icon-bottom-transform-z-active: 0 !default;
|
||||
|
||||
/// @prop - Transform x for the active tab button icon when the layout is icon-left
|
||||
$tabs-md-tab-icon-left-transform-x-active: -2px !default;
|
||||
$tabs-md-tab-icon-left-transform-x-active: -0.2rem !default;
|
||||
|
||||
/// @prop - Transform y for the active tab button icon when the layout is icon-left
|
||||
$tabs-md-tab-icon-left-transform-y-active: 0 !default;
|
||||
@ -119,50 +101,61 @@ $tabs-md-tab-icon-left-transform-y-active: 0 !default;
|
||||
/// @prop - Transform z for the active tab button icon when the layout is icon-left
|
||||
$tabs-md-tab-icon-left-transform-z-active: 0 !default;
|
||||
|
||||
/// @prop - Transform origin x for the tab button text
|
||||
$tabs-md-tab-icon-transform-origin-x: 50% !default;
|
||||
|
||||
/// @prop - Transform origin y for the tab button text
|
||||
$tabs-md-tab-icon-transform-origin-y: 150% !default;
|
||||
|
||||
/// @prop - Text transition for the tab button icon
|
||||
$tabs-md-tab-icon-transition: transform .3s ease-in-out !default;
|
||||
|
||||
/// @prop - Size of the tab button icon
|
||||
$tabs-md-tab-icon-size: 2.4rem !default;
|
||||
|
||||
.tabbar-md {
|
||||
height: $tabs-md-tab-height;
|
||||
|
||||
.tabs-md .tabbar {
|
||||
border-top: 1px solid $tabs-md-border-color;
|
||||
background: $tabs-md-background;
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.tabbar-md > ion-tab-highlight {
|
||||
background: $tabs-md-tab-color-active;
|
||||
}
|
||||
|
||||
// Material Design Tab Button
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabs-md .tab-button {
|
||||
.tabbar-md ion-tab-button {
|
||||
@include padding($tabs-md-tab-padding-top, $tabs-md-tab-padding-end, $tabs-md-tab-padding-bottom, $tabs-md-tab-padding-start);
|
||||
|
||||
min-height: $tabs-md-tab-min-height;
|
||||
// min-height: $tabs-md-tab-min-height;
|
||||
display: flex;
|
||||
max-width: 168px;
|
||||
// min-width: 80px;
|
||||
// Maximum: 168dp
|
||||
// Minimum: 96dp
|
||||
// max-width: 96px;
|
||||
// min-width: 56px;
|
||||
height: 100%;
|
||||
|
||||
font-weight: $tabs-md-tab-font-weight;
|
||||
color: $tabs-md-tab-text-color;
|
||||
|
||||
fill: $tabs-md-tab-icon-color;
|
||||
}
|
||||
|
||||
.tabs-md .tab-button[aria-selected=true] {
|
||||
@include padding($tabs-md-tab-padding-active-top, $tabs-md-tab-padding-active-end, $tabs-md-tab-padding-active-bottom, $tabs-md-tab-padding-active-start);
|
||||
|
||||
.tabbar-md .tab-selected {
|
||||
color: $tabs-md-tab-text-color-active;
|
||||
|
||||
fill: $tabs-md-tab-icon-color-active;
|
||||
}
|
||||
|
||||
|
||||
// Material Design Tab Button Text
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabs-md .tab-button-text {
|
||||
.tabbar-md .tab-button-text {
|
||||
@include margin($tabs-md-tab-text-margin-top, $tabs-md-tab-text-margin-end, $tabs-md-tab-text-margin-bottom, $tabs-md-tab-text-margin-start);
|
||||
|
||||
@include transform-origin($tabs-md-tab-text-transform-origin-x, $tabs-md-tab-text-transform-origin-y);
|
||||
@include transform-origin(center, bottom);
|
||||
|
||||
font-size: $tabs-md-tab-font-size;
|
||||
|
||||
@ -170,56 +163,53 @@ $tabs-md-tab-icon-size: 2.4rem !default;
|
||||
transition: $tabs-md-tab-text-transition;
|
||||
}
|
||||
|
||||
.tabs-md .tab-button[aria-selected=true] .tab-button-text {
|
||||
.tabbar-md .tab-selected .tab-button-text {
|
||||
transform: $tabs-md-tab-text-transform-active;
|
||||
transition: $tabs-md-tab-text-transition;
|
||||
}
|
||||
|
||||
.tabs-md[tabsLayout=icon-top] .has-icon .tab-button-text {
|
||||
@include margin(4px, null, 0, null);
|
||||
.tabbar-md.layout-icon-top .has-icon .tab-button-text {
|
||||
@include margin(null, null, -.2rem, null);
|
||||
}
|
||||
|
||||
.tabs-md[tabsLayout=icon-bottom] .tab-button .tab-button-text {
|
||||
@include margin(0, null, null, null);
|
||||
.tabbar-md.layout-icon-bottom .tab-button-text {
|
||||
@include transform-origin(center, top);
|
||||
|
||||
@include margin(-.2rem, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
// Material Design Tab Button Icon
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabs-md .tab-button-icon {
|
||||
@include margin(1px, null, null, null);
|
||||
.tabbar-md .tab-button-icon {
|
||||
@include transform-origin(center, center);
|
||||
|
||||
@include transform-origin($tabs-md-tab-icon-transform-origin-x, $tabs-md-tab-icon-transform-origin-y);
|
||||
|
||||
min-width: $tabs-md-tab-icon-size;
|
||||
width: $tabs-md-tab-icon-size;
|
||||
height: $tabs-md-tab-icon-size;
|
||||
|
||||
font-size: $tabs-md-tab-icon-size;
|
||||
|
||||
color: $tabs-md-tab-icon-color;
|
||||
|
||||
transition: $tabs-md-tab-icon-transition;
|
||||
}
|
||||
|
||||
// Tab layout: icon-top, icon-only, title-only
|
||||
.tabs-md .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-md .tab-selected .tab-button-icon {
|
||||
@include transform(translate3d($tabs-md-tab-icon-transform-x-active, $tabs-md-tab-icon-transform-y-active, $tabs-md-tab-icon-transform-z-active));
|
||||
|
||||
color: $tabs-md-tab-icon-color-active;
|
||||
}
|
||||
|
||||
// Tab layout: icon-end
|
||||
.tabs-md[tabsLayout=icon-end] .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-md.layout-icon-end .tab-selected .tab-button-icon {
|
||||
@include transform(translate3d($tabs-md-tab-icon-right-transform-x-active, $tabs-md-tab-icon-right-transform-y-active, $tabs-md-tab-icon-right-transform-z-active));
|
||||
}
|
||||
|
||||
// Tab layout: icon-bottom
|
||||
.tabs-md[tabsLayout=icon-bottom] .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-md.layout-icon-bottom .tab-selected .tab-button-icon {
|
||||
@include transform(translate3d($tabs-md-tab-icon-bottom-transform-x-active, $tabs-md-tab-icon-bottom-transform-y-active, $tabs-md-tab-icon-bottom-transform-z-active));
|
||||
}
|
||||
|
||||
// Tab layout: icon-start
|
||||
.tabs-md[tabsLayout=icon-start] .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-md.layout-icon-start .tab-selected .tab-button-icon {
|
||||
@include transform(translate3d($tabs-md-tab-icon-left-transform-x-active, $tabs-md-tab-icon-left-transform-y-active, $tabs-md-tab-icon-left-transform-z-active));
|
||||
}
|
||||
|
||||
@ -227,62 +217,33 @@ $tabs-md-tab-icon-size: 2.4rem !default;
|
||||
// Material Design Tab with Icon or Title only
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabs-md[tabsLayout=icon-hide] .tab-button,
|
||||
.tabs-md[tabsLayout=title-hide] .tab-button,
|
||||
.tabs-md .tab-button.icon-only,
|
||||
.tabs-md .tab-button.has-title-only {
|
||||
@include padding(0, 10px);
|
||||
.tabbar-md.layout-icon-hide ion-tab-button,
|
||||
.tabbar-md.layout-title-hide ion-tab-button,
|
||||
.tabbar-md ion-tab-button.icon-only,
|
||||
.tabbar-md ion-tab-button.has-title-only {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
// Material Design Tab Highlight
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabs-md[tabsHighlight=true] .tab-highlight {
|
||||
@include position(null, null, 0, 0);
|
||||
@include transform-origin(0, 0);
|
||||
|
||||
position: absolute;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 1px;
|
||||
height: 2px;
|
||||
|
||||
background: $tabs-md-tab-color-active;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.tabs-md[tabsHighlight=true] .tab-highlight.animate {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.tabs-md[tabsHighlight=true][tabsPlacement=bottom] > .tabbar > .tab-highlight {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
|
||||
// Material Design Tabs Color Mixin
|
||||
// --------------------------------------------------
|
||||
|
||||
@mixin tabbar-md($color-name, $color-base, $color-contrast) {
|
||||
|
||||
.tabs-md-#{$color-name} .tabbar {
|
||||
background-color: $color-base;
|
||||
}
|
||||
|
||||
.tabs-md-#{$color-name} .tab-button,
|
||||
.tabs-md-#{$color-name} .tab-button-icon {
|
||||
.tabbar-md-#{$color-name} {
|
||||
color: rgba($color-contrast, $tabs-md-tab-opacity);
|
||||
background-color: $color-base;
|
||||
|
||||
fill: rgba($color-contrast, $tabs-md-tab-opacity);
|
||||
}
|
||||
|
||||
.tabs-md-#{$color-name} .tab-button:hover:not(.disable-hover),
|
||||
.tabs-md-#{$color-name} .tab-button[aria-selected=true],
|
||||
.tabs-md-#{$color-name} .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-md-#{$color-name} ion-tab-button:hover:not(.disable-hover),
|
||||
.tabbar-md-#{$color-name} ion-tab-button.tab-selected {
|
||||
color: $color-contrast;
|
||||
|
||||
fill: $color-contrast;
|
||||
}
|
||||
|
||||
.tabs-md-#{$color-name}[tabsHighlight=true] .tab-highlight {
|
||||
.tabbar-md-#{$color-name} ion-tab-highlight {
|
||||
background: $color-contrast;
|
||||
}
|
||||
|
||||
|
@ -1,36 +1,30 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Tabs
|
||||
// Tabbar
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabbar {
|
||||
@include position(null, null, 0, 0);
|
||||
|
||||
position: absolute;
|
||||
ion-tabbar {
|
||||
position: relative;
|
||||
|
||||
z-index: $z-index-toolbar;
|
||||
display: flex;
|
||||
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
|
||||
// default to hidden until ready
|
||||
//opacity: 0;
|
||||
order: 1;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tabbar-hidden .tabbar {
|
||||
ion-tabbar.tabbar-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabbar.show-tabbar {
|
||||
opacity: 1;
|
||||
ion-tabbar.placement-top {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
[tabsPlacement=top] > .tabbar {
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
ion-tab-button {
|
||||
@include margin(0);
|
||||
@include text-align(center);
|
||||
@include border-radius(0);
|
||||
@ -43,8 +37,7 @@
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
|
||||
border: 0;
|
||||
|
||||
@ -59,19 +52,13 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tab-disabled ion-badge,
|
||||
.tab-disabled ion-icon,
|
||||
.tab-disabled span {
|
||||
.tab-disabled > * {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
.tab-button-text {
|
||||
@include margin(3px, null, 2px, null);
|
||||
}
|
||||
|
||||
.tab-button-text,
|
||||
.tab-button-icon {
|
||||
//display: none;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
|
||||
align-self: center;
|
||||
@ -92,31 +79,28 @@
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
[tabsLayout=icon-bottom] .tab-button .tab-button-icon {
|
||||
order: 10;
|
||||
}
|
||||
|
||||
[tabsLayout=icon-start] .tab-button,
|
||||
[tabsLayout=icon-end] .tab-button {
|
||||
.layout-icon-start ion-tab-button {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
[tabsLayout=icon-start] .tab-button .tab-button-icon {
|
||||
@include padding-horizontal(null, 8px);
|
||||
@include text-align(end);
|
||||
.layout-icon-end ion-tab-button {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
[tabsLayout=icon-end] .tab-button .tab-button-icon {
|
||||
@include padding-horizontal(8px, null);
|
||||
@include text-align(start);
|
||||
.layout-icon-bottom ion-tab-button {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
order: 10;
|
||||
.layout-icon-start ion-tab-button,
|
||||
.layout-icon-end ion-tab-button,
|
||||
.layout-icon-hide ion-tab-button,
|
||||
.layout-title-hide ion-tab-button {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-hidden,
|
||||
.tab-highlight,
|
||||
[tabsLayout=icon-hide] .tab-button-icon,
|
||||
[tabsLayout=title-hide] .tab-button-text {
|
||||
.layout-icon-hide .tab-button-icon,
|
||||
.layout-title-hide .tab-button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -140,8 +124,43 @@
|
||||
@include position(null, calc(50% - 30px), null, null);
|
||||
}
|
||||
|
||||
[tabsLayout=icon-bottom] .tab-badge,
|
||||
[tabsLayout=icon-start] .tab-badge,
|
||||
[tabsLayout=icon-end] .tab-badge {
|
||||
.layout-icon-bottom .tab-badge,
|
||||
.layout-icon-start .tab-badge,
|
||||
.layout-icon-end .tab-badge {
|
||||
@include position(null, calc(50% - 50px), null, null);
|
||||
}
|
||||
|
||||
|
||||
// Tab Highlight
|
||||
// --------------------------------------------------
|
||||
|
||||
ion-tab-highlight {
|
||||
@include position(null, null, 0, 0);
|
||||
@include transform-origin(0, 0);
|
||||
|
||||
position: absolute;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 1px;
|
||||
height: 2px;
|
||||
|
||||
transform: translateZ(0);
|
||||
|
||||
&.animated {
|
||||
transition-duration: 300ms;
|
||||
transition-property: transform;
|
||||
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
|
||||
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
|
||||
.placement-top > ion-tab-highlight {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.placement-bottom > ion-tab-highlight {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
|
@ -1,97 +1,386 @@
|
||||
import { Component, Listen, Prop, PropDidChange, State } from '@stencil/core';
|
||||
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
|
||||
import { Config, HTMLIonTabElement } from '../../index';
|
||||
|
||||
import { TabEvent } from './tab';
|
||||
export interface NavOptions { }
|
||||
// import { isPresent } from '../../utils/helpers';
|
||||
|
||||
/**
|
||||
* @name Tabs
|
||||
* @description
|
||||
* Tabs make it easy to navigate between different pages or functional
|
||||
* aspects of an app. The Tabs component, written as `<ion-tabs>`, is
|
||||
* a container of individual [Tab](../Tab/) components. Each individual `ion-tab`
|
||||
* is a declarative component for a [NavController](../../../navigation/NavController/)
|
||||
*
|
||||
* For more information on using nav controllers like Tab or [Nav](../../nav/Nav/),
|
||||
* take a look at the [NavController API Docs](../../../navigation/NavController/).
|
||||
*
|
||||
* ### Placement
|
||||
*
|
||||
* The position of the tabs relative to the content varies based on
|
||||
* the mode. The tabs are placed at the bottom of the screen
|
||||
* for iOS and Android, and at the top for Windows by default. The position can
|
||||
* be configured using the `tabsPlacement` attribute on the `<ion-tabs>` component,
|
||||
* or in an app's [config](../../config/Config/).
|
||||
* See the [Input Properties](#input-properties) below for the available
|
||||
* values of `tabsPlacement`.
|
||||
*
|
||||
* ### Layout
|
||||
*
|
||||
* The layout for all of the tabs can be defined using the `tabsLayout`
|
||||
* property. If the individual tab has a title and icon, the icons will
|
||||
* show on top of the title by default. All tabs can be changed by setting
|
||||
* the value of `tabsLayout` on the `<ion-tabs>` element, or in your
|
||||
* app's [config](../../config/Config/). For example, this is useful if
|
||||
* you want to show tabs with a title only on Android, but show icons
|
||||
* and a title for iOS. See the [Input Properties](#input-properties)
|
||||
* below for the available values of `tabsLayout`.
|
||||
*
|
||||
* ### Selecting a Tab
|
||||
*
|
||||
* There are different ways you can select a specific tab from the tabs
|
||||
* component. You can use the `selectedIndex` property to set the index
|
||||
* on the `<ion-tabs>` element, or you can call `select()` from the `Tabs`
|
||||
* instance after creation. See [usage](#usage) below for more information.
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* You can add a basic tabs template to a `@Component` using the following
|
||||
* template:
|
||||
*
|
||||
* ```html
|
||||
* <ion-tabs>
|
||||
* <ion-tab [root]="tab1Root"></ion-tab>
|
||||
* <ion-tab [root]="tab2Root"></ion-tab>
|
||||
* <ion-tab [root]="tab3Root"></ion-tab>
|
||||
* </ion-tabs>
|
||||
* ```
|
||||
*
|
||||
* Where `tab1Root`, `tab2Root`, and `tab3Root` are each a page:
|
||||
*
|
||||
*```ts
|
||||
* @Component({
|
||||
* templateUrl: 'build/pages/tabs/tabs.html'
|
||||
* })
|
||||
* export class TabsPage {
|
||||
* // this tells the tabs component which Pages
|
||||
* // should be each tab's root Page
|
||||
* tab1Root = Page1;
|
||||
* tab2Root = Page2;
|
||||
* tab3Root = Page3;
|
||||
*
|
||||
* constructor() {
|
||||
*
|
||||
* }
|
||||
* }
|
||||
*```
|
||||
*
|
||||
* By default, the first tab will be selected upon navigation to the
|
||||
* Tabs page. We can change the selected tab by using `selectedIndex`
|
||||
* on the `<ion-tabs>` element:
|
||||
*
|
||||
* ```html
|
||||
* <ion-tabs selectedIndex="2">
|
||||
* <ion-tab [root]="tab1Root"></ion-tab>
|
||||
* <ion-tab [root]="tab2Root"></ion-tab>
|
||||
* <ion-tab [root]="tab3Root"></ion-tab>
|
||||
* </ion-tabs>
|
||||
* ```
|
||||
*
|
||||
* Since the index starts at `0`, this will select the 3rd tab which has
|
||||
* root set to `tab3Root`. If you wanted to change it dynamically from
|
||||
* your class, you could use [property binding](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#property-binding).
|
||||
*
|
||||
* Alternatively, you can grab the `Tabs` instance and call the `select()`
|
||||
* method. This requires the `<ion-tabs>` element to have an `id`. For
|
||||
* example, set the value of `id` to `myTabs`:
|
||||
*
|
||||
* ```html
|
||||
* <ion-tabs #myTabs>
|
||||
* <ion-tab [root]="tab1Root"></ion-tab>
|
||||
* <ion-tab [root]="tab2Root"></ion-tab>
|
||||
* <ion-tab [root]="tab3Root"></ion-tab>
|
||||
* </ion-tabs>
|
||||
* ```
|
||||
*
|
||||
* Then in your class you can grab the `Tabs` instance and call `select()`,
|
||||
* passing the index of the tab as the argument. Here we're grabbing the tabs
|
||||
* by using ViewChild.
|
||||
*
|
||||
*```ts
|
||||
* export class TabsPage {
|
||||
*
|
||||
* @ViewChild('myTabs') tabRef: Tabs;
|
||||
*
|
||||
* ionViewDidEnter() {
|
||||
* this.tabRef.select(2);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*```
|
||||
*
|
||||
* You can also switch tabs from a child component by calling `select()` on the
|
||||
* parent view using the `NavController` instance. For example, assuming you have
|
||||
* a `TabsPage` component, you could call the following from any of the child
|
||||
* components to switch to `TabsRoot3`:
|
||||
*
|
||||
*```ts
|
||||
* switchTabs() {
|
||||
* this.navCtrl.parent.select(2);
|
||||
* }
|
||||
*```
|
||||
* @demo /docs/demos/src/tabs/
|
||||
*
|
||||
* @see {@link /docs/components#tabs Tabs Component Docs}
|
||||
* @see {@link ../Tab Tab API Docs}
|
||||
* @see {@link ../../config/Config Config API Docs}
|
||||
*
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-tabs',
|
||||
styleUrls: {
|
||||
ios: 'tabs.ios.scss',
|
||||
md: 'tabs.md.scss',
|
||||
wp: 'tabs.wp.scss'
|
||||
},
|
||||
host: {
|
||||
theme: 'tabs'
|
||||
}
|
||||
})
|
||||
export class Tabs {
|
||||
// Current list of tabs
|
||||
@State() tabs: any;
|
||||
|
||||
private ids: number = -1;
|
||||
private tabsId: number = (++tabIds);
|
||||
private selectHistory: string[] = [];
|
||||
|
||||
@Element() el: HTMLElement;
|
||||
|
||||
@State() tabs: HTMLIonTabElement[] = [];
|
||||
@State() selectedTab: HTMLIonTabElement;
|
||||
|
||||
@Prop({ context: 'config' }) config: Config;
|
||||
|
||||
/**
|
||||
* @state {number} The selected tab
|
||||
* @input {string} A unique name for the tabs
|
||||
*/
|
||||
@State() selectedTab: any;
|
||||
@Prop() name: string;
|
||||
|
||||
/**
|
||||
* @state {number} The selected tab index
|
||||
* @input {boolean} If true, the tabbar
|
||||
*/
|
||||
@State() selectedIndex: number = 0;
|
||||
@Prop() tabbarHidden = false;
|
||||
|
||||
/**
|
||||
* @input {string} Set the tabbar layout: `icon-top`, `icon-start`, `icon-end`, `icon-bottom`, `icon-hide`, `title-hide`.
|
||||
*/
|
||||
@Prop() tabsLayout: string = 'icon-top';
|
||||
@Prop({ mutable: true }) tabbarLayout: string;
|
||||
|
||||
/**
|
||||
* @input {string} Set position of the tabbar: `top`, `bottom`.
|
||||
*/
|
||||
@Prop() tabsPlacement: string = 'bottom';
|
||||
@Prop({ mutable: true }) tabbarPlacement: string;
|
||||
|
||||
/**
|
||||
* @input {boolean} If true, show the tab highlight bar under the selected tab.
|
||||
*/
|
||||
@Prop() tabsHighlight: boolean = false;
|
||||
@Prop({ mutable: true }) tabbarHighlight: boolean;
|
||||
|
||||
/**
|
||||
* @output {Event} Emitted when the tab changes.
|
||||
* @output {any} Emitted when the tab changes.
|
||||
*/
|
||||
@Prop() ionChange: Function;
|
||||
@Event() ionChange: EventEmitter;
|
||||
|
||||
/**
|
||||
* If selectedIndex was changed, grab the reference to the tab it points to.
|
||||
*/
|
||||
@PropDidChange('selectedIndex')
|
||||
protected selectedIndexChanged() {
|
||||
this.selectedTab = this.tabs[this.selectedIndex];
|
||||
protected ionViewDidLoad() {
|
||||
this.loadConfig('tabsPlacement', 'bottom');
|
||||
this.loadConfig('tabsLayout', 'icon-top');
|
||||
this.loadConfig('tabsHighlight', true);
|
||||
|
||||
// TODO: handle navigation parent
|
||||
// if (this.parent) {
|
||||
// // this Tabs has a parent Nav
|
||||
// this.parent.registerChildNav(this);
|
||||
|
||||
// } else if (viewCtrl && viewCtrl.getNav()) {
|
||||
// // this Nav was opened from a modal
|
||||
// this.parent = <any>viewCtrl.getNav();
|
||||
// this.parent.registerChildNav(this);
|
||||
|
||||
// } else if (this._app) {
|
||||
// // this is the root navcontroller for the entire app
|
||||
// this._app.registerRootNav(this);
|
||||
// }
|
||||
|
||||
// // Tabs may also be an actual ViewController which was navigated to
|
||||
// // if Tabs is static and not navigated to within a NavController
|
||||
// // then skip this and don't treat it as it's own ViewController
|
||||
// if (viewCtrl) {
|
||||
// viewCtrl._setContent(this);
|
||||
// viewCtrl._setContentRef(elementRef);
|
||||
// }
|
||||
// }
|
||||
|
||||
this.initTabs();
|
||||
}
|
||||
|
||||
protected ionViewDidUnload() {
|
||||
this.tabs = this.selectedTab = null;
|
||||
}
|
||||
|
||||
@Listen('ionTabbarClick')
|
||||
@Listen('ionSelect')
|
||||
tabChange(ev: CustomEvent) {
|
||||
const selectedTab = ev.detail as HTMLIonTabElement;
|
||||
this.select(selectedTab);
|
||||
}
|
||||
|
||||
@Listen('ionTabDidLoad')
|
||||
tabDidLoad(ev: TabEvent) {
|
||||
const tab = ev.detail.tab;
|
||||
|
||||
// First tab? Select it
|
||||
if (this.tabs.length === 0) {
|
||||
this.handleOnTabSelected(tab, 0);
|
||||
}
|
||||
|
||||
this.tabs = [ ...this.tabs, tab ];
|
||||
protected addTab(ev: CustomEvent) {
|
||||
const tab = ev.detail as HTMLIonTabElement;
|
||||
const id = `t-${this.tabsId}-${++this.ids}`;
|
||||
tab.btnId = 'tab-' + id;
|
||||
tab.id = 'tabpanel-' + id;
|
||||
this.tabs = [...this.tabs, tab];
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
@Listen('ionTabDidUnload')
|
||||
tabDidUnload(ev: any) {
|
||||
this.tabs = this.tabs.filter((t: any) => t !== ev.detail.tab);
|
||||
protected removeTab(ev: CustomEvent) {
|
||||
const tab = ev.detail;
|
||||
this.tabs.slice(this.tabs.indexOf(tab));
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
handleOnTabSelected(tab: any, index: number) {
|
||||
// Select just this tab
|
||||
this.tabs.forEach((t: any) => t.isSelected = false);
|
||||
tab.isSelected = true;
|
||||
/**
|
||||
* @param {number|Tab} tabOrIndex Index, or the Tab instance, of the tab to select.
|
||||
*/
|
||||
@Method()
|
||||
select(tabOrIndex: number | HTMLIonTabElement) {
|
||||
const selectedTab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex);
|
||||
if (!selectedTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the selected tab and index
|
||||
this.selectedTab = tab;
|
||||
this.selectedIndex = index;
|
||||
// Reset rest of tabs
|
||||
for (let tab of this.tabs) {
|
||||
if (selectedTab !== tab) {
|
||||
tab.selected = false;
|
||||
}
|
||||
}
|
||||
selectedTab.selected = true;
|
||||
|
||||
// Fire a change event
|
||||
this.ionChange && this.ionChange(tab);
|
||||
if (this.selectedTab === selectedTab) {
|
||||
selectedTab.goToRoot();
|
||||
} else {
|
||||
const promise = selectedTab._setActive(true);
|
||||
const leavingTab = this.selectedTab;
|
||||
if (leavingTab) {
|
||||
promise.then(() => {
|
||||
Context.dom.raf(() => {
|
||||
leavingTab._setActive(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.selectedTab = selectedTab;
|
||||
this.selectHistory.push(selectedTab.id);
|
||||
this.ionChange.emit(selectedTab);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index Index of the tab you want to get
|
||||
* @returns {HTMLIonTabElement} Returns the tab who's index matches the one passed
|
||||
*/
|
||||
@Method()
|
||||
getByIndex(index: number): HTMLIonTabElement {
|
||||
return this.tabs[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {HTMLIonTabElement} Returns the currently selected tab
|
||||
*/
|
||||
@Method()
|
||||
getSelected(): HTMLIonTabElement {
|
||||
return this.tabs.find((tab) => tab.selected);
|
||||
}
|
||||
|
||||
@Method()
|
||||
getIndex(tab: HTMLIonTabElement): number {
|
||||
return this.tabs.indexOf(tab);
|
||||
}
|
||||
|
||||
@Method()
|
||||
getTabs(): HTMLIonTabElement[] {
|
||||
return this.tabs;
|
||||
}
|
||||
|
||||
private initTabs() {
|
||||
// find pre-selected tabs
|
||||
let selectedTab = this.tabs.find(t => t.selected);
|
||||
|
||||
// reset all tabs none is selected
|
||||
for (let tab of this.tabs) {
|
||||
tab.selected = false;
|
||||
}
|
||||
|
||||
// find a tab candidate in case, the selected in null
|
||||
if (!selectedTab) {
|
||||
selectedTab = this.tabs.find(t => t.show && t.enabled);
|
||||
}
|
||||
selectedTab._setActive(true);
|
||||
this.selectedTab = selectedTab;
|
||||
this.selectHistory.push(selectedTab.id);
|
||||
}
|
||||
|
||||
private loadConfig(attrKey: string, fallback: any) {
|
||||
const val = (this as any)[attrKey];
|
||||
if (typeof val === 'undefined') {
|
||||
(this as any)[attrKey] = this.config.get(attrKey, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previously selected Tab which is currently not disabled or hidden.
|
||||
* @param {boolean} trimHistory If the selection history should be trimmed up to the previous tab selection or not.
|
||||
* @returns {HTMLIonTabElement}
|
||||
*/
|
||||
@Method()
|
||||
previousTab(trimHistory: boolean = true): HTMLIonTabElement {
|
||||
// walk backwards through the tab selection history
|
||||
// and find the first previous tab that is enabled and shown
|
||||
for (var i = this.selectHistory.length - 2; i >= 0; i--) {
|
||||
var id = this.selectHistory[i];
|
||||
var tab = this.tabs.find(t => t.id === id);
|
||||
if (tab && tab.enabled && tab.show) {
|
||||
if (trimHistory) {
|
||||
this.selectHistory.splice(i + 1);
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Method()
|
||||
resize() {
|
||||
const tab = this.getSelected();
|
||||
tab && tab.resize();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return [
|
||||
<ion-tab-bar
|
||||
tabs={this.tabs}
|
||||
onTabSelected={this.handleOnTabSelected.bind(this)}
|
||||
selectedIndex={this.selectedIndex} />,
|
||||
<slot></slot>
|
||||
];
|
||||
const dom = [
|
||||
<div class='tabs-inner'>
|
||||
<slot></slot>
|
||||
</div>];
|
||||
if (!this.tabbarHidden) {
|
||||
dom.push(
|
||||
<ion-tabbar
|
||||
tabs={this.tabs}
|
||||
selectedTab={this.selectedTab}
|
||||
highlight={this.tabbarHighlight}
|
||||
placement={this.tabbarPlacement}
|
||||
layout={this.tabbarLayout}>
|
||||
</ion-tabbar>
|
||||
);
|
||||
}
|
||||
return dom;
|
||||
}
|
||||
}
|
||||
|
||||
let tabIds = -1;
|
||||
|
@ -54,11 +54,11 @@ $tabs-wp-tab-background-activated: rgba(0, 0, 0, .1) !default;
|
||||
$tabs-wp-tab-icon-size: 2.4rem !default;
|
||||
|
||||
|
||||
.tabs-wp .tabbar {
|
||||
.tabbar-wp {
|
||||
background: $tabs-wp-background;
|
||||
}
|
||||
|
||||
.tabs-wp .tab-button {
|
||||
.tabbar-wp ion-tab-button {
|
||||
@include border-radius(0);
|
||||
|
||||
@include padding($tabs-wp-tab-padding-top, $tabs-wp-tab-padding-end, $tabs-wp-tab-padding-bottom, $tabs-wp-tab-padding-start);
|
||||
@ -72,21 +72,25 @@ $tabs-wp-tab-icon-size: 2.4rem !default;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tabs-wp .tab-button[aria-selected=true] {
|
||||
.tabbar-wp ion-tab-highlight {
|
||||
background: $tabs-wp-tab-color-active;
|
||||
}
|
||||
|
||||
.tabbar-wp ion-tab-button.tab-selected {
|
||||
border-bottom-color: $tabs-wp-tab-border-color-active;
|
||||
color: $tabs-wp-tab-color-active;
|
||||
}
|
||||
|
||||
.tabs-wp .tab-button.activated {
|
||||
.tabbar-wp ion-tab-button.activated {
|
||||
background: $tabs-wp-tab-background-activated;
|
||||
}
|
||||
|
||||
.tabs-wp[tabsPlacement=bottom] .tab-button {
|
||||
.tabbar-wp.placement-bottom ion-tab-button {
|
||||
border-top: $tabs-wp-tab-border;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.tabs-wp[tabsPlacement=bottom] .tab-button[aria-selected=true] {
|
||||
.tabbar-wp.placement-bottom ion-tab-button.tab-selected {
|
||||
border-top-color: $tabs-wp-tab-border-color-active;
|
||||
}
|
||||
|
||||
@ -94,7 +98,7 @@ $tabs-wp-tab-icon-size: 2.4rem !default;
|
||||
// Windows Tab Button Text
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabs-wp .tab-button-text {
|
||||
.tabbar-wp .tab-button-text {
|
||||
@include margin(5px, null);
|
||||
}
|
||||
|
||||
@ -102,7 +106,7 @@ $tabs-wp-tab-icon-size: 2.4rem !default;
|
||||
// Windows Tab Button Icon
|
||||
// --------------------------------------------------
|
||||
|
||||
.tabs-wp .tab-button-icon {
|
||||
.tabbar-wp .tab-button-icon {
|
||||
min-width: $tabs-wp-tab-icon-size;
|
||||
|
||||
font-size: $tabs-wp-tab-icon-size;
|
||||
@ -110,28 +114,28 @@ $tabs-wp-tab-icon-size: 2.4rem !default;
|
||||
color: $tabs-wp-tab-icon-color;
|
||||
}
|
||||
|
||||
.tabs-wp .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-wp .tab-selected .tab-button-icon {
|
||||
color: $tabs-wp-tab-icon-color-active;
|
||||
}
|
||||
|
||||
.tabs-wp[tabsLayout=icon-bottom] .tab-button {
|
||||
.tabbar-wp.layout-icon-bottom ion-tab-button {
|
||||
@include padding(8px, null, 8px, null);
|
||||
}
|
||||
|
||||
.tabs-wp[tabsLayout=icon-end] .tab-button,
|
||||
.tabs-wp[tabsLayout=icon-start] .tab-button {
|
||||
.tabbar-wp.layout-icon-end ion-tab-button,
|
||||
.tabbar-wp.layout-icon-start ion-tab-button {
|
||||
@include padding(null, null, 10px, null);
|
||||
}
|
||||
|
||||
.tabs-wp[tabsLayout=icon-end] .tab-button ion-icon,
|
||||
.tabs-wp[tabsLayout=icon-start] .tab-button ion-icon {
|
||||
.tabbar-wp.layout-icon-end ion-icon,
|
||||
.tabbar-wp.layout-icon-start ion-icon {
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.tabs-wp[tabsLayout=icon-hide] .tab-button,
|
||||
.tabs-wp[tabsLayout=title-hide] .tab-button,
|
||||
.tabs-wp .tab-button.icon-only,
|
||||
.tabs-wp .tab-button.has-title-only {
|
||||
.tabbar-wp.layout-icon-hide ion-tab-button,
|
||||
.tabbar-wp.layout-title-hide ion-tab-button,
|
||||
.tabbar-wp ion-tab-button.icon-only,
|
||||
.tabbar-wp ion-tab-button.has-title-only {
|
||||
@include padding(6px, 10px);
|
||||
}
|
||||
|
||||
@ -141,19 +145,15 @@ $tabs-wp-tab-icon-size: 2.4rem !default;
|
||||
|
||||
@mixin tabbar-wp($color-name, $color-base, $color-contrast) {
|
||||
|
||||
.tabs-wp-#{$color-name} .tabbar {
|
||||
background-color: $color-base;
|
||||
}
|
||||
|
||||
.tabs-wp-#{$color-name} .tab-button,
|
||||
.tabs-wp-#{$color-name} .tab-button-icon {
|
||||
.tabbar-wp-#{$color-name} {
|
||||
color: rgba($color-contrast, $tabs-wp-tab-opacity);
|
||||
background-color: $color-base;
|
||||
|
||||
fill: rgba($color-contrast, $tabs-wp-tab-opacity);
|
||||
}
|
||||
|
||||
.tabs-wp-#{$color-name} .tab-button:hover:not(.disable-hover),
|
||||
.tabs-wp-#{$color-name} .tab-button:hover:not(.disable-hover) .tab-button-icon,
|
||||
.tabs-wp-#{$color-name} .tab-button[aria-selected=true],
|
||||
.tabs-wp-#{$color-name} .tab-button[aria-selected=true] .tab-button-icon {
|
||||
.tabbar-wp-#{$color-name} ion-tab-button:hover:not(.disable-hover),
|
||||
.tabbar-wp-#{$color-name} ion-tab-button.tab-selected {
|
||||
border-color: $color-contrast;
|
||||
color: $color-contrast;
|
||||
}
|
||||
|
35
packages/core/src/components/tabs/test/basic.html
Normal file
35
packages/core/src/components/tabs/test/basic.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ionic Spinners</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<script src="/dist/ionic.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-tabs>
|
||||
<ion-tab root="page-tab" title="Plain List" icon="star"></ion-tab>
|
||||
<ion-tab root="page-two" title="Schedule" icon="globe"></ion-tab>
|
||||
<ion-tab root="page-three" title="Stopwatch" icon="logo-facebook"></ion-tab>
|
||||
<ion-tab root="page-one" title="Messages" icon="chatboxes"></ion-tab>
|
||||
</ion-tabs>
|
||||
|
||||
<ion-nav-controller></ion-nav-controller>
|
||||
</ion-app>
|
||||
|
||||
<style>
|
||||
f {
|
||||
display: block;
|
||||
margin: 15px auto;
|
||||
max-width: 150px;
|
||||
height: 150px;
|
||||
background: blue;
|
||||
}
|
||||
|
||||
f:last-of-type {
|
||||
background: yellow;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
@ -89,10 +89,9 @@ export class Toggle implements BooleanInputComponent {
|
||||
'onStart': this.onDragStart.bind(this),
|
||||
'onMove': this.onDragMove.bind(this),
|
||||
'onEnd': this.onDragEnd.bind(this),
|
||||
'onPress': this.toggle.bind(this),
|
||||
'gestureName': 'toggle',
|
||||
'gesturePriority': 30,
|
||||
'type': 'pan,press',
|
||||
'type': 'pan',
|
||||
'direction': 'x',
|
||||
'threshold': 0,
|
||||
'attachTo': 'parent'
|
||||
|
@ -22,6 +22,7 @@ export const PLATFORM_CONFIGS: PlatformConfig[] = [
|
||||
name: IOS,
|
||||
settings: {
|
||||
mode: IOS,
|
||||
tabsHighlight: false,
|
||||
},
|
||||
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, IOS, [IPHONE, IPAD, 'ipod'], WINDOWS_PHONE)
|
||||
},
|
||||
|
3
packages/core/src/index.d.ts
vendored
3
packages/core/src/index.d.ts
vendored
@ -23,6 +23,9 @@ import { Segment } from './components/segment/segment';
|
||||
import { SegmentButton, SegmentButtonEvent } from './components/segment-button/segment-button';
|
||||
import { SplitPane, SplitPaneAlert } from './components/split-pane/split-pane';
|
||||
|
||||
import { Tab } from './components/tabs/tab';
|
||||
import { Tabs } from './components/tabs/tabs';
|
||||
|
||||
import { Toast, ToastEvent, ToastOptions } from './components/toast/toast';
|
||||
import { ToastController } from './components/toast-controller/toast-controller';
|
||||
|
||||
|
@ -33,6 +33,7 @@ $toolbar-md-inactive-color: $toolbar-inactive-color !default;
|
||||
// --------------------------------------------------
|
||||
|
||||
$tabs-md-background: $tabs-background !default;
|
||||
$tabs-md-border-color: rgba(255, 255, 255, .1) !default;
|
||||
$tabs-md-tab-color-inactive: $tabs-tab-color-inactive !default;
|
||||
$tabs-md-tab-color-active: $tabs-tab-color-active !default;
|
||||
|
||||
|
@ -33,6 +33,7 @@ $toolbar-md-inactive-color: $toolbar-inactive-color !default;
|
||||
// --------------------------------------------------
|
||||
|
||||
$tabs-md-background: $tabs-background !default;
|
||||
$tabs-md-border-color: rgba(0, 0, 0, .07) !default;
|
||||
$tabs-md-tab-color-inactive: #3c3c3c !default;
|
||||
$tabs-md-tab-color-active: $tabs-tab-color-active !default;
|
||||
|
||||
|
@ -34,7 +34,7 @@ exports.config = {
|
||||
{ components: ['ion-spinner'] },
|
||||
{ components: ['ion-split-pane'] },
|
||||
{ components: ['ion-range', 'ion-range-knob']},
|
||||
{ components: ['ion-tabs', 'ion-tab', 'ion-tab-bar', 'ion-tab-button', 'ion-tab-highlight'] },
|
||||
{ components: ['ion-tabs', 'ion-tab', 'ion-tabbar', 'ion-tab-button', 'ion-tab-highlight'] },
|
||||
{ components: ['ion-toggle'] },
|
||||
{ components: ['ion-nav', 'ion-nav-controller', 'stencil-ion-nav-delegate','page-one', 'page-two', 'page-three'] },
|
||||
{ components: ['ion-toast', 'ion-toast-controller'] },
|
||||
|
Reference in New Issue
Block a user