refactor(tabs): apply design-doc (#16048)

This commit is contained in:
Manu MA
2018-10-25 22:13:19 +02:00
committed by GitHub
parent 5948a1c191
commit 4d3ad67740
65 changed files with 1384 additions and 1513 deletions

View File

@@ -21,7 +21,7 @@ export class StackController {
ref: enteringRef,
element: (enteringRef && enteringRef.location && enteringRef.location.nativeElement) as HTMLElement,
url: this.getUrl(route),
fullpath: document.location.pathname,
fullpath: document.location!.pathname,
deactivatedId: -1
};
}

View File

@@ -15,7 +15,7 @@ export class TabsDelegate {
}
}
@HostListener('ionTabbarClick', ['$event'])
@HostListener('ionTabButtonClick', ['$event'])
onTabbarClick(ev: UIEvent) {
const tabElm: HTMLIonTabElement = ev.detail as any;
if (this.router && tabElm && tabElm.href) {

View File

@@ -72,7 +72,8 @@ export const DIRECTIVES = [
d.Spinner,
d.SplitPane,
d.Tab,
d.Tabbar,
d.TabBar,
d.TabButton,
d.Tabs,
d.Text,
d.Textarea,

View File

@@ -813,33 +813,42 @@ export class SplitPane {
}
export declare interface Tab extends StencilComponents<'IonTab'> {}
@Component({ selector: 'ion-tab', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['active', 'btnId', 'delegate', 'label', 'href', 'icon', 'badge', 'badgeColor', 'component', 'name', 'disabled', 'selected', 'show', 'tabsHideOnSubPages'] })
@Component({ selector: 'ion-tab', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['active', 'delegate', 'tab', 'component'] })
export class Tab {
ionSelect: EventEmitter<CustomEvent>;
ionTabMutated: EventEmitter<CustomEvent>;
constructor(r: ElementRef) {
const el = r.nativeElement;
proxyMethods(this, el, ['setActive']);
proxyInputs(this, el, ['active', 'btnId', 'delegate', 'label', 'href', 'icon', 'badge', 'badgeColor', 'component', 'name', 'disabled', 'selected', 'show', 'tabsHideOnSubPages']);
proxyOutputs(this, el, ['ionSelect', 'ionTabMutated']);
proxyInputs(this, el, ['active', 'delegate', 'tab', 'component']);
}
}
export declare interface Tabbar extends StencilComponents<'IonTabbar'> {}
@Component({ selector: 'ion-tabbar', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['mode', 'color', 'layout', 'placement', 'selectedTab', 'tabs', 'highlight', 'translucent'] })
export class Tabbar {
ionTabbarClick: EventEmitter<CustomEvent>;
export declare interface TabBar extends StencilComponents<'IonTabBar'> {}
@Component({ selector: 'ion-tab-bar', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['mode', 'color', 'layout', 'placement', 'selectedTab', 'translucent'] })
export class TabBar {
ionTabBarChanged: EventEmitter<CustomEvent>;
constructor(r: ElementRef) {
const el = r.nativeElement;
proxyInputs(this, el, ['mode', 'color', 'layout', 'placement', 'selectedTab', 'tabs', 'highlight', 'translucent']);
proxyOutputs(this, el, ['ionTabbarClick']);
proxyInputs(this, el, ['mode', 'color', 'layout', 'placement', 'selectedTab', 'translucent']);
proxyOutputs(this, el, ['ionTabBarChanged']);
}
}
export declare interface TabButton extends StencilComponents<'IonTabButton'> {}
@Component({ selector: 'ion-tab-button', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['mode', 'color', 'layout', 'href', 'tab', 'disabled'] })
export class TabButton {
ionTabButtonClick: EventEmitter<CustomEvent>;
constructor(r: ElementRef) {
const el = r.nativeElement;
proxyInputs(this, el, ['mode', 'color', 'layout', 'href', 'tab', 'disabled']);
proxyOutputs(this, el, ['ionTabButtonClick']);
}
}
export declare interface Tabs extends StencilComponents<'IonTabs'> {}
@Component({ selector: 'ion-tabs', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['name', 'tabbarHidden', 'useRouter'] })
@Component({ selector: 'ion-tabs', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: '<ng-content></ng-content>', inputs: ['name'] })
export class Tabs {
ionChange: EventEmitter<CustomEvent>;
ionNavWillLoad: EventEmitter<CustomEvent>;
@@ -849,7 +858,7 @@ export class Tabs {
constructor(r: ElementRef) {
const el = r.nativeElement;
proxyMethods(this, el, ['select', 'setRouteId', 'getRouteId', 'getTab', 'getSelected']);
proxyInputs(this, el, ['name', 'tabbarHidden', 'useRouter']);
proxyInputs(this, el, ['name']);
proxyOutputs(this, el, ['ionChange', 'ionNavWillLoad', 'ionNavWillChange', 'ionNavDidChange']);
}
}

View File

@@ -81,7 +81,8 @@ const DECLARATIONS = [
d.Spinner,
d.SplitPane,
d.Tab,
d.Tabbar,
d.TabBar,
d.TabButton,
d.Tabs,
d.Text,
d.Textarea,

View File

@@ -55,6 +55,8 @@ import {
Side,
SpinnerTypes,
StyleEvent,
TabbarChangedDetail,
TabbarClickDetail,
TabbarLayout,
TabbarPlacement,
TextFieldTypes,
@@ -3704,7 +3706,7 @@ export namespace Components {
interface IonRoute {
/**
* Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in ion-tabs it actually refers to the name of the `ion-tab` to select.
* Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
*/
'component': string;
/**
@@ -3718,7 +3720,7 @@ export namespace Components {
}
interface IonRouteAttributes extends StencilHTMLAttributes {
/**
* Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in ion-tabs it actually refers to the name of the `ion-tab` to select.
* Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
*/
'component'?: string;
/**
@@ -4538,146 +4540,13 @@ export namespace Components {
'when'?: string | boolean;
}
interface IonTab {
/**
* If `true`, sets the tab as the active tab.
*/
'active': boolean;
/**
* The badge for the tab.
*/
'badge'?: string;
/**
* The badge color for the tab button.
*/
'badgeColor'?: Color;
/**
* hidden
*/
'btnId'?: string;
/**
* The component to display inside of the tab.
*/
'component'?: ComponentRef;
/**
* hidden
*/
'delegate'?: FrameworkDelegate;
/**
* If `true`, the user cannot interact with the tab. Defaults to `false`.
*/
'disabled': boolean;
/**
* The URL which will be used as the `href` within this tab's button anchor.
*/
'href'?: string;
/**
* The icon for the tab.
*/
'icon'?: string;
/**
* The label of the tab.
*/
'label'?: string;
/**
* The name of the tab.
*/
'name'?: string;
/**
* If `true`, the tab will be selected. Defaults to `false`.
*/
'selected': boolean;
/**
* Set the active component for the tab
*/
'setActive': () => Promise<void>;
/**
* If `true`, the tab button is visible within the tabbar. Defaults to `true`.
*/
'show': boolean;
/**
* If `true`, hide the tabs on child pages.
*/
'tabsHideOnSubPages': boolean;
}
interface IonTabAttributes extends StencilHTMLAttributes {
/**
* If `true`, sets the tab as the active tab.
*/
'active'?: boolean;
/**
* The badge for the tab.
*/
'badge'?: string;
/**
* The badge color for the tab button.
*/
'badgeColor'?: Color;
/**
* hidden
*/
'btnId'?: string;
/**
* The component to display inside of the tab.
*/
'component'?: ComponentRef;
/**
* hidden
*/
'delegate'?: FrameworkDelegate;
/**
* If `true`, the user cannot interact with the tab. Defaults to `false`.
*/
'disabled'?: boolean;
/**
* The URL which will be used as the `href` within this tab's button anchor.
*/
'href'?: string;
/**
* The icon for the tab.
*/
'icon'?: string;
/**
* The label of the tab.
*/
'label'?: string;
/**
* The name of the tab.
*/
'name'?: string;
/**
* Emitted when the current tab is selected.
*/
'onIonSelect'?: (event: CustomEvent<void>) => void;
/**
* Emitted when the tab props mutates. Used internally.
*/
'onIonTabMutated'?: (event: CustomEvent<void>) => void;
/**
* If `true`, the tab will be selected. Defaults to `false`.
*/
'selected'?: boolean;
/**
* If `true`, the tab button is visible within the tabbar. Defaults to `true`.
*/
'show'?: boolean;
/**
* If `true`, hide the tabs on child pages.
*/
'tabsHideOnSubPages'?: boolean;
}
interface IonTabbar {
interface IonTabBar {
/**
* 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;
/**
* If `true`, show the tab highlight bar under the selected tab.
*/
'highlight': 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"`.
* Set the layout of the text and icon in the tabbar.
*/
'layout': TabbarLayout;
/**
@@ -4685,33 +4554,87 @@ export namespace Components {
*/
'mode': Mode;
/**
* Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`.
* Set the position of the tabbar, relative to the content.
*/
'placement': TabbarPlacement;
/**
* The selected tab component
*/
'selectedTab'?: HTMLIonTabElement;
'selectedTab'?: string;
/**
* The tabs to render
*/
'tabs': HTMLIonTabElement[];
/**
* If `true`, the tabbar will be translucent. Defaults to `false`.
* If `true`, the tab bar will be translucent. Defaults to `false`.
*/
'translucent': boolean;
}
interface IonTabbarAttributes extends StencilHTMLAttributes {
interface IonTabBarAttributes 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;
/**
* If `true`, show the tab highlight bar under the selected tab.
* Set the layout of the text and icon in the tabbar.
*/
'highlight'?: boolean;
'layout'?: TabbarLayout;
/**
* 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"`.
* The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`.
*/
'mode'?: Mode;
'onIonTabBarChanged'?: (event: CustomEvent<TabbarChangedDetail>) => void;
/**
* Set the position of the tabbar, relative to the content.
*/
'placement'?: TabbarPlacement;
/**
* The selected tab component
*/
'selectedTab'?: string;
/**
* If `true`, the tab bar will be translucent. Defaults to `false`.
*/
'translucent'?: boolean;
}
interface IonTabButton {
/**
* 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 selected tab component
*/
'disabled': boolean;
/**
* The URL which will be used as the `href` within this tab's button anchor.
*/
'href'?: string;
/**
* Set the layout of the text and icon in the tabbar.
*/
'layout': TabbarLayout;
/**
* The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`.
*/
'mode': Mode;
/**
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
*/
'tab'?: string;
}
interface IonTabButtonAttributes 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 selected tab component
*/
'disabled'?: boolean;
/**
* The URL which will be used as the `href` within this tab's button anchor.
*/
'href'?: string;
/**
* Set the layout of the text and icon in the tabbar.
*/
'layout'?: TabbarLayout;
/**
@@ -4721,23 +4644,40 @@ export namespace Components {
/**
* Emitted when the tab bar is clicked
*/
'onIonTabbarClick'?: (event: CustomEvent<HTMLIonTabElement>) => void;
'onIonTabButtonClick'?: (event: CustomEvent<TabbarClickDetail>) => void;
/**
* Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`.
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
*/
'placement'?: TabbarPlacement;
'tab'?: string;
}
interface IonTab {
'active': boolean;
/**
* The selected tab component
* The component to display inside of the tab.
*/
'selectedTab'?: HTMLIonTabElement;
'component'?: ComponentRef;
'delegate'?: FrameworkDelegate;
/**
* The tabs to render
* Set the active component for the tab
*/
'tabs'?: HTMLIonTabElement[];
'setActive': () => Promise<void>;
/**
* If `true`, the tabbar will be translucent. Defaults to `false`.
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
*/
'translucent'?: boolean;
'tab'?: string;
}
interface IonTabAttributes extends StencilHTMLAttributes {
'active'?: boolean;
/**
* The component to display inside of the tab.
*/
'component'?: ComponentRef;
'delegate'?: FrameworkDelegate;
/**
* A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them.
*/
'tab'?: string;
}
interface IonTabs {
@@ -4749,7 +4689,7 @@ export namespace Components {
/**
* Get the tab at the given index
*/
'getTab': (tabOrIndex: string | number | HTMLIonTabElement) => Promise<HTMLIonTabElement | undefined>;
'getTab': (tab: string | HTMLIonTabElement) => Promise<HTMLIonTabElement | undefined>;
/**
* A unique name for the tabs.
*/
@@ -4757,16 +4697,8 @@ export namespace Components {
/**
* Index or the Tab instance, of the tab to select.
*/
'select': (tabOrIndex: number | HTMLIonTabElement) => Promise<boolean>;
'select': (tab: string | HTMLIonTabElement) => Promise<boolean>;
'setRouteId': (id: string) => Promise<RouteWrite>;
/**
* If `true`, the tabbar will be hidden. Defaults to `false`.
*/
'tabbarHidden': boolean;
/**
* If `true`, the tabs will use the router and `selectedTab` will not do anything.
*/
'useRouter': boolean;
}
interface IonTabsAttributes extends StencilHTMLAttributes {
/**
@@ -4789,14 +4721,6 @@ export namespace Components {
* Emitted when the navigation will load a component.
*/
'onIonNavWillLoad'?: (event: CustomEvent<void>) => void;
/**
* If `true`, the tabbar will be hidden. Defaults to `false`.
*/
'tabbarHidden'?: boolean;
/**
* If `true`, the tabs will use the router and `selectedTab` will not do anything.
*/
'useRouter'?: boolean;
}
interface IonText {
@@ -5456,8 +5380,9 @@ declare global {
'IonSlides': Components.IonSlides;
'IonSpinner': Components.IonSpinner;
'IonSplitPane': Components.IonSplitPane;
'IonTabBar': Components.IonTabBar;
'IonTabButton': Components.IonTabButton;
'IonTab': Components.IonTab;
'IonTabbar': Components.IonTabbar;
'IonTabs': Components.IonTabs;
'IonText': Components.IonText;
'IonTextarea': Components.IonTextarea;
@@ -5559,8 +5484,9 @@ declare global {
'ion-slides': Components.IonSlidesAttributes;
'ion-spinner': Components.IonSpinnerAttributes;
'ion-split-pane': Components.IonSplitPaneAttributes;
'ion-tab-bar': Components.IonTabBarAttributes;
'ion-tab-button': Components.IonTabButtonAttributes;
'ion-tab': Components.IonTabAttributes;
'ion-tabbar': Components.IonTabbarAttributes;
'ion-tabs': Components.IonTabsAttributes;
'ion-text': Components.IonTextAttributes;
'ion-textarea': Components.IonTextareaAttributes;
@@ -6102,18 +6028,24 @@ declare global {
new (): HTMLIonSplitPaneElement;
};
interface HTMLIonTabBarElement extends Components.IonTabBar, HTMLStencilElement {}
var HTMLIonTabBarElement: {
prototype: HTMLIonTabBarElement;
new (): HTMLIonTabBarElement;
};
interface HTMLIonTabButtonElement extends Components.IonTabButton, HTMLStencilElement {}
var HTMLIonTabButtonElement: {
prototype: HTMLIonTabButtonElement;
new (): HTMLIonTabButtonElement;
};
interface HTMLIonTabElement extends Components.IonTab, HTMLStencilElement {}
var HTMLIonTabElement: {
prototype: HTMLIonTabElement;
new (): HTMLIonTabElement;
};
interface HTMLIonTabbarElement extends Components.IonTabbar, HTMLStencilElement {}
var HTMLIonTabbarElement: {
prototype: HTMLIonTabbarElement;
new (): HTMLIonTabbarElement;
};
interface HTMLIonTabsElement extends Components.IonTabs, HTMLStencilElement {}
var HTMLIonTabsElement: {
prototype: HTMLIonTabsElement;
@@ -6263,8 +6195,9 @@ declare global {
'ion-slides': HTMLIonSlidesElement
'ion-spinner': HTMLIonSpinnerElement
'ion-split-pane': HTMLIonSplitPaneElement
'ion-tab-bar': HTMLIonTabBarElement
'ion-tab-button': HTMLIonTabButtonElement
'ion-tab': HTMLIonTabElement
'ion-tabbar': HTMLIonTabbarElement
'ion-tabs': HTMLIonTabsElement
'ion-text': HTMLIonTextElement
'ion-textarea': HTMLIonTextareaElement
@@ -6366,8 +6299,9 @@ declare global {
'ion-slides': HTMLIonSlidesElement;
'ion-spinner': HTMLIonSpinnerElement;
'ion-split-pane': HTMLIonSplitPaneElement;
'ion-tab-bar': HTMLIonTabBarElement;
'ion-tab-button': HTMLIonTabButtonElement;
'ion-tab': HTMLIonTabElement;
'ion-tabbar': HTMLIonTabbarElement;
'ion-tabs': HTMLIonTabsElement;
'ion-text': HTMLIonTextElement;
'ion-textarea': HTMLIonTextareaElement;

View File

@@ -4,7 +4,7 @@ import { Animation, AnimationBuilder, AnimationController, Config } from '../../
import { Animator } from './animator';
/** @hidden */
/** @internal */
@Component({
tag: 'ion-animation-controller'
})

View File

@@ -132,7 +132,6 @@ export class Button implements ComponentInterface {
const form = this.el.closest('form');
if (form) {
ev.preventDefault();
ev.stopPropagation();
const fakeButton = document.createElement('button');
fakeButton.type = this.type;

View File

@@ -40,7 +40,10 @@ export class ItemDivider implements ComponentInterface {
hostData() {
return {
class: createColorClasses(this.color)
class: {
...createColorClasses(this.color),
'item': true,
}
};
}

View File

@@ -16,7 +16,10 @@ export class ItemGroup implements ComponentInterface {
hostData() {
return {
class: createThemedClasses(this.mode, 'item-group')
class: {
...createThemedClasses(this.mode, 'item-group'),
'item': true,
}
};
}
}

View File

@@ -4,7 +4,7 @@
// iOS Label
// --------------------------------------------------
:host {
:host-context(.item) {
@include margin($label-ios-margin-top, $label-ios-margin-end, $label-ios-margin-bottom, $label-ios-margin-start);
}

View File

@@ -4,7 +4,7 @@
// Material Design Label
// --------------------------------------------------
:host {
:host-context(.item) {
@include margin($label-md-margin-top, $label-md-margin-end, $label-md-margin-bottom, $label-md-margin-start);
}

View File

@@ -4,7 +4,7 @@
// Label
// --------------------------------------------------
:host {
:host-context(.item) {
/**
* @prop --color: Color of the label
*/

View File

@@ -4,7 +4,6 @@ import { Gesture, GestureDetail, Mode, PickerColumn } from '../../interface';
import { hapticSelectionChanged } from '../../utils';
import { clamp } from '../../utils/helpers';
/** @hidden */
@Component({
tag: 'ion-picker-column'
})

View File

@@ -3,7 +3,7 @@ import { Component, ComponentInterface, Method, Prop } from '@stencil/core';
import { OverlayController, PickerOptions } from '../../interface';
import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays';
/** @hidden */
/** @internal */
@Component({
tag: 'ion-picker-controller'
})

View File

@@ -9,11 +9,11 @@ Router is a component that can take a component, and render it when the Browser
## Properties
| Property | Attribute | Description | Type |
| ---------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| `componentProps` | -- | A key value `{ 'red': true, 'blue': 'white'}` containing props that should be passed to the defined component when rendered. | `undefined \| { [key: string]: any; }` |
| `component` | `component` | Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in ion-tabs it actually refers to the name of the `ion-tab` to select. | `string` |
| `url` | `url` | Relative path that needs to match in order for this route to apply. Accepts paths similar to expressjs so that you can define parameters in the url /foo/:bar where bar would be available in incoming props. | `string` |
| Property | Attribute | Description | Type |
| ---------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| `componentProps` | -- | A key value `{ 'red': true, 'blue': 'white'}` containing props that should be passed to the defined component when rendered. | `undefined \| { [key: string]: any; }` |
| `component` | `component` | Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select. | `string` |
| `url` | `url` | Relative path that needs to match in order for this route to apply. Accepts paths similar to expressjs so that you can define parameters in the url /foo/:bar where bar would be available in incoming props. | `string` |
## Events

View File

@@ -18,7 +18,7 @@ export class Route implements ComponentInterface {
* when the route matches.
*
* The value of this property is not always the tagname of the component to load,
* in ion-tabs it actually refers to the name of the `ion-tab` to select.
* in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
*/
@Prop() component!: string;

View File

@@ -105,26 +105,38 @@
class TabsPage extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-tabs>
<ion-tab component="tab-one"
label="Plain List"
icon="star"></ion-tab>
<ion-tabs>
<ion-tab component="tab-two"
label="Schedule"
icon="globe"></ion-tab>
<ion-tab tab="tab-one" component="tab-one"></ion-tab>
<ion-tab tab="tab-two" component="tab-two"></ion-tab>
<ion-tab tab="tab-three" component="tab-three"></ion-tab>
<ion-tab tab="tab-four">
inline tab 4
</ion-tab>
<ion-tab name="tab3"
label="Stopwatch"
icon="logo-facebook"
component="tab-three"></ion-tab>
<ion-tab-bar>
<ion-tab name="tab4"
label="Messages"
icon="chatboxes"
name="tab-four">
inline tab 4
</ion-tab>
<ion-tab-button tab="tab-one">
<ion-label>Plain List</ion-label>
<ion-icon name="star"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-two">
<ion-label>Schedule</ion-label>
<ion-icon name="globe"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-three">
<ion-label>Stopwatch</ion-label>
<ion-icon name="logo-facebook"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-four">
<ion-label>Messages</ion-label>
<ion-icon name="star"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
`;
}
@@ -194,8 +206,8 @@
<ion-route url="/second-page" component="page-two"> </ion-route>
<ion-route url="/three/:prop1" component="page-three"> </ion-route>
</ion-route>
<ion-route url="/three" component="tab3"> </ion-route>
<ion-route url="/four" component="tab4"> </ion-route>
<ion-route url="/three" component="tab-three"> </ion-route>
<ion-route url="/four" component="tab-four"> </ion-route>
</ion-route>
</ion-router>

View File

@@ -0,0 +1,42 @@
# ion-tab-bar
Tab bar is the UI component that implements the array of button of `ion-tabs`. It's provided by default when `ion-tabs` is used, though, this "implicit" tab bar can not be customized.
In order to have a custom tabbar, it should be provided in user's markup as direct children of `ion-tabs`:
```html
<style>
ion-tab-bar {
font-size: 20px;
}
</style>
<ion-tabs>
<!-- User tabs -->
<ion-tab></ion-tab>
<ion-tab></ion-tab>
<!-- User provided ion-tab-bar that can be customized -->
<ion-tab-bar color="dark" layout="icon-only">
</ion-tabs>
```
<!-- Auto Generated Below -->
## Properties
| Property | Attribute | Description | Type |
| ------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `color` | `color` | 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). | `string \| undefined` |
| `layout` | `layout` | Set the layout of the text and icon in the tabbar. | `"icon-bottom" \| "icon-end" \| "icon-hide" \| "icon-start" \| "icon-top" \| "label-hide"` |
| `mode` | `mode` | The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`. | `"ios" \| "md"` |
| `placement` | `placement` | Set the position of the tabbar, relative to the content. | `"bottom" \| "top"` |
| `selectedTab` | `selected-tab` | The selected tab component | `string \| undefined` |
| `translucent` | `translucent` | If `true`, the tab bar will be translucent. Defaults to `false`. | `boolean` |
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*

View File

@@ -1,2 +1,11 @@
export type TabbarLayout = 'icon-top' | 'icon-start' | 'icon-end' | 'icon-bottom' | 'icon-hide' | 'label-hide';
export type TabbarPlacement = 'top' | 'bottom';
export interface TabbarChangedDetail {
tab?: string;
}
export interface TabbarClickDetail {
tab?: string;
href?: string;
}

View File

@@ -0,0 +1,21 @@
@import "./tab-bar";
@import "../../themes/ionic.globals.ios";
// iOS Tabs
// --------------------------------------------------
:host {
// default color / background
--background: #{$tabbar-ios-background};
--border: #{$hairlines-width solid $tabbar-ios-border-color};
height: 50px;
}
// iOS Translucent Tabbar
// --------------------------------------------------
:host(.tabbar-translucent) {
background-color: #{current-color(base, .8)};
backdrop-filter: saturate(210%) blur(20px);
}

View File

@@ -0,0 +1,10 @@
@import "./tab-bar";
@import "../../themes/ionic.globals.md";
:host {
// default color / background
--background: #{$tabbar-md-background};
--border: #{1px solid $tabbar-md-border-color};
height: 56px;
}

View File

@@ -0,0 +1,51 @@
@import "../../themes/ionic.globals";
:host {
@include padding-horizontal(
var(--ion-safe-area-left),
var(--ion-safe-area-right)
);
display: flex;
align-items: center;
justify-content: center;
order: 1;
width: auto;
background: var(--background);
color: var(--color);
contain: strict;
user-select: none;
z-index: $z-index-toolbar;
box-sizing: content-box;
}
:host(.ion-color) {
--background: #{current-color(base)};
}
:host(.ion-color) ::slotted(ion-tab-button) {
--color: #{current-color(contrast, .7)};
--color-selected: #{current-color(contrast)};
--background-focused: #{current-color(shade)};
}
:host(.placement-top) {
order: -1;
border-bottom: var(--border);
}
:host(.placement-bottom) {
padding-bottom: var(--ion-safe-area-bottom, 0);
border-top: var(--border);
}
:host(.tabbar-hidden) {
/* stylelint-disable-next-line declaration-no-important */
display: none !important;
}

View File

@@ -0,0 +1,101 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Color, Mode, TabbarChangedDetail, TabbarLayout, TabbarPlacement } from '../../interface';
import { createColorClasses } from '../../utils/theme';
@Component({
tag: 'ion-tab-bar',
styleUrls: {
ios: 'tab-bar.ios.scss',
md: 'tab-bar.md.scss'
},
shadow: true
})
export class TabBar implements ComponentInterface {
@Element() el!: HTMLElement;
@Prop({ context: 'queue' }) queue!: QueueApi;
@Prop({ context: 'document' }) doc!: Document;
@State() keyboardVisible = false;
/**
* 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;
/**
* Set the layout of the text and icon in the tabbar.
*/
@Prop() layout: TabbarLayout = 'icon-top';
/**
* Set the position of the tabbar, relative to the content.
*/
@Prop() placement: TabbarPlacement = 'bottom';
/**
* The selected tab component
*/
@Prop() selectedTab?: string;
@Watch('selectedTab')
selectedTabChanged() {
this.ionTabBarChanged.emit({
tab: this.selectedTab
});
}
/**
* If `true`, the tab bar will be translucent. Defaults to `false`.
*/
@Prop() translucent = false;
/** @internal */
@Event() ionTabBarChanged!: EventEmitter<TabbarChangedDetail>;
@Listen('body:keyboardWillHide')
protected onKeyboardWillHide() {
setTimeout(() => this.keyboardVisible = false, 50);
}
@Listen('body:keyboardWillShow')
protected onKeyboardWillShow() {
if (this.placement === 'bottom') {
this.keyboardVisible = true;
}
}
componentWillLoad() {
this.selectedTabChanged();
}
hostData() {
const { color, translucent, placement, keyboardVisible } = this;
return {
role: 'tablist',
'aria-hidden': keyboardVisible ? 'true' : null,
'slot': 'tabbar',
class: {
...createColorClasses(color),
'tabbar-translucent': translucent,
[`placement-${placement}`]: true,
'tabbar-hidden': keyboardVisible,
}
};
}
render() {
return (
<slot></slot>
);
}
}

View File

@@ -0,0 +1,10 @@
import { newE2EPage } from '@stencil/core/testing';
it('tab-bar: scenarios', async () => {
const page = await newE2EPage({
url: '/src/components/tab-bar/test/scenarios?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -0,0 +1,194 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Tab - Colors</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<script src="../../../../../dist/ionic.js"></script>
<link rel="stylesheet" href="../../../../../css/ionic.bundle.css">
<link rel="stylesheet" href="../../../../../scripts/testing/styles.css">
</head>
<body>
<ion-app>
<!-- Default -->
<ion-tab-bar selected-tab="1">
<ion-tab-button tab='1'>
<ion-label>Recents</ion-label>
</ion-tab-button>
<ion-tab-button tab='2'>
<ion-label>Favorites</ion-label>
<ion-badge>6</ion-badge>
</ion-tab-button>
<ion-tab-button tab='3'>
<ion-label>Settings</ion-label>
</ion-tab-button>
</ion-tab-bar>
<!-- Icons -->
<ion-tab-bar color="primary" selected-tab="1">
<ion-tab-button tab='1'>
<ion-icon name="call"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='2' selected-tab="1">
<ion-icon name="heart"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='3'>
<ion-icon name="settings"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
<!-- Icons on top of text -->
<ion-tab-bar color="secondary" selected-tab="1">
<ion-tab-button tab='1'>
<ion-label>Location</ion-label>
<ion-icon name="navigate"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='2'>
<ion-badge>6</ion-badge>
<ion-icon name="heart"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='3'>
<ion-label>Radio</ion-label>
<ion-icon name="musical-notes"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
<!-- Icons below text -->
<ion-tab-bar color="dark" selected-tab="1">
<ion-tab-button tab='1' layout="icon-bottom">
<ion-label>Recents</ion-label>
<ion-icon name="call"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='2' layout="icon-top">
<ion-badge>6</ion-badge>
<ion-label>Favorites</ion-label>
<ion-icon name="heart"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='3' layout="icon-bottom">
<ion-label>Settings</ion-label>
<ion-icon name="settings"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
<!-- Icons right of text -->
<ion-tab-bar color="danger" selected-tab="1">
<ion-tab-button tab='1' layout="icon-end">
<ion-label>Recents</ion-label>
<ion-icon name="call"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='2' layout="icon-end">
<ion-label>Favorites</ion-label>
<ion-icon name="heart"></ion-icon>
<ion-badge>6</ion-badge>
</ion-tab-button>
<ion-tab-button tab='3' layout="icon-end">
<ion-label>Settings</ion-label>
<ion-icon name="settings"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
<!-- Icons left of text -->
<ion-tab-bar color="light" selected-tab="1">
<ion-tab-button tab='1' layout="icon-start">
<ion-label>Recents</ion-label>
<ion-icon name="call"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='2' layout="icon-start">
<ion-label>Favorites</ion-label>
<ion-icon name="heart"></ion-icon>
<ion-badge color="danger">6</ion-badge>
</ion-tab-button>
<ion-tab-button tab='3' layout="icon-start">
<ion-label>Settings</ion-label>
<ion-icon name="settings"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
<!-- No icons -->
<ion-tab-bar color="primary" selected-tab="1">
<ion-tab-button tab='1' layout="icon-hide">
<ion-label>Recents</ion-label>
<ion-icon name="call"></ion-icon>
</ion-tab-button>
<ion-tab-button tab='2' layout="icon-hide">
<ion-label>Favorites</ion-label>
<ion-icon name="heart"></ion-icon>
<ion-badge color="danger">6</ion-badge>
</ion-tab-button>
<ion-tab-button tab='3' layout="icon-hide">
<ion-label>Settings</ion-label>
<ion-icon name="settings"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
<!-- No label -->
<ion-tab-bar color="secondary" selected-tab="1">
<ion-tab-button tab='1' layout="label-hide">
<ion-label>Recents</ion-label>
<ion-icon name="navigate"></ion-icon>
<ion-badge>6</ion-badge>
</ion-tab-button>
<ion-tab-button tab='2' layout="label-hide">
<ion-label>Favorites</ion-label>
<ion-icon name="heart"></ion-icon>
<ion-badge color="danger">6</ion-badge>
</ion-tab-button>
<ion-tab-button tab='3' layout="label-hide">
<ion-label>Settings</ion-label>
<ion-icon name="settings"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
<!-- No overflow text -->
<ion-tab-bar color="danger" selected-tab="1">
<ion-tab-button tab='1'>
<ion-label>Indiana Jones and the Raiders of the Lost Ark</ion-label>
</ion-tab-button>
<ion-tab-button tab='2'>
<ion-label>Indiana Jones and the Temple of Doom</ion-label>
</ion-tab-button>
<ion-tab-button tab='3'>
<ion-label>Indiana Jones and the Last Crusade</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,42 @@
# ion-tab-bar
Tab bar is the UI component that implements the array of button of `ion-tabs`. It's provided by default when `ion-tabs` is used, though, this "implicit" tab bar can not be customized.
In order to have a custom tab bar, it should be provided in user's markup as direct children of `ion-tabs`:
```html
<style>
ion-tab-bar {
font-size: 20px;
}
</style>
<ion-tabs>
<!-- User tabs -->
<ion-tab></ion-tab>
<ion-tab></ion-tab>
<!-- User provided ion-tab-bar that can be customized -->
<ion-tab-bar color="dark" layout="icon-only">
</ion-tabs>
```
<!-- Auto Generated Below -->
## Properties
| Property | Attribute | Description | Type |
| ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `color` | `color` | 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). | `string \| undefined` |
| `disabled` | `disabled` | The selected tab component | `boolean` |
| `href` | `href` | The URL which will be used as the `href` within this tab's button anchor. | `string \| undefined` |
| `layout` | `layout` | Set the layout of the text and icon in the tabbar. | `"icon-bottom" \| "icon-end" \| "icon-hide" \| "icon-start" \| "icon-top" \| "label-hide"` |
| `mode` | `mode` | The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`. | `"ios" \| "md"` |
| `tab` | `tab` | A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them. | `string \| undefined` |
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*

View File

@@ -0,0 +1,72 @@
@import "./tab-button";
@import "./tab-button.ios.vars";
:host {
--padding-top: #{$tab-button-ios-padding-top};
--padding-end: #{$tab-button-ios-padding-end};
--padding-bottom: #{$tab-button-ios-padding-bottom};
--padding-start: #{$tab-button-ios-padding-start};
--color: #{$tab-button-ios-text-color};
--color-selected: #{$tabbar-ios-color-activated};
--background: transparent;
--background-focused: #{$tabbar-ios-background-focused};
max-width: $tab-button-ios-max-width;
font-size: $tab-button-ios-font-size;
}
:host(.tab-has-label-only) ::slotted(ion-label) {
@include margin(2px, 0);
font-size: $tab-button-ios-font-size + 2;
font-size: 14px;
line-height: 1.1;
}
::slotted(ion-label) {
@include margin(0, null, 1px, null);
min-height: $tab-button-ios-font-size + 1;
}
::slotted(ion-icon) {
@include margin(4px, null, null, null);
font-size: $tab-button-ios-icon-size;
}
::slotted(ion-icon::before) {
vertical-align: top;
}
// iOS TabButton Layout
// --------------------------------------------------
:host(.tab-layout-icon-end) ::slotted(ion-label),
:host(.tab-layout-icon-start) ::slotted(ion-label),
:host(.tab-layout-icon-hide) ::slotted(ion-label) {
margin-top: 2px;
margin-bottom: 2px;
font-size: 14px;
line-height: 1.1;
}
:host(.tab-layout-icon-end) ::slotted(ion-icon),
:host(.tab-layout-icon-start) ::slotted(ion-icon) {
min-width: 24px;
height: 26px;
margin-top: 2px;
margin-bottom: 1px;
font-size: 24px;
}
:host(.tab-layout-label-hide) ::slotted(ion-icon) {
@include margin(0);
}

View File

@@ -0,0 +1,88 @@
@import "./tab-button";
@import "./tab-button.md.vars";
// Material Design Tab Button
// --------------------------------------------------
:host {
--padding-top: #{$tab-button-md-padding-top};
--padding-end: #{$tab-button-md-padding-end};
--padding-bottom: #{$tab-button-md-padding-bottom};
--padding-start: #{$tab-button-md-padding-start};
--color: #{$tab-button-md-text-color};
--color-selected: #{$tabbar-md-color-activated};
--background: transparent;
--background-focused: #{$tabbar-md-background-focused};
max-width: 168px;
font-size: $tab-button-md-font-size;
font-weight: $tab-button-md-font-weight;
}
// Material Design Tab Button Text
// --------------------------------------------------
::slotted(ion-label) {
@include margin($tab-button-md-text-margin-top, $tab-button-md-text-margin-end, $tab-button-md-text-margin-bottom, $tab-button-md-text-margin-start);
@include transform-origin(center, bottom);
transition: $tab-button-md-text-transition;
text-transform: $tab-button-md-text-capitalization;
}
::slotted(.tab-selected) ::slotted(ion-label) {
transform: #{$tab-button-md-text-transform-active};
transition: $tab-button-md-text-transition;
}
// Material Design Tab Button Icon
// --------------------------------------------------
::slotted(ion-icon) {
@include transform-origin(center, center);
width: $tab-button-md-icon-size;
height: $tab-button-md-icon-size;
transition: $tab-button-md-icon-transition;
font-size: $tab-button-md-icon-size;
}
// Material Design TabButton Layout
// --------------------------------------------------
:host(.tab-layout-icon-top) ::slotted(ion-label) {
margin-bottom: -2px;
}
:host(.tab-layout-icon-bottom) ::slotted(ion-label) {
margin-top: -2px;
// --label-transform: transform-origin(center, top);
}
:host(.tab-selected) ::slotted(ion-label) {
transform: #{$tab-button-md-text-transform-active};
}
:host(.tab-selected) ::slotted(ion-icon) {
transform: #{$tab-button-md-icon-transform-active};
}
:host(.tab-layout-icon-end.tab-selected) ::slotted(ion-icon) {
transform: #{$tab-button-md-icon-right-transform-active};
}
:host(.tab-layout-icon-bottom.tab-selected) ::slotted(ion-icon) {
transform: #{$tab-button-md-icon-bottom-transform-active};
}
:host(.tab-layout-icon-start.tab-selected) ::slotted(ion-icon) {
transform: #{$tab-button-md-icon-left-transform-active};
}

View File

@@ -0,0 +1,169 @@
@import "../../themes/ionic.globals";
:host {
--badge-end: 4%;
flex: 1;
height: 100%;
color: var(--color);
}
a {
@include text-inherit();
@include margin(0);
@include padding(
var(--padding-top),
var(--padding-end),
var(--padding-bottom),
var(--padding-start),
);
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
border: 0;
outline: none;
background: var(--background);
text-decoration: none;
cursor: pointer;
overflow: hidden;
box-sizing: border-box;
-webkit-user-drag: none;
}
a:focus-visible {
background: var(--background-focused);
}
@media (any-hover: hover) {
a:hover {
color: var(--color-selected);
}
}
:host(.tab-selected) {
color: var(--color-selected);
}
:host(.tab-hidden) {
/* stylelint-disable-next-line declaration-no-important */
display: none !important;
}
:host(.tab-disabled) {
pointer-events: none;
opacity: .4;
}
::slotted(ion-label) {
order: 0;
}
::slotted(ion-icon) {
order: -1;
height: 1em;
}
::slotted(ion-label),
::slotted(ion-icon) {
display: block;
align-self: center;
min-width: 26px;
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
}
:host(.tab-has-label-only) ::slotted(ion-label) {
white-space: normal;
}
// Tab Badges
// --------------------------------------------------
::slotted(ion-badge) {
@include position(null, var(--badge-end), null, null);
@include padding(1px, 6px);
box-sizing: border-box;
position: absolute;
height: auto;
font-size: 12px;
line-height: 16px;
}
// Tab Button Layout
// --------------------------------------------------
:host(.tab-layout-icon-start) a {
flex-direction: row;
}
:host(.tab-layout-icon-end) a {
flex-direction: row-reverse;
}
:host(.tab-layout-icon-bottom) a {
flex-direction: column-reverse;
}
:host(.tab-layout-icon-start) a,
:host(.tab-layout-icon-end) a,
:host(.tab-layout-icon-hide) a,
:host(.tab-layout-label-hide) a,
:host(.tab-has-icon-only) a,
:host(.tab-has-label-only) a {
justify-content: center;
}
:host(.tab-layout-icon-hide) ::slotted(ion-icon) {
display: none;
}
:host(.tab-layout-label-hide) ::slotted(ion-label) {
display: none;
}
:host(.tab-layout-icon-top),
:host(.tab-layout-icon-bottom),
:host(.tab-layout-icon-only),
:host(.tab-layout-label-hide),
:host(.tab-has-icon-only) {
--badge-end: #{calc(50% - 30px)};
}
:host(.tab-layout-icon-hide),
:host(.tab-layout-icon-start),
:host(.tab-layout-icon-end),
:host(.tab-has-label-only) {
--badge-end: #{calc(50% - 50px)};
}

View File

@@ -0,0 +1,128 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, QueueApi, State } from '@stencil/core';
import { Color, Mode, TabbarClickDetail, TabbarLayout } from '../../interface';
import { createColorClasses } from '../../utils/theme';
import { TabbarChangedDetail } from '../tab-bar/tab-bar-interface';
@Component({
tag: 'ion-tab-button',
styleUrls: {
ios: 'tab-button.ios.scss',
md: 'tab-button.md.scss'
},
shadow: true
})
export class TabButton implements ComponentInterface {
@Element() el!: HTMLElement;
@Prop({ context: 'queue' }) queue!: QueueApi;
@Prop({ context: 'document' }) doc!: Document;
/**
* The selected tab component
*/
@State() selected = false;
/**
* 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;
/**
* Set the layout of the text and icon in the tabbar.
*/
@Prop() layout: TabbarLayout = 'icon-top';
/**
* The URL which will be used as the `href` within this tab's button anchor.
*/
@Prop() href?: string;
/**
* A tab id must be provided for each `ion-tab`. It's used internally to reference
* the selected tab or by the router to switch between them.
*/
@Prop() tab?: string;
/**
* The selected tab component
*/
@Prop() disabled = false;
/**
* Emitted when the tab bar is clicked
* @internal
*/
@Event() ionTabButtonClick!: EventEmitter<TabbarClickDetail>;
@Listen('parent:ionTabBarChanged')
onTabbarChanged(ev: CustomEvent<TabbarChangedDetail>) {
this.selected = this.tab === ev.detail.tab;
}
@Listen('click')
onClick(ev: Event) {
if (!this.disabled) {
this.ionTabButtonClick.emit({
tab: this.tab,
href: this.href
});
}
ev.preventDefault();
}
componentWillLoad() {
if (this.tab === undefined) {
console.warn('ion-tab-button needs a tab, so it can be selected');
}
}
private get hasLabel() {
return !!this.el.querySelector('ion-label');
}
private get hasIcon() {
return !!this.el.querySelector('ion-icon');
}
hostData() {
const { color, tab, selected, layout, disabled, hasLabel, hasIcon } = this;
return {
'role': 'tab',
'ion-activatable': true,
'aria-selected': selected ? 'true' : null,
'id': `tab-button-${tab}`,
'aria-controls': `tab-view-${tab}`,
class: {
...createColorClasses(color),
'tab-selected': selected,
'tab-disabled': disabled,
'tab-has-label': hasLabel,
'tab-has-icon': hasIcon,
'tab-has-label-only': hasLabel && !hasIcon,
'tab-has-icon-only': hasIcon && !hasLabel,
[`tab-layout-${layout}`]: true,
}
};
}
render() {
const { mode, href } = this;
return (
<a href={href || '#'}>
<slot></slot>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</a>
);
}
}

View File

@@ -12,30 +12,12 @@ See the [Tabs API Docs](../Tabs/) for more details on configuring Tabs.
## Properties
| Property | Attribute | Description | Type |
| -------------------- | ------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------------- |
| `active` | `active` | If `true`, sets the tab as the active tab. | `boolean` |
| `badgeColor` | `badge-color` | The badge color for the tab button. | `string \| undefined` |
| `badge` | `badge` | The badge for the tab. | `string \| undefined` |
| `btnId` | `btn-id` | hidden | `string \| undefined` |
| `component` | `component` | The component to display inside of the tab. | `Function \| HTMLElement \| null \| string \| undefined` |
| `delegate` | -- | hidden | `FrameworkDelegate \| undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the tab. Defaults to `false`. | `boolean` |
| `href` | `href` | The URL which will be used as the `href` within this tab's button anchor. | `string \| undefined` |
| `icon` | `icon` | The icon for the tab. | `string \| undefined` |
| `label` | `label` | The label of the tab. | `string \| undefined` |
| `name` | `name` | The name of the tab. | `string \| undefined` |
| `selected` | `selected` | If `true`, the tab will be selected. Defaults to `false`. | `boolean` |
| `show` | `show` | If `true`, the tab button is visible within the tabbar. Defaults to `true`. | `boolean` |
| `tabsHideOnSubPages` | `tabs-hide-on-sub-pages` | If `true`, hide the tabs on child pages. | `boolean` |
## Events
| Event | Detail | Description |
| --------------- | ------ | ---------------------------------------------------- |
| `ionSelect` | | Emitted when the current tab is selected. |
| `ionTabMutated` | | Emitted when the tab props mutates. Used internally. |
| Property | Attribute | Description | Type |
| ----------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `active` | `active` | | `boolean` |
| `component` | `component` | The component to display inside of the tab. | `Function \| HTMLElement \| null \| string \| undefined` |
| `delegate` | -- | | `FrameworkDelegate \| undefined` |
| `tab` | `tab` | A tab id must be provided for each `ion-tab`. It's used internally to reference the selected tab or by the router to switch between them. | `string \| undefined` |
## Methods

View File

@@ -1,6 +1,6 @@
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core';
import { Build, Component, ComponentInterface, Element, Method, Prop } from '@stencil/core';
import { Color, ComponentRef, FrameworkDelegate } from '../../interface';
import { ComponentRef, FrameworkDelegate } from '../../interface';
import { attachComponent } from '../../utils/framework-delegate';
@Component({
@@ -13,94 +13,24 @@ export class Tab implements ComponentInterface {
private loaded = false;
@Element() el!: HTMLIonTabElement;
/**
* If `true`, sets the tab as the active tab.
*/
/** @internal */
@Prop({ mutable: true }) active = false;
/** hidden */
@Prop() btnId?: string;
/** hidden */
/** @internal */
@Prop() delegate?: FrameworkDelegate;
/**
* The label of the tab.
* A tab id must be provided for each `ion-tab`. It's used internally to reference
* the selected tab or by the router to switch between them.
*/
@Prop() label?: string;
/**
* The URL which will be used as the `href` within this tab's button anchor.
*/
@Prop() href?: string;
/**
* The icon for the tab.
*/
@Prop() icon?: string;
/**
* The badge for the tab.
*/
@Prop() badge?: string;
/**
* The badge color for the tab button.
*/
@Prop() badgeColor?: Color;
@Prop() tab?: string;
/**
* The component to display inside of the tab.
*/
@Prop() component?: ComponentRef;
/**
* The name of the tab.
*/
@Prop({ mutable: true }) name?: string;
/**
* If `true`, the user cannot interact with the tab. Defaults to `false`.
*/
@Prop() disabled = false;
/**
* If `true`, the tab will be selected. Defaults to `false`.
*/
@Prop() selected = false;
@Watch('selected')
selectedChanged(selected: boolean) {
if (selected) {
this.ionSelect.emit();
}
}
/**
* If `true`, the tab button is visible within the tabbar. Defaults to `true`.
*/
@Prop() show = true;
/**
* If `true`, hide the tabs on child pages.
*/
@Prop() tabsHideOnSubPages = false;
/**
* Emitted when the current tab is selected.
*/
@Event() ionSelect!: EventEmitter<void>;
/**
* Emitted when the tab props mutates. Used internally.
*/
@Event() ionTabMutated!: EventEmitter<void>;
componentWillLoad() {
// Set default name
if (this.name === undefined && typeof this.component === 'string') {
this.name = this.component;
}
if (Build.isDev) {
if (this.component !== undefined && this.el.childElementCount > 0) {
@@ -109,18 +39,12 @@ export class Tab implements ComponentInterface {
` or` +
`- Remove the embedded content inside the ion-tab: <ion-tab></ion-tab>`);
}
}
}
@Watch('label')
@Watch('href')
@Watch('show')
@Watch('disabled')
@Watch('badge')
@Watch('badgeColor')
@Watch('icon')
onPropChanged() {
this.ionTabMutated.emit();
if (this.tab === undefined) {
console.error(`Tab views need to have an unique id attribute:
<ion-tab tab="my-unique-id">`);
}
}
}
/** Set the active component for the tab */
@@ -139,11 +63,12 @@ export class Tab implements ComponentInterface {
}
hostData() {
const { btnId, active, component } = this;
const { tab, active, component } = this;
return {
'aria-labelledby': btnId,
'aria-hidden': !active ? 'true' : null,
'role': 'tabpanel',
'aria-hidden': !active ? 'true' : null,
'aria-labelledby': `tab-button-${tab}`,
'id': `tab-view-${tab}`,
'class': {
'ion-page': component === undefined,
'tab-hidden': !active

View File

@@ -1,51 +0,0 @@
# ion-tabbar
Tabbar is the UI component that implements the array of button of `ion-tabs`. It's provided by default when `ion-tabs` is used, though, this "implicit" tabbar can not be customized.
In order to have a custom tabbar, it should be provided in user's markup as direct children of `ion-tabs`:
```html
<style>
ion-tabbar {
font-size: 20px;
}
</style>
<ion-tabs>
<!-- User tabs -->
<ion-tab></ion-tab>
<ion-tab></ion-tab>
<!-- User provided ion-tabbar that can be customized -->
<ion-tabbar color="dark" layout="icon-only">
</ion-tabs>
```
<!-- Auto Generated Below -->
## Properties
| Property | Attribute | Description | Type |
| ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `color` | `color` | 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). | `string \| undefined` |
| `highlight` | `highlight` | If `true`, show the tab highlight bar under the selected tab. | `boolean` |
| `layout` | `layout` | 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"`. | `"icon-bottom" \| "icon-end" \| "icon-hide" \| "icon-start" \| "icon-top" \| "label-hide"` |
| `mode` | `mode` | The mode determines which platform styles to use. Possible values are: `"ios"` or `"md"`. | `"ios" \| "md"` |
| `placement` | `placement` | Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`. | `"bottom" \| "top"` |
| `selectedTab` | -- | The selected tab component | `HTMLIonTabElement \| undefined` |
| `tabs` | -- | The tabs to render | `HTMLIonTabElement[]` |
| `translucent` | `translucent` | If `true`, the tabbar will be translucent. Defaults to `false`. | `boolean` |
## Events
| Event | Detail | Description |
| ---------------- | ----------------- | ----------------------------------- |
| `ionTabbarClick` | HTMLIonTabElement | Emitted when the tab bar is clicked |
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*

View File

@@ -1,32 +0,0 @@
@import "./tab-button";
@import "./tab-button.ios.vars";
.tab-btn {
@include padding($tab-button-ios-padding-top, $tab-button-ios-padding-end, $tab-button-ios-padding-bottom, $tab-button-ios-padding-start);
max-width: $tab-button-ios-max-width;
}
.tab-btn-text {
@include margin(0, null, 1px, null);
min-height: $tab-button-ios-font-size + 1;
}
.tab-btn-has-label-only .tab-btn-text {
@include margin(2px, 0);
font-size: $tab-button-ios-font-size + 2;
font-size: 14px;
line-height: 1.1;
}
.tab-btn-icon {
@include margin(4px, null, null, null);
font-size: $tab-button-ios-icon-size;
}
.tab-btn-icon::before {
vertical-align: top;
}

View File

@@ -1,48 +0,0 @@
@import "./tab-button";
@import "./tab-button.md.vars";
// Material Design Tab Button
// --------------------------------------------------
.tab-btn {
@include padding($tab-button-md-padding-top, $tab-button-md-padding-end, $tab-button-md-padding-bottom, $tab-button-md-padding-start);
max-width: 168px;
}
// Material Design Tab Button Text
// --------------------------------------------------
.tab-btn-text {
@include margin($tab-button-md-text-margin-top, $tab-button-md-text-margin-end, $tab-button-md-text-margin-bottom, $tab-button-md-text-margin-start);
@include transform-origin(center, bottom);
transform: var(--label-transform);
transition: $tab-button-md-text-transition;
text-transform: $tab-button-md-text-capitalization;
}
.tab-btn-selected .tab-btn-text {
transform: #{$tab-button-md-text-transform-active};
transition: $tab-button-md-text-transition;
}
// Material Design Tab Button Icon
// --------------------------------------------------
.tab-btn-icon {
@include transform-origin(center, center);
width: $tab-button-md-icon-size;
height: $tab-button-md-icon-size;
transform: var(--icon-transform);
transition: $tab-button-md-icon-transition;
font-size: $tab-button-md-icon-size;
}

View File

@@ -1,134 +0,0 @@
@import "../../themes/ionic.globals";
.tab-btn {
@include text-inherit();
@include margin(0);
@include padding(0);
display: flex;
position: relative;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
border: 0;
outline: none;
background: transparent;
text-decoration: none;
cursor: pointer;
overflow: hidden;
box-sizing: border-box;
-webkit-user-drag: none;
}
.tab-btn:focus-visible {
background: var(--background-focused);
}
@media (any-hover: hover) {
.tab-btn:hover {
color: var(--color-selected);
}
}
.tab-btn-selected {
color: var(--color-selected);
}
.tab-btn-hidden {
/* stylelint-disable-next-line declaration-no-important */
display: none !important;
}
.tab-btn-disabled {
pointer-events: none;
opacity: .4;
}
.tab-btn-text {
@include margin(var(--label-margin-top), null, var(--label-margin-bottom), null);
display: var(--label-display, block);
font-size: var(--label-font-size);
line-height: var(--label-line-height);
}
.tab-btn-icon {
@include margin(var(--icon-margin-top), null, var(--icon-margin-bottom), null);
display: var(--icon-display, block);
min-width: var(--icon-min-width);
height: var(--icon-height, 1em);
font-size: var(--icon-font-size);
}
.tab-btn-text,
.tab-btn-icon {
align-self: center;
min-width: 26px;
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
}
.tab-btn-has-label-only .tab-btn-text {
white-space: normal;
}
.tab-btn-has-icon-only,
.tab-btn-has-label-only {
justify-content: center;
}
// Tab Badges
// --------------------------------------------------
.tab-btn-badge {
@include position(6%, 4%, null, null); // 4% fallback
@include position(null, calc(50% - 30px), null, null);
@include padding(1px, 6px);
box-sizing: border-box;
position: absolute;
height: auto;
font-size: 12px;
line-height: 16px;
}
.tab-btn-has-label-only .tab-btn-badge {
@include position-horizontal(null, #{calc(50% - 50px)});
}
.tab-btn-has-icon-only .tab-btn-badge {
@include position-horizontal(null, #{calc(50% - 30px)});
}
.tab-btn-selected .tab-btn-icon {
transform: var(--icon-transform-selected);
}

View File

@@ -1,63 +0,0 @@
@import "./tabbar";
@import "./tabbar.ios.vars";
@import "./tab-button.ios";
// iOS Tabs
// --------------------------------------------------
:host {
// default color / background
--background: #{$tabbar-ios-background};
--color: #{$tab-button-ios-text-color};
--color-selected: #{$tabbar-ios-color-activated};
--background-focused: #{$tabbar-ios-background-focused};
justify-content: center;
height: $tabbar-ios-height;
border-top: $tabbar-ios-border;
font-size: $tab-button-ios-font-size;
contain: strict;
}
:host(.placement-top) {
border-top: 0;
border-bottom: $tabbar-ios-border;
}
// iOS Translucent Tabbar
// --------------------------------------------------
:host(.tabbar-translucent) {
background-color: #{current-color(base, .8)};
backdrop-filter: $tabbar-ios-translucent-filter;
}
// iOS Tabbar Layout
// --------------------------------------------------
:host(.layout-icon-end),
:host(.layout-icon-start),
:host(.layout-icon-hide) {
--label-margin-top: 2px;
--label-margin-bottom: 2px;
--label-font-size: 14px;
--label-line-height: 1.1;
}
:host(.layout-icon-end),
:host(.layout-icon-start) {
--icon-margin-top: 2px;
--icon-margin-bottom: 1px;
--icon-min-width: 24px;
--icon-height: 26px;
--icon-font-size: 24px;
}
:host(.layout-label-hide) {
--icon-margin: 0;
}

View File

@@ -1,13 +0,0 @@
@import "../../themes/ionic.globals.ios";
// iOS Tabs
// --------------------------------------------------
/// @prop - Height of the tabbar
$tabbar-ios-height: 50px !default;
/// @prop - Border on the tabbar (border-top when [tabsPlacement=bottom] and border-bottom when [tabsPlacement=top])
$tabbar-ios-border: $hairlines-width solid $tabbar-ios-border-color !default;
/// @prop - Filter of the translucent tabbar
$tabbar-ios-translucent-filter: saturate(210%) blur(20px) !default;

View File

@@ -1,42 +0,0 @@
@import "./tabbar";
@import "./tabbar.md.vars";
@import "./tab-button.md";
:host {
// default color / background
--color: #{$tab-button-md-text-color};
--color-selected: #{$tabbar-md-color-activated};
--background: #{$tabbar-md-background};
--background-focused: #{$tabbar-md-background-focused};
--icon-transform-selected: #{$tab-button-md-icon-transform-active};
height: $tabbar-md-height;
border-top: $tabbar-md-border;
font-size: $tab-button-md-font-size;
font-weight: $tab-button-md-font-weight;
contain: strict;
}
// Material Design Tabbar Layout
// --------------------------------------------------
:host(.layout-icon-top) {
--label-margin-bottom: -2px;
}
:host(.layout-icon-end) {
--icon-transform-selected: #{$tab-button-md-icon-right-transform-active};
}
:host(.layout-icon-bottom) {
--label-margin-top: -2px;
--label-transform: transform-origin(center, top);
--icon-transform-selected: #{$tab-button-md-icon-bottom-transform-active};
}
:host(.layout-icon-start) {
--icon-transform-selected: #{$tab-button-md-icon-left-transform-active};
}

View File

@@ -1,10 +0,0 @@
@import "../../themes/ionic.globals.md";
// Material Design Tabs
// --------------------------------------------------
/// @prop - Minimum height of the tabbar
$tabbar-md-height: 56px !default;
/// @prop - Border on the tabbar
$tabbar-md-border: 1px solid $tabbar-md-border-color !default;

View File

@@ -1,119 +0,0 @@
@import "../../themes/ionic.globals";
:host {
@include padding-horizontal(
var(--ion-safe-area-left),
var(--ion-safe-area-right)
);
display: flex;
align-items: center;
justify-content: center;
order: 1;
width: auto;
background: var(--background);
color: var(--color);
user-select: none;
z-index: $z-index-toolbar;
box-sizing: content-box;
}
:host(.ion-color) {
--background: #{current-color(base)};
--color: #{current-color(contrast, .7)};
--color-selected: #{current-color(contrast)};
}
:host(.tabbar-hidden) {
/* stylelint-disable-next-line declaration-no-important */
display: none !important;
}
:host(.placement-top) {
order: -1;
}
:host(.placement-bottom) {
padding-bottom: var(--ion-safe-area-bottom, 0);
}
// Tab Highlight
// --------------------------------------------------
.tabbar-highlight {
@include position(null, null, 0, 0);
@include transform-origin(0, 0);
display: block;
position: absolute;
width: 1px;
height: 2px;
transform: translateZ(0);
background: currentColor;
&.animated {
transition-duration: 300ms;
transition-property: transform;
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
will-change: transform;
}
}
:host(.placement-top) .tabbar-highlight {
bottom: 0;
}
:host(.placement-bottom) .tabbar-highlight {
top: 0;
}
// Tab Layout
// --------------------------------------------------
:host(.layout-icon-start) .tab-btn {
flex-direction: row;
}
:host(.layout-icon-end) .tab-btn {
flex-direction: row-reverse;
}
:host(.layout-icon-bottom) .tab-btn {
flex-direction: column-reverse;
}
:host(.layout-icon-start) .tab-btn,
:host(.layout-icon-end) .tab-btn,
:host(.layout-icon-hide) .tab-btn,
:host(.layout-label-hide) .tab-btn {
justify-content: center;
}
:host(.layout-icon-hide) {
--icon-display: none;
}
:host(.layout-label-hide) {
--label-display: none;
}
:host(.layout-icon-top) .tab-btn,
:host(.layout-icon-bottom) .tab-btn {
--badge-end: #{calc(50% - 30px)};
}
:host(.layout-icon-hide) .tab-btn,
:host(.layout-icon-start) .tab-btn,
:host(.layout-icon-end) .tab-btn {
--badge-end: #{calc(50% - 50px)};
}

View File

@@ -1,163 +0,0 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Color, Mode, TabbarLayout, TabbarPlacement } from '../../interface';
import { createColorClasses } from '../../utils/theme';
@Component({
tag: 'ion-tabbar',
styleUrls: {
ios: 'tabbar.ios.scss',
md: 'tabbar.md.scss'
},
scoped: true
})
export class Tabbar implements ComponentInterface {
/**
* 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;
@Element() el!: HTMLElement;
@Prop({ context: 'queue' }) queue!: QueueApi;
@Prop({ context: 'document' }) doc!: Document;
@State() canScrollLeft = false;
@State() canScrollRight = false;
@State() keyboardVisible = false;
/**
* 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() layout: TabbarLayout = 'icon-top';
/**
* Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`.
*/
@Prop() placement: TabbarPlacement = 'bottom';
/**
* The selected tab component
*/
@Prop() selectedTab?: HTMLIonTabElement;
/**
* The tabs to render
*/
@Prop() tabs: HTMLIonTabElement[] = [];
/**
* If `true`, show the tab highlight bar under the selected tab.
*/
@Prop() highlight = false;
/**
* If `true`, the tabbar will be translucent. Defaults to `false`.
*/
@Prop() translucent = false;
/**
* Emitted when the tab bar is clicked
*/
@Event() ionTabbarClick!: EventEmitter<HTMLIonTabElement>;
@Listen('body:keyboardWillHide')
protected onKeyboardWillHide() {
setTimeout(() => this.keyboardVisible = false, 50);
}
@Listen('body:keyboardWillShow')
protected onKeyboardWillShow() {
if (this.placement === 'bottom') {
this.keyboardVisible = true;
}
}
componentDidLoad() {
this.updateHighlight();
}
@Watch('selectedTab')
@Listen('window:resize')
private updateHighlight() {
if (!this.highlight) {
return;
}
this.queue.read(() => {
const btn = this.el.shadowRoot!.querySelector('.tab-btn-selected') as HTMLElement | null;
const highlight = this.el.shadowRoot!.querySelector('.tabbar-highlight') as HTMLElement;
if (btn && highlight) {
highlight.style.transform = `translate3d(${btn.offsetLeft}px,0,0) scaleX(${btn.offsetWidth})`;
}
});
}
hostData() {
const { color, translucent, layout, placement, keyboardVisible } = this;
return {
role: 'tablist',
'aria-hidden': keyboardVisible ? 'true' : null,
'slot': 'tabbar',
class: {
...createColorClasses(color),
'tabbar-translucent': translucent,
[`layout-${layout}`]: true,
[`placement-${placement}`]: true,
'tabbar-hidden': keyboardVisible,
}
};
}
renderTabButton(tab: HTMLIonTabElement) {
const { icon, label, disabled, badge, badgeColor, href } = tab;
const selected = tab === this.selectedTab;
const hasLabel = label !== undefined;
const hasIcon = icon !== undefined;
return (
<a
role="tab"
ion-activatable
aria-selected={selected ? 'true' : null}
href={href || '#'}
class={{
'tab-btn': true,
'tab-btn-selected': selected,
'tab-btn-has-label': hasLabel,
'tab-btn-has-icon': hasIcon,
'tab-btn-has-label-only': hasLabel && !hasIcon,
'tab-btn-has-icon-only': hasIcon && !hasLabel,
'tab-btn-has-badge': badge !== undefined,
'tab-btn-disabled': disabled,
'tab-btn-hidden': !tab.show
}}
onClick={ev => {
if (!tab.disabled) {
this.ionTabbarClick.emit(tab);
}
ev.preventDefault();
}}
>
{icon && <ion-icon class="tab-btn-icon" icon={icon} lazy={false}></ion-icon>}
{label && <span class="tab-btn-text">{label}</span>}
{badge && <ion-badge class="tab-btn-badge" color={badgeColor}>{badge}</ion-badge>}
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</a>
);
}
render() {
return [
this.tabs.map(tab => this.renderTabButton(tab)),
this.highlight && <div class="animated tabbar-highlight" />
];
}
}

View File

@@ -4,23 +4,29 @@ Tabs are a top level navigation component for created multiple stacked navs.
The component is a container of individual [Tab](../Tab/) components.
`ion-tabs` is a styleless component that works as a router outlet in
order to handle navigation. When the user does not provide a `ion-tabbar` in their markup, `ion-tabs`, by default provides one. Notice that `ion-tabbar` is the UI component that can be used to switch between tabs.
order to handle navigation. When the user does not provide a `ion-tab-bar` in their markup, `ion-tabs`, by default provides one. Notice that `ion-tab-bar` is the UI component that can be used to switch between tabs.
In order to customize the style of the `ion-tabbar`, it should be included in the user's markup as
In order to customize the style of the `ion-tab-bar`, it should be included in the user's markup as
direct children of `ion-tabs`, like this:
```html
<style>
.my-custom-tabs {
font-size: 20px;
}
</style>
<ion-tabs>
<ion-tab label="Indiana Jones"></ion-tab>
<ion-tab label="Star Wars"></ion-tab>
<ion-tab label="Jurassic Park"></ion-tab>
<ion-tab tab="home">Home Content</ion-tab>
<ion-tab tab="settings">Settings Content</ion-tab>
<ion-tabbar color="danger" layout="icon-start" placement="top"></ion-tabbar>
<ion-tab-bar>
<ion-tab-button tab="home">
<ion-label>Home</ion-label>
<ion-icon name="home"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="settings">
<ion-label>Settings</ion-label>
<ion-icon name="gear"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
```
@@ -29,11 +35,9 @@ direct children of `ion-tabs`, like this:
## Properties
| Property | Attribute | Description | Type |
| -------------- | --------------- | ------------------------------------------------------------------------------- | --------------------- |
| `name` | `name` | A unique name for the tabs. | `string \| undefined` |
| `tabbarHidden` | `tabbar-hidden` | If `true`, the tabbar will be hidden. Defaults to `false`. | `boolean` |
| `useRouter` | `use-router` | If `true`, the tabs will use the router and `selectedTab` will not do anything. | `boolean` |
| Property | Attribute | Description | Type |
| -------- | --------- | --------------------------- | --------------------- |
| `name` | `name` | A unique name for the tabs. | `string \| undefined` |
## Events
@@ -68,15 +72,15 @@ Type: `Promise<HTMLIonTabElement | undefined>`
### `getTab(tabOrIndex: string | number | HTMLIonTabElement) => Promise<HTMLIonTabElement | undefined>`
### `getTab(tab: string | HTMLIonTabElement) => Promise<HTMLIonTabElement | undefined>`
Get the tab at the given index
#### Parameters
| Name | Type | Description |
| ------------ | --------------------------------------- | ----------- |
| `tabOrIndex` | `HTMLIonTabElement \| number \| string` | |
| Name | Type | Description |
| ----- | ----------------------------- | ----------- |
| `tab` | `HTMLIonTabElement \| string` | |
#### Returns
@@ -84,15 +88,15 @@ Type: `Promise<HTMLIonTabElement | undefined>`
### `select(tabOrIndex: number | HTMLIonTabElement) => Promise<boolean>`
### `select(tab: string | HTMLIonTabElement) => Promise<boolean>`
Index or the Tab instance, of the tab to select.
#### Parameters
| Name | Type | Description |
| ------------ | ----------------------------- | ----------- |
| `tabOrIndex` | `HTMLIonTabElement \| number` | |
| Name | Type | Description |
| ----- | ----------------------------- | ----------- |
| `tab` | `HTMLIonTabElement \| string` | |
#### Returns

View File

@@ -22,8 +22,3 @@
contain: layout size style;
}
:host(.tabbar-hidden) ion-tabbar,
:host(.tabbar-hidden)::slotted(ion-tabbar) {
display: none;
}

View File

@@ -1,6 +1,6 @@
import { Build, Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
import { Config, NavOutlet, RouteID, RouteWrite } from '../../interface';
import { Config, NavOutlet, RouteID, RouteWrite, TabbarClickDetail } from '../../interface';
@Component({
tag: 'ion-tabs',
@@ -9,11 +9,9 @@ import { Config, NavOutlet, RouteID, RouteWrite } from '../../interface';
})
export class Tabs implements NavOutlet {
private ids = -1;
private transitioning = false;
private tabsId = (++tabIds);
private leavingTab?: HTMLIonTabElement;
private userTabbarEl?: HTMLIonTabbarElement;
private useRouter = false;
@Element() el!: HTMLStencilElement;
@@ -28,16 +26,6 @@ export class Tabs implements NavOutlet {
*/
@Prop() name?: string;
/**
* If `true`, the tabbar will be hidden. Defaults to `false`.
*/
@Prop() tabbarHidden = false;
/**
* If `true`, the tabs will use the router and `selectedTab` will not do anything.
*/
@Prop({ mutable: true }) useRouter = false;
/**
* Emitted when the tab changes.
*/
@@ -59,13 +47,8 @@ export class Tabs implements NavOutlet {
@Event() ionNavDidChange!: EventEmitter<void>;
async componentWillLoad() {
if (!this.useRouter) {
this.useRouter = !!this.doc.querySelector('ion-router') && !this.el.closest('[no-router]');
}
this.userTabbarEl = this.el.querySelector('ion-tabbar') || undefined;
this.initTabs();
this.useRouter = !!this.doc.querySelector('ion-router') && !this.el.closest('[no-router]');
this.tabs = Array.from(this.el.querySelectorAll('ion-tab'));
this.ionNavWillLoad.emit();
this.componentWillUpdate();
}
@@ -80,28 +63,23 @@ export class Tabs implements NavOutlet {
}
componentWillUpdate() {
const tabbarEl = this.userTabbarEl;
if (tabbarEl) {
tabbarEl.tabs = this.tabs.slice();
tabbarEl.selectedTab = this.selectedTab;
const tabbar = this.el.querySelector('ion-tab-bar');
if (tabbar) {
const tab = this.selectedTab ? this.selectedTab.tab : undefined;
tabbar.selectedTab = tab;
}
}
@Listen('ionTabMutated')
protected onTabMutated() {
this.el.forceUpdate();
}
@Listen('ionTabbarClick')
protected onTabClicked(ev: CustomEvent<HTMLIonTabElement>) {
const selectedTab = ev.detail;
const href = selectedTab.href as string | undefined;
@Listen('ionTabButtonClick')
protected onTabClicked(ev: CustomEvent<TabbarClickDetail>) {
const { href, tab } = ev.detail;
const selectedTab = this.tabs.find(t => t.tab === tab);
if (this.useRouter && href !== undefined) {
const router = this.doc.querySelector('ion-router');
if (router) {
router.push(href);
}
} else {
} else if (selectedTab) {
this.select(selectedTab);
}
}
@@ -110,8 +88,8 @@ export class Tabs implements NavOutlet {
* Index or the Tab instance, of the tab to select.
*/
@Method()
async select(tabOrIndex: number | HTMLIonTabElement): Promise<boolean> {
const selectedTab = await this.getTab(tabOrIndex);
async select(tab: string | HTMLIonTabElement): Promise<boolean> {
const selectedTab = await this.getTab(tab);
if (!this.shouldSwitch(selectedTab)) {
return false;
}
@@ -141,20 +119,21 @@ export class Tabs implements NavOutlet {
/** @internal */
@Method()
async getRouteId(): Promise<RouteID | undefined> {
const id = this.selectedTab && this.selectedTab.name;
return id !== undefined ? { id, element: this.selectedTab } : undefined;
const tabId = this.selectedTab && this.selectedTab.tab;
return tabId !== undefined ? { id: tabId, element: this.selectedTab } : undefined;
}
/** Get the tab at the given index */
@Method()
async getTab(tabOrIndex: string | number | HTMLIonTabElement): Promise<HTMLIonTabElement | undefined> {
if (typeof tabOrIndex === 'string') {
return this.tabs.find(tab => tab.name === tabOrIndex);
async getTab(tab: string | HTMLIonTabElement): Promise<HTMLIonTabElement | undefined> {
const tabEl = (typeof tab === 'string')
? this.tabs.find(t => t.tab === tab)
: tab;
if (!tabEl) {
console.error(`tab with id: "${tabEl}" does not exist`);
}
if (typeof tabOrIndex === 'number') {
return this.tabs[tabOrIndex];
}
return tabOrIndex;
return tabEl;
}
/**
@@ -165,47 +144,13 @@ export class Tabs implements NavOutlet {
return Promise.resolve(this.selectedTab);
}
private initTabs() {
const tabs = this.tabs = Array.from(this.el.querySelectorAll('ion-tab'));
tabs.forEach(tab => {
const id = `t-${this.tabsId}-${++this.ids}`;
tab.btnId = 'tab-' + id;
tab.id = 'tabpanel-' + id;
});
}
private async initSelect(): Promise<void> {
const tabs = this.tabs;
// wait for all tabs to be ready
await Promise.all(tabs.map(tab => tab.componentOnReady()));
if (this.useRouter) {
if (Build.isDev) {
const tab = tabs.find(t => t.selected);
if (tab) {
console.warn('When using a router (ion-router) <ion-tab selected="true"> makes no difference' +
'Define routes properly the define which tab is selected');
}
}
return;
}
// find pre-selected tabs
const selectedTab = tabs.find(t => t.selected) ||
tabs.find(t => t.show && !t.disabled);
// reset all tabs none is selected
for (const tab of tabs) {
if (tab !== selectedTab) {
tab.selected = false;
}
}
if (selectedTab) {
await selectedTab.setActive();
}
this.selectedTab = selectedTab;
if (selectedTab) {
selectedTab.selected = true;
selectedTab.active = true;
}
// wait for all tabs to be ready
await Promise.all(this.tabs.map(tab => tab.componentOnReady()));
await this.select(this.tabs[0]);
}
private setActive(selectedTab: HTMLIonTabElement): Promise<void> {
@@ -213,13 +158,6 @@ export class Tabs implements NavOutlet {
return Promise.reject('transitioning already happening');
}
// Reset rest of tabs
for (const tab of this.tabs) {
if (selectedTab !== tab) {
tab.selected = false;
}
}
this.transitioning = true;
this.leavingTab = this.selectedTab;
this.selectedTab = selectedTab;
@@ -237,7 +175,6 @@ export class Tabs implements NavOutlet {
return;
}
selectedTab.selected = true;
if (leavingTab !== selectedTab) {
if (leavingTab) {
leavingTab.active = false;
@@ -262,28 +199,12 @@ export class Tabs implements NavOutlet {
return selectedTab !== undefined && selectedTab !== leavingTab && !this.transitioning;
}
hostData() {
return {
class: {
'tabbar-hidden': this.tabbarHidden
}
};
}
render() {
return [
<div class="tabs-inner">
<slot></slot>
</div>,
<slot name="tabbar">
<ion-tabbar
tabs={this.tabs.slice()}
selectedTab={this.selectedTab}
>
</ion-tabbar>
</slot>
<slot name="tabbar"></slot>
];
}
}
let tabIds = -1;

View File

@@ -1,34 +0,0 @@
'use strict';
const { register, Page, platforms } = require('../../../../../scripts/e2e');
const { getElement, waitAndGetElementById, waitForTransition } = require('../../../../../scripts/e2e/utils');
class E2ETestPage extends Page {
constructor(driver, platform) {
super(driver, `http://localhost:3333/src/components/tabs/test/basic?ionic:mode=${platform}`);
}
}
platforms.forEach(platform => {
describe('tabs/basic', () => {
register('should init', driver => {
const page = new E2ETestPage(driver, platform);
return page.navigate();
});
// register('should check each tab', async (driver, testContext) => {
// testContext.timeout(60000);
// const page = new E2ETestPage(driver, platform);
// await waitForTransition(300);
// const tabTwoButton = await waitAndGetElementById(driver, 'tab-t-0-1');
// tabTwoButton.click();
// await waitForTransition(600);
// const tabThreeButton = await waitAndGetElementById(driver, 'tab-t-0-2');
// tabThreeButton.click();
// await waitForTransition(600);
// });
});
});

View File

@@ -0,0 +1,25 @@
import { newE2EPage } from '@stencil/core/testing';
it('tab-group: basic', async () => {
const page = await newE2EPage({
url: '/src/components/tab-group/test/basic?ionic:_testing=true'
});
let compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
const button2 = await page.find('.e2eTabTwoButton');
await button2.click();
compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
const button3 = await page.find('.e2eTabThreeButton');
await button3.click();
compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
const button4 = await page.find('.e2eTabFourButton');
await button4.click();
compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -14,7 +14,8 @@
<ion-app>
<ion-tabs>
<ion-tab label="Plain List" icon="star">
<ion-tab tab="tab-one">
<ion-header>
<ion-toolbar>
<ion-title>Tab One</ion-title>
@@ -22,13 +23,12 @@
</ion-header>
<ion-content padding>
<h1>Tab One</h1>
<ion-button expand="block" onclick="updateBadgeCount()">Update Badge Count</ion-button>
<ion-button color="secondary" expand="block" onclick="updateBadgeColor()">Update Badge Color</ion-button>
</ion-content>
</ion-tab>
<ion-tab label="Schedule" icon="globe" badge="6" badge-color="danger">
<ion-tab tab="schedule">
<ion-header>
<ion-toolbar>
<ion-title>Tab Two</ion-title>
@@ -39,7 +39,7 @@
</ion-content>
</ion-tab>
<ion-tab id="tabWithBadge" badge="6" badge-color="primary" label="Stopwatch" icon="logo-facebook">
<ion-tab tab="tab-three">
<ion-header>
<ion-toolbar>
<ion-title>Tab Three</ion-title>
@@ -50,7 +50,7 @@
</ion-content>
</ion-tab>
<ion-tab id="hiddenTab" label="Hidden" icon="alert" show="false">
<ion-tab tab="hidden-tab">
<ion-header>
<ion-toolbar>
<ion-title>Hidden Tab</ion-title>
@@ -61,25 +61,58 @@
</ion-content>
</ion-tab>
<ion-tab disabled label="Messages" icon="chatboxes" component="page-one">
</ion-tab>
<ion-tab tab="tab-four" component="page-one"></ion-tab>
<ion-tabbar></ion-tabbar>
<ion-tab-bar>
<ion-tab-button href="" tab="tab-one" class="e2eTabOneButton">
<ion-label>Plain List</ion-label>
<ion-icon name="star"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="schedule" class="e2eTabTwoButton">
<ion-label>Schedule</ion-label>
<ion-icon name="globe"></ion-icon>
<ion-badge color="danger">6</ion-badge>
</ion-tab-button>
<ion-tab-button tab="tab-three" class="e2eTabThreeButton" disabled>
<ion-label>Stopwatch</ion-label>
<ion-icon name="logo-facebook"></ion-icon>
<ion-badge color="primary">6</ion-badge>
</ion-tab-button>
<ion-tab-button tab="hidden-tab" hidden>
<ion-label>Hidden</ion-label>
<ion-icon name="alert"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-four" class="e2eTabFourButton">
<ion-label>Messages</ion-label>
<ion-icon name="chatboxes"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-app>
<script>
var tab = document.getElementById('tabWithBadge');
function updateBadgeCount() {
var current = parseInt(tab.badge);
tab.badge = ++current;
class PageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Page Four</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Four
</ion-content>
`;
}
}
customElements.define('page-one', PageOne);
function updateBadgeColor() {
var current = tab.badgeColor;
tab.badgeColor = current === 'primary' ? 'danger' : 'primary';
}
</script>
</body>

View File

@@ -1,34 +0,0 @@
'use strict';
const { register, Page, platforms } = require('../../../../../scripts/e2e');
const { getElement, waitAndGetElementById, waitForTransition } = require('../../../../../scripts/e2e/utils');
class E2ETestPage extends Page {
constructor(driver, platform) {
super(driver, `http://localhost:3333/src/components/tabs/test/colors?ionic:mode=${platform}`);
}
}
platforms.forEach(platform => {
describe('tabs/colors', () => {
register('should init', driver => {
const page = new E2ETestPage(driver, platform);
return page.navigate();
});
// register('should check each tab', async (driver, testContext) => {
// testContext.timeout(60000);
// const page = new E2ETestPage(driver, platform);
// await waitForTransition(300);
// const tabTwoButton = await waitAndGetElementById(driver, 'tab-t-0-1');
// tabTwoButton.click();
// await waitForTransition(600);
// const tabThreeButton = await waitAndGetElementById(driver, 'tab-t-0-2');
// tabThreeButton.click();
// await waitForTransition(600);
// });
});
});

View File

@@ -1,114 +0,0 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Tab - Colors</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../dist/ionic.js"></script>
</head>
<body>
<ion-app>
<!-- Default -->
<ion-tabs>
<ion-tab label="Recents"></ion-tab>
<ion-tab label="Favorites" badge="6"></ion-tab>
<ion-tab label="Settings"></ion-tab>
</ion-tabs>
<!-- Icons -->
<ion-tabs selected-index="1">
<ion-tab icon="call"></ion-tab>
<ion-tab icon="heart"></ion-tab>
<ion-tab icon="settings"></ion-tab>
<ion-tabbar color="primary"></ion-tabbar>
</ion-tabs>
<!-- Icons on top of text -->
<ion-tabs selected-index="2">
<ion-tab label="Location" icon="navigate"></ion-tab>
<ion-tab label="Favorites" badge="6" icon="heart"></ion-tab>
<ion-tab label="Radio" icon="musical-notes"></ion-tab>
<ion-tabbar color="secondary"></ion-tabbar>
</ion-tabs>
<!-- Icons below text -->
<ion-tabs selected-index="1">
<ion-tab label="Recents" icon="call"></ion-tab>
<ion-tab label="Favorites" badge="6" icon="heart"></ion-tab>
<ion-tab label="Settings" icon="settings"></ion-tab>
<ion-tabbar layout="icon-bottom" color="dark"></ion-tabbar>
</ion-tabs>
<!-- Icons right of text -->
<ion-tabs selected-index="0">
<ion-tab label="Recents" icon="call"></ion-tab>
<ion-tab label="Favorites" badge="6" icon="heart"></ion-tab>
<ion-tab label="Settings" icon="settings"></ion-tab>
<ion-tabbar layout="icon-end" color="danger"></ion-tabbar>
</ion-tabs>
<!-- Icons left of text -->
<ion-tabs>
<ion-tab label="Recents" icon="call"></ion-tab>
<ion-tab label="Favorites" badge="6" badge-color="danger" icon="heart"></ion-tab>
<ion-tab label="Settings" icon="settings"></ion-tab>
<ion-tabbar layout="icon-start" color="light"></ion-tabbar>
</ion-tabs>
<!-- No icons -->
<ion-tabs>
<ion-tab label="Recents" icon="call"></ion-tab>
<ion-tab label="Favorites" badge="6" badge-color="danger" icon="heart"></ion-tab>
<ion-tab label="Settings" icon="settings"></ion-tab>
<ion-tabbar layout="icon-hide" color="primary"></ion-tabbar>
</ion-tabs>
<!-- No label -->
<ion-tabs>
<ion-tab label="Recents" icon="call"></ion-tab>
<ion-tab label="Favorites" badge="6" badge-color="danger" icon="heart"></ion-tab>
<ion-tab label="Settings" icon="settings"></ion-tab>
<ion-tabbar layout="label-hide" color="secondary"></ion-tabbar>
</ion-tabs>
<!-- No overflow text -->
<ion-tabs>
<ion-tab label="Indiana Jones and the Raiders of the Lost Ark"></ion-tab>
<ion-tab label="Indiana Jones and the Temple of Doom"></ion-tab>
<ion-tab label="Indiana Jones and the Last Crusade"></ion-tab>
<ion-tabbar color="danger"></ion-tabbar>
</ion-tabs>
</ion-app>
<style>
ion-tabs {
position: relative;
height: 60px;
}
</style>
</body>
</html>

View File

@@ -252,19 +252,36 @@
<body>
<ion-app useRouter='false'>
<ion-app>
<ion-tabs>
<ion-tab label="Plain List" icon="star" path="">
<ion-tab tab="plain-list">
<ion-nav root="page-one"></ion-nav>
</ion-tab>
<ion-tab label="Schedule" icon="globe" path="tab2">
<ion-tab tab="schedule">
<ion-nav root="page-four"></ion-nav>
</ion-tab>
<ion-tab label="Stopwatch" icon="logo-facebook" path="tab3">
<ion-tab tab="stopwatch">
<ion-nav root="page-seven"></ion-nav>
</ion-tab>
<ion-tab-bar>
<ion-tab-button tab="plain-list">
<ion-label>Plain List</ion-label>
<ion-icon name="star"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="schedule">
<ion-label>Schedule</ion-label>
<ion-icon name="globe"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="stopwatch">
<ion-label>Stopwatch</ion-label>
<ion-icon name="logo-facebook"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-app>
</body>

View File

@@ -1,34 +0,0 @@
'use strict';
const { register, Page, platforms } = require('../../../../../scripts/e2e');
const { getElement, waitAndGetElementById, waitForTransition } = require('../../../../../scripts/e2e/utils');
class E2ETestPage extends Page {
constructor(driver, platform) {
super(driver, `http://localhost:3333/src/components/tabs/test/placements?ionic:mode=${platform}`);
}
}
platforms.forEach(platform => {
describe('tabs/placements', () => {
register('should init', driver => {
const page = new E2ETestPage(driver, platform);
return page.navigate();
});
// register('should check each tab', async (driver, testContext) => {
// testContext.timeout(60000);
// const page = new E2ETestPage(driver, platform);
// await waitForTransition(300);
// const tabTwoButton = await waitAndGetElementById(driver, 'tab-t-0-1');
// tabTwoButton.click();
// await waitForTransition(600);
// const tabThreeButton = await waitAndGetElementById(driver, 'tab-t-0-2');
// tabThreeButton.click();
// await waitForTransition(600);
// });
});
});

View File

@@ -0,0 +1,10 @@
import { newE2EPage } from '@stencil/core/testing';
it('tab-group: placements', async () => {
const page = await newE2EPage({
url: '/src/components/tab-group/test/placements?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -14,7 +14,7 @@
<ion-app>
<ion-tabs>
<ion-tab icon="star">
<ion-tab tab="star-tab">
<ion-header>
<ion-toolbar>
@@ -27,7 +27,7 @@
</ion-tab>
<ion-tab icon="globe">
<ion-tab tab="globe-tab">
<ion-header>
<ion-toolbar>
@@ -40,7 +40,7 @@
</ion-tab>
<ion-tab icon="logo-facebook">
<ion-tab tab="facebook-tab">
<ion-header>
<ion-toolbar>
@@ -53,10 +53,23 @@
</ion-tab>
<ion-tab disabled icon="chatboxes" component="page-one"></ion-tab>
<ion-tab tab="page-one" disabled icon="chatboxes" component="page-one"></ion-tab>
<ion-tabbar placement="top"></ion-tabbar>
<ion-tab-bar placement="top">
<ion-tab-button tab="star-tab">
<ion-icon name="star"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="globe-tab">
<ion-icon name="globe"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="facebook-tab">
<ion-icon name="logo-facebook"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-app>
</body>

View File

@@ -14,8 +14,8 @@
<ion-app>
<ion-tabs>
<ion-tab label="Plain List" icon="star" path="">
<ion-tab tab="tab-one">
<ion-header translucent>
<ion-toolbar>
<ion-title>Tab One</ion-title>
@@ -24,10 +24,9 @@
<ion-content padding fullscreen>
Tab One
</ion-content>
</ion-tab>
<ion-tab label="Schedule" icon="globe" path="tab2">
<ion-tab tab="tab-two">
<ion-header translucent>
<ion-toolbar>
@@ -40,7 +39,7 @@
</ion-tab>
<ion-tab label="Stopwatch" icon="logo-facebook" path="tab3">
<ion-tab tab="tab-three">
<ion-header translucent>
<ion-toolbar>
@@ -53,8 +52,29 @@
</ion-tab>
<ion-tab disabled label="Messages" icon="chatboxes" component="page-one" path="tab4">
</ion-tab>
<ion-tab tab="tab-four" component="page-one"></ion-tab>
<ion-tab-bar>
<ion-tab-button tab="tab-one">
<ion-label>Plain List</ion-label>
<ion-icon name="star"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-two">
<ion-label>Schedule</ion-label>
<ion-icon name="globe"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-three">
<ion-label>Stopwatch</ion-label>
<ion-icon name="logo-facebook"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-four">
<ion-label>Messages</ion-label>
<ion-icon name="chatboxes"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
<style>

View File

@@ -1,93 +0,0 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Tab - Translucent</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../dist/ionic.js"></script>
<script type="text/javascript">
window.Ionic.config = { useRouter: true };
</script>
</head>
<body>
<ion-app>
<ion-tabs>
<ion-tab label="Today" icon="calendar" path="" root="translucent-page-tab">
<ion-route path="" component="translucent-page-tab"></ion-route>
</ion-tab>
<ion-tab label="Games" icon="planet" root="page-two">
<ion-route path="" component="page-two"></ion-route>
<ion-route path="path/to/page/three" component="page-three"></ion-route>
</ion-tab>
<ion-tab label="Apps" icon="logo-buffer" root="page-three">
<ion-route path="/" component="page-three"></ion-route>
</ion-tab>
<ion-tab label="Updates" icon="download" badge="7" badge-style="danger" root="translucent-page-tab">
<ion-route component="page-one"></ion-route>
<ion-route path="paginaaaa-two" component="page-two"></ion-route>
</ion-tab>
<ion-tab label="Search" icon="search">
<ion-route path="" component="translucent-page-tab"></ion-route>
</ion-tab>
<ion-tabbar color="primary" translucent></ion-tabbar>
</ion-tabs>
<ion-nav-controller></ion-nav-controller>
</ion-app>
<style>
f {
display: block;
background: blue;
width: 100%;
height: 200px;
margin: 20px auto;
}
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
</style>
</body>
</html>

View File

@@ -15,13 +15,25 @@
<body>
<ion-tabs>
<ion-tab label="Tab One" icon="star" path="">
<ion-tab tab="tab-one">
<div class="div-one" slot="not-initialized">Div One</div>
</ion-tab>
<ion-tab label="Tab Two" icon="globe" path="tab2">
<ion-tab tab="tab-two">
<div class="div-two" slot="not-initialized">Div Two</div>
</ion-tab>
<ion-tab-bar>
<ion-tab-button tab="tab-one">
<ion-label>Tab One</ion-label>
<ion-icon name="star"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-two">
<ion-label>Tab Two</ion-label>
<ion-icon name="globe"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
<style>

View File

@@ -34,7 +34,7 @@ if (config.getBoolean('persistConfig')) {
// first see if the mode was set as an attribute on <html>
// which could have been set by the user, or by prerendering
// otherwise get the mode via config settings, and fallback to md
const documentElement = document.documentElement;
const documentElement = document.documentElement!;
const mode = config.get('mode', documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md'));
Ionic.mode = Context.mode = mode;
config.set('mode', mode);

View File

@@ -18,7 +18,7 @@ export * from './components/content/content-interface';
export * from './components/select/select-interface';
export * from './components/select-popover/select-popover-interface';
export * from './components/spinner/spinner-interface';
export * from './components/tabbar/tabbar-interface';
export * from './components/tab-bar/tab-bar-interface';
export * from './components/toast/toast-interface';
export * from './components/virtual-scroll/virtual-scroll-interface';

View File

@@ -25,7 +25,7 @@ export function renderHiddenInput(container: HTMLElement, name: string, value: s
if (hasShadowDom(container)) {
let input = container.querySelector('input.aux-input') as HTMLInputElement | null;
if (!input) {
input = container.ownerDocument.createElement('input');
input = container.ownerDocument!.createElement('input');
input.type = 'hidden';
input.classList.add('aux-input');
container.appendChild(input);

View File

@@ -21,7 +21,7 @@ export function relocateInput(
// before it receives the actual focus event
// We hide the focused input (with the visible caret) invisiable by making it scale(0),
cloneInputComponent(componentEl, inputEl);
const doc = componentEl.ownerDocument;
const doc = componentEl.ownerDocument!;
const tx = doc.dir === 'rtl' ? 9999 : -9999;
inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0)`;
// TODO
@@ -33,7 +33,7 @@ export function relocateInput(
}
export function isFocused(input: HTMLInputElement): boolean {
return input === input.ownerDocument.activeElement;
return input === input.ownerDocument!.activeElement;
}
function removeClone(componentEl: HTMLElement, inputEl: HTMLElement) {
@@ -55,7 +55,7 @@ function cloneInputComponent(componentEl: HTMLElement, inputEl: HTMLInputElement
// find its parent wrapping component like <ion-input> or <ion-textarea>
// then clone the entire component
const parentElement = componentEl.parentElement;
const doc = componentEl.ownerDocument;
const doc = componentEl.ownerDocument!;
if (componentEl && parentElement) {
// DOM READ
const srcTop = componentEl.offsetTop;

View File

@@ -42,7 +42,7 @@ export const config: Config = {
{ components: ['ion-slides', 'ion-slide'] },
{ components: ['ion-spinner'] },
{ components: ['ion-split-pane'] },
{ components: ['ion-tabs', 'ion-tab', 'ion-tabbar'] },
{ components: ['ion-tabs', 'ion-tab', 'ion-tab-bar', 'ion-tab-button'] },
{ components: ['ion-text'] },
{ components: ['ion-toast', 'ion-toast-controller'] },
{ components: ['ion-toggle'] },