diff --git a/packages/core/src/components/tabs/tab-bar.tsx b/packages/core/src/components/tabs/tab-bar.tsx new file mode 100644 index 0000000000..ba1642439f --- /dev/null +++ b/packages/core/src/components/tabs/tab-bar.tsx @@ -0,0 +1,55 @@ +import { Component, h } from '@stencil/core'; +import { VNodeData } from '../../utils/interfaces'; + + + +@Component({ + tag: 'ion-tab-bar', + host: { + theme: 'tabbar' + } +}) +export class TabBar { + @Prop() tabs: [Tab] = []; + + @Prop() onTabSelected: Function; + + @Prop() selectedIndex: number = 0; + + /** + * @prop {string} Set the tabbar layout: `icon-top`, `icon-start`, `icon-end`, `icon-bottom`, `icon-hide`, `title-hide`. + */ + @Prop() tabsLayout: string = 'icon-top' + + hostData(): VNodeData { + return { + attrs: { + 'role': 'tablist' + }, + class: { + 'tabbar': true + } + } + } + + handleTabButtonClick(tab, index) { + this.onTabSelected && this.onTabSelected(tab, index); + } + + render() { + return ( +
+ {this.tabs.map((tab, index) => { + return ( + + ) + })} +
+ ) + } +} diff --git a/packages/core/src/components/tabs/tab-button.tsx b/packages/core/src/components/tabs/tab-button.tsx new file mode 100644 index 0000000000..4130bc7005 --- /dev/null +++ b/packages/core/src/components/tabs/tab-button.tsx @@ -0,0 +1,78 @@ +import { Component, h } from '@stencil/core'; +import { VNodeData } from '../../utils/interfaces'; + +@Component({ + tag: 'ion-tab-button', + host: { + theme: 'tab-button' + } +}) +export class TabButton { + @Prop() tab: Tab; + + @Prop() layout: string; + + @Prop() selectedIndex: number; + + @Prop() index: number; + + hostData(): VNodeData { + const tab = this.tab; + if(!tab) return {}; + + // attr.id + // attr.aria-controls + + const hasTitle = !!tab.tabTitle; + const hasIcon = !!tab.tabIcon && this.layout !== 'icon-hide'; + const hasTitleOnly = (hasTitle && !hasIcon); + const hasIconOnly = (hasIcon && !hasTitle); + const hasBadge = !!tab.tabBadge; + // class.disable-hover + // class.tab-disabled + // class.tab-hidden + + return { + attrs: { + 'aria-selected': this.selectedIndex == this.index + }, + class: { + 'has-title': hasTitle, + 'has-icon': hasIcon, + 'has-title-only': hasTitleOnly, + 'has-icon-only': hasIconOnly, + 'has-badge': hasBadge + } + }; + } + + render() { + if(!this.tab) { + return null; + } + + const tab = this.tab + + // TODO: Apply these on host? + let { id, ariaControls, ariaSelected, hasTitle, hasIcon, hasTitleOnly, + iconOnly, hasBadge, disableHover, tabDisabled, tabHidden } = {}; + + const items = [] + + if(tab.tabIcon) { + items.push(); + } + + if(tab.tabTitle) { + items.push({tab.tabTitle}); + } + + if(tab.tabBadge) { + items.push({tab.tabBadge}); + } + + items.push(
); + + return (items) + } +} diff --git a/packages/core/src/components/tabs/tab-highlight.tsx b/packages/core/src/components/tabs/tab-highlight.tsx new file mode 100644 index 0000000000..fc09f7c209 --- /dev/null +++ b/packages/core/src/components/tabs/tab-highlight.tsx @@ -0,0 +1,13 @@ +import { Component, h } from '@stencil/core'; + + +@Component({ + tag: 'ion-tab-highlight' +}) +export class TabHighlight { + render() { + return ( +
+ ); + } +} diff --git a/packages/core/src/components/tabs/tab.tsx b/packages/core/src/components/tabs/tab.tsx new file mode 100644 index 0000000000..df600f8caa --- /dev/null +++ b/packages/core/src/components/tabs/tab.tsx @@ -0,0 +1,110 @@ +import { Component, h, Ionic } from '@stencil/core'; +import { VNodeData } from '../../utils/interfaces'; + + +@Component({ + tag: 'ion-tab', + host: { + theme: 'tab' + } +}) +export class Tab { + /** + * @prop {Page} Set the root component for this tab. + */ + @Prop() root: string; + + /** + * @prop {object} Any nav-params to pass to the root componentof this tab. + */ + @Prop() rootParams: any; + + /** + * @prop {boolean} If true, the tab is selected + */ + @State() isSelected: Boolean = false; + + /** + * @prop {string} The title of the tab button. + */ + @Prop() tabTitle: string; + + /** + * @prop {string} The icon for the tab button. + */ + @Prop() tabIcon: string; + + /** + * @prop {string} The badge for the tab button. + */ + @Prop() tabBadge: string; + + /** + * @prop {string} The badge color for the tab button. + */ + @Prop() tabBadgeStyle: string; + + /** + * @prop {boolean} If true, enable the tab. If false, + * the user cannot interact with this element. + * Default: `true`. + */ + @Prop() enabled: boolean = true; + + /** + * @prop {boolean} If true, the tab button is visible within the + * tabbar. Default: `true`. + */ + @Prop() shown: boolean = true; + + /** + * @prop {boolean} If true, hide the tabs on child pages. + */ + @Prop() tabsHideOnSubPages: boolean = false; + + /** + * @prop {Tab} Emitted when the current tab is selected. + */ + @Prop() onSelected: Function; + + hostData(): VNodeData { + return { + style: { + display: !this.isSelected && 'none' || '' + }, + attrs: { + 'role': 'tabpanel' + //'id': _tabId, + //aria-labelledby: _btnId + }, + class: { + } + }; + } + + ionViewDidLoad() { + setTimeout(() => { + Ionic.emit(this, 'ionTabDidLoad', { + detail: { + tab: this + } + }) + }, 0) + } + + ionViewDidUnload() { + Ionic.emit(this, 'ionTabDidLoad', { + detail: { + tab: this + } + }) + } + + render() { + const RootComponent = this.root; + return [ + , + + ] + } +} diff --git a/packages/core/src/components/tabs/tabs.ios.scss b/packages/core/src/components/tabs/tabs.ios.scss new file mode 100644 index 0000000000..8fe133287b --- /dev/null +++ b/packages/core/src/components/tabs/tabs.ios.scss @@ -0,0 +1,175 @@ +@import "../../themes/ionic.globals.ios"; +@import "./tabs"; + +// iOS Tabs +// -------------------------------------------------- + +/// @prop - Border on the tabbar (border-top when [tabsPlacement=bottom] and border-bottom when [tabsPlacement=top]) +$tabs-ios-border: $hairlines-width solid $tabs-ios-border-color !default; + +// deprecated +$tabs-ios-tab-padding: null !default; + +/// @prop - Padding top on the tab button +$tabs-ios-tab-padding-top: 0 !default; + +/// @prop - Padding end on the tab button +$tabs-ios-tab-padding-end: 2px !default; + +/// @prop - Padding bottom on the tab button +$tabs-ios-tab-padding-bottom: $tabs-ios-tab-padding-top !default; + +/// @prop - Padding start on the tab button +$tabs-ios-tab-padding-start: $tabs-ios-tab-padding-end !default; + +/// @prop - Max width of the tab button +$tabs-ios-tab-max-width: 240px !default; + +/// @prop - Minimum height of the tab button +$tabs-ios-tab-min-height: 49px !default; + +/// @prop - Text color of the inactive tab button +$tabs-ios-tab-text-color: $tabs-ios-tab-color-inactive !default; + +/// @prop - Text color of the active tab button +$tabs-ios-tab-text-color-active: $tabs-ios-tab-color-active !default; + +/// @prop - Icon color of the inactive tab button +$tabs-ios-tab-icon-color: $tabs-ios-tab-color-inactive !default; + +/// @prop - Icon color of the active tab button +$tabs-ios-tab-icon-color-active: $tabs-ios-tab-color-active !default; + +/// @prop - Font size of the tab button text +$tabs-ios-tab-font-size: 10px !default; + +/// @prop - Size of the tab button icon +$tabs-ios-tab-icon-size: 30px !default; + + +.tabs-ios .tabbar { + justify-content: center; + + border-top: $tabs-ios-border; + background: $tabs-ios-background; +} + +.tabs-ios[tabsPlacement=top] .tabbar { + border-top: 0; + border-bottom: $tabs-ios-border; +} + +.tabs-ios .tab-button { + max-width: $tabs-ios-tab-max-width; + min-height: $tabs-ios-tab-min-height; + + font-size: $tabs-ios-tab-font-size; + color: $tabs-ios-tab-text-color; + + @include deprecated-variable(padding, $tabs-ios-tab-padding) { + @include padding($tabs-ios-tab-padding-top, $tabs-ios-tab-padding-end, $tabs-ios-tab-padding-bottom, $tabs-ios-tab-padding-start); + } +} + +.tabs-ios .tab-button:hover:not(.disable-hover), +.tabs-ios .tab-button[aria-selected=true] { + color: $tabs-ios-tab-text-color-active; +} + +.tabs-ios .tab-button[aria-selected=true] .tab-button-icon { + color: $tabs-ios-tab-icon-color-active; +} + +.tabs-ios .tab-button-text { + @include margin(0, null, 1px, null); + + min-height: $tabs-ios-tab-font-size + 1; +} + +.tabs-ios .has-title-only .tab-button-text { + font-size: $tabs-ios-tab-font-size + 2; +} + +.tabs-ios .tab-button-icon { + @include margin(4px, null, 1px, null); + + min-width: $tabs-ios-tab-icon-size + 5; + height: $tabs-ios-tab-icon-size; + + font-size: $tabs-ios-tab-icon-size; + + color: $tabs-ios-tab-icon-color; +} + +.tabs-ios .tab-button-icon::before { + vertical-align: top; +} + +.tabs-ios[tabsLayout=icon-right] .tab-button .tab-button-text, // deprecated +.tabs-ios[tabsLayout=icon-left] .tab-button .tab-button-text, // deprecated +.tabs-ios[tabsLayout=icon-end] .tab-button .tab-button-text, +.tabs-ios[tabsLayout=icon-start] .tab-button .tab-button-text { + font-size: 1.4rem; + line-height: 1.1; +} + +.tabs-ios[tabsLayout=icon-right] .tab-button ion-icon, // deprecated +.tabs-ios[tabsLayout=icon-left] .tab-button ion-icon, // deprecated +.tabs-ios[tabsLayout=icon-end] .tab-button ion-icon, +.tabs-ios[tabsLayout=icon-start] .tab-button ion-icon { + min-width: 24px; + height: 26px; + + font-size: 24px; +} + +.tabs-ios[tabsLayout=icon-hide] .tab-button, +.tabs-ios .tab-button.has-title-only { + min-height: $tabs-ios-tab-min-height - 8; +} + +.tabs-ios[tabsLayout=icon-hide] .tab-button .tab-button-text, +.tabs-ios .tab-button.has-title-only .tab-button-text { + @include margin(2px, 0); + + font-size: 1.4rem; + line-height: 1.1; +} + +.tabs-ios[tabsLayout=title-hide] .tab-button, +.tabs-ios .tab-button.icon-only { + min-height: $tabs-ios-tab-min-height - 8; +} + + +// iOS Tabbar Color Mixin +// -------------------------------------------------- + +@mixin tabbar-ios($color-name, $color-base, $color-contrast) { + + .tabs-ios-#{$color-name} .tabbar { + border-color: darken($color-base, 10%); + background-color: $color-base; + } + + .tabs-ios-#{$color-name} .tab-button, + .tabs-ios-#{$color-name} .tab-button-icon, + .tabs-ios-#{$color-name} .tab-button:hover:not(.disable-hover), + .tabs-ios-#{$color-name} .tab-button:hover:not(.disable-hover) .tab-button-icon { + color: rgba($color-contrast, .7); + } + + .tabs-ios-#{$color-name} .tab-button[aria-selected=true], + .tabs-ios-#{$color-name} .tab-button[aria-selected=true] .tab-button-icon { + color: $color-contrast; + } + +} + + +// iOS Tabbar Color Generation +// -------------------------------------------------- + +@each $color-name, $color-base, $color-contrast in get-colors($colors-ios) { + @include tabbar-ios($color-name, $color-base, $color-contrast); +} diff --git a/packages/core/src/components/tabs/tabs.md.scss b/packages/core/src/components/tabs/tabs.md.scss new file mode 100644 index 0000000000..ba1d291ab9 --- /dev/null +++ b/packages/core/src/components/tabs/tabs.md.scss @@ -0,0 +1,344 @@ +@import "../../themes/ionic.globals.md"; +@import "./tabs"; + + +// Material Design Tabs +// -------------------------------------------------- + +// deprecated +$tabs-md-tab-padding: null !default; + +/// @prop - Padding top on the tab button +$tabs-md-tab-padding-top: 0 !default; + +/// @prop - Padding end on the tab button +$tabs-md-tab-padding-end: $tabs-md-tab-padding-top !default; + +/// @prop - Padding bottom on the tab button +$tabs-md-tab-padding-bottom: $tabs-md-tab-padding-top !default; + +/// @prop - Padding start on the tab button +$tabs-md-tab-padding-start: $tabs-md-tab-padding-end !default; + +/// @prop - Minimum height of the tab button +$tabs-md-tab-min-height: 5.6rem !default; + +/// @prop - Font size of the inactive tab button text +$tabs-md-tab-font-size: 1.2rem !default; + +/// @prop - Font weight of the tab button text +$tabs-md-tab-font-weight: normal !default; + +/// @prop - Opacity of the inactive tab button +$tabs-md-tab-opacity: .7 !default; + +/// @prop - Text color of the inactive tab button +$tabs-md-tab-text-color: rgba($tabs-md-tab-color-inactive, $tabs-md-tab-opacity) !default; + +/// @prop - Text color of the active tab button +$tabs-md-tab-text-color-active: $tabs-md-tab-color-active !default; + +/// @prop - Icon color of the inactive tab button +$tabs-md-tab-icon-color: rgba($tabs-md-tab-color-inactive, $tabs-md-tab-opacity) !default; + +/// @prop - Icon color of the active tab button +$tabs-md-tab-icon-color-active: $tabs-md-tab-color-active !default; + +// deprecated +$tabs-md-tab-padding-active: null !default; + +/// @prop - Padding top of the active tab button +$tabs-md-tab-padding-active-top: 0 !default; + +/// @prop - Padding end of the active tab button +$tabs-md-tab-padding-active-end: $tabs-md-tab-padding-active-top !default; + +/// @prop - Padding bottom of the active tab button +$tabs-md-tab-padding-active-bottom: $tabs-md-tab-padding-active-top !default; + +/// @prop - Padding start of the active tab button +$tabs-md-tab-padding-active-start: $tabs-md-tab-padding-active-end !default; + +/// @prop - Font size of the active tab button text +$tabs-md-tab-font-size-active: 1.4rem !default; + +// deprecated +$tabs-md-tab-text-margin: null !default; + +/// @prop - Margin top on the tab button text +$tabs-md-tab-text-margin-top: 0 !default; + +/// @prop - Margin end on the tab button text +$tabs-md-tab-text-margin-end: $tabs-md-tab-text-margin-top !default; + +/// @prop - Margin bottom on the tab button text +$tabs-md-tab-text-margin-bottom: $tabs-md-tab-text-margin-top !default; + +/// @prop - Margin start on the tab button text +$tabs-md-tab-text-margin-start: $tabs-md-tab-text-margin-end !default; + +/// @prop - Capitalization of the tab button text +$tabs-md-tab-text-capitalization: none !default; + +// deprecated +$tabs-md-tab-text-transform-origin: null !default; + +/// @prop - Transform origin x for the tab button text +$tabs-md-tab-text-transform-origin-x: 50% !default; + +/// @prop - Transform origin y for the tab button text +$tabs-md-tab-text-transform-origin-y: 80% !default; + +/// @prop - Transform for the active tab button text +$tabs-md-tab-text-transform-active: scale3d($tabs-md-tab-font-size-active / $tabs-md-tab-font-size, $tabs-md-tab-font-size-active / $tabs-md-tab-font-size, 1) !default; + +/// @prop - Text transition for the tab button text +$tabs-md-tab-text-transition: transform .3s ease-in-out !default; + +// deprecated +$tabs-md-tab-icon-transform-active: null !default; + +/// @prop - Transform x for the active tab button icon when the layout is icon-top, icon-only, or title-only +$tabs-md-tab-icon-transform-x-active: 0 !default; + +/// @prop - Transform y for the active tab button icon when the layout is icon-top, icon-only, or title-only +$tabs-md-tab-icon-transform-y-active: -2px !default; + +/// @prop - Transform z for the active tab button icon when the layout is icon-top, icon-only, or title-only +$tabs-md-tab-icon-transform-z-active: 0 !default; + +// deprecated +$tabs-md-tab-icon-end-transform-active: null !default; + +/// @prop - Transform x for the active tab button icon when the layout is icon-right +$tabs-md-tab-icon-right-transform-x-active: 2px !default; + +/// @prop - Transform y for the active tab button icon when the layout is icon-right +$tabs-md-tab-icon-right-transform-y-active: 0 !default; + +/// @prop - Transform z for the active tab button icon when the layout is icon-right +$tabs-md-tab-icon-right-transform-z-active: 0 !default; + +// deprecated +$tabs-md-tab-icon-bottom-transform-active: null !default; + +/// @prop - Transform x for the active tab button icon when the layout is icon-bottom +$tabs-md-tab-icon-bottom-transform-x-active: 0 !default; + +/// @prop - Transform y for the active tab button icon when the layout is icon-bottom +$tabs-md-tab-icon-bottom-transform-y-active: 2px !default; + +/// @prop - Transform z for the active tab button icon when the layout is icon-bottom +$tabs-md-tab-icon-bottom-transform-z-active: 0 !default; + +// deprecated +$tabs-md-tab-icon-start-transform-active: null !default; + +/// @prop - Transform x for the active tab button icon when the layout is icon-left +$tabs-md-tab-icon-left-transform-x-active: -2px !default; + +/// @prop - Transform y for the active tab button icon when the layout is icon-left +$tabs-md-tab-icon-left-transform-y-active: 0 !default; + +/// @prop - Transform z for the active tab button icon when the layout is icon-left +$tabs-md-tab-icon-left-transform-z-active: 0 !default; + +// deprecated +$tabs-md-tab-icon-transform-origin: null !default; + +/// @prop - Transform origin x for the tab button text +$tabs-md-tab-icon-transform-origin-x: 50% !default; + +/// @prop - Transform origin y for the tab button text +$tabs-md-tab-icon-transform-origin-y: 150% !default; + +/// @prop - Text transition for the tab button icon +$tabs-md-tab-icon-transition: transform .3s ease-in-out !default; + +/// @prop - Size of the tab button icon +$tabs-md-tab-icon-size: 2.4rem !default; + + +.tabs-md .tabbar { + background: $tabs-md-background; +} + + +// Material Design Tab Button +// -------------------------------------------------- + +.tabs-md .tab-button { + min-height: $tabs-md-tab-min-height; + + font-weight: $tabs-md-tab-font-weight; + color: $tabs-md-tab-text-color; + + @include deprecated-variable(padding, $tabs-md-tab-padding) { + @include padding($tabs-md-tab-padding-top, $tabs-md-tab-padding-end, $tabs-md-tab-padding-bottom, $tabs-md-tab-padding-start); + } +} + +.tabs-md .tab-button[aria-selected=true] { + color: $tabs-md-tab-text-color-active; + + @include deprecated-variable(padding, $tabs-md-tab-padding-active) { + @include padding($tabs-md-tab-padding-active-top, $tabs-md-tab-padding-active-end, $tabs-md-tab-padding-active-bottom, $tabs-md-tab-padding-active-start); + } +} + + +// Material Design Tab Button Text +// -------------------------------------------------- + +.tabs-md .tab-button-text { + font-size: $tabs-md-tab-font-size; + + text-transform: $tabs-md-tab-text-capitalization; + transition: $tabs-md-tab-text-transition; + + @include deprecated-variable(margin, $tabs-md-tab-text-margin) { + @include margin($tabs-md-tab-text-margin-top, $tabs-md-tab-text-margin-end, $tabs-md-tab-text-margin-bottom, $tabs-md-tab-text-margin-start); + } + + @include deprecated-variable(transform-origin, $tabs-md-tab-text-transform-origin) { + @include transform-origin($tabs-md-tab-text-transform-origin-x, $tabs-md-tab-text-transform-origin-y); + } +} + +.tabs-md .tab-button[aria-selected=true] .tab-button-text { + transform: $tabs-md-tab-text-transform-active; + transition: $tabs-md-tab-text-transition; +} + +.tabs-md[tabsLayout=icon-top] .has-icon .tab-button-text { + @include margin(4px, null, 0, null); +} + +.tabs-md[tabsLayout=icon-bottom] .tab-button .tab-button-text { + @include margin(0, null, null, null); +} + + +// Material Design Tab Button Icon +// -------------------------------------------------- + +.tabs-md .tab-button-icon { + @include margin(1px, null, null, null); + + min-width: $tabs-md-tab-icon-size; + + font-size: $tabs-md-tab-icon-size; + + color: $tabs-md-tab-icon-color; + + transition: $tabs-md-tab-icon-transition; + + @include deprecated-variable(transform-origin, $tabs-md-tab-icon-transform-origin) { + @include transform-origin($tabs-md-tab-icon-transform-origin-x, $tabs-md-tab-icon-transform-origin-y); + } +} + +// Tab layout: icon-top, icon-only, title-only +.tabs-md .tab-button[aria-selected=true] .tab-button-icon { + color: $tabs-md-tab-icon-color-active; + + @include deprecated-variable(transform, $tabs-md-tab-icon-transform-active) { + @include transform(translate3d($tabs-md-tab-icon-transform-x-active, $tabs-md-tab-icon-transform-y-active, $tabs-md-tab-icon-transform-z-active)); + } +} + +// Tab layout: icon-end +.tabs-md[tabsLayout=icon-right] .tab-button[aria-selected=true] .tab-button-icon, // deprecated +.tabs-md[tabsLayout=icon-end] .tab-button[aria-selected=true] .tab-button-icon { + @include deprecated-variable(transform, $tabs-md-tab-icon-end-transform-active) { + @include transform(translate3d($tabs-md-tab-icon-right-transform-x-active, $tabs-md-tab-icon-right-transform-y-active, $tabs-md-tab-icon-right-transform-z-active)); + } +} + +// Tab layout: icon-bottom +.tabs-md[tabsLayout=icon-bottom] .tab-button[aria-selected=true] .tab-button-icon { + @include deprecated-variable(transform, $tabs-md-tab-icon-bottom-transform-active) { + @include transform(translate3d($tabs-md-tab-icon-bottom-transform-x-active, $tabs-md-tab-icon-bottom-transform-y-active, $tabs-md-tab-icon-bottom-transform-z-active)); + } +} + +// Tab layout: icon-start +.tabs-md[tabsLayout=icon-left] .tab-button[aria-selected=true] .tab-button-icon, // deprecated +.tabs-md[tabsLayout=icon-start] .tab-button[aria-selected=true] .tab-button-icon { + @include deprecated-variable(transform, $tabs-md-tab-icon-start-transform-active) { + @include transform(translate3d($tabs-md-tab-icon-left-transform-x-active, $tabs-md-tab-icon-left-transform-y-active, $tabs-md-tab-icon-left-transform-z-active)); + } +} + + +// Material Design Tab with Icon or Title only +// -------------------------------------------------- + +.tabs-md[tabsLayout=icon-hide] .tab-button, +.tabs-md[tabsLayout=title-hide] .tab-button, +.tabs-md .tab-button.icon-only, +.tabs-md .tab-button.has-title-only { + @include padding(0, 10px); +} + + +// Material Design Tab Highlight +// -------------------------------------------------- + +.tabs-md[tabsHighlight=true] .tab-highlight { + @include position(null, null, 0, 0); + @include transform-origin(0, 0); + + position: absolute; + + display: block; + + width: 1px; + height: 2px; + + background: $tabs-md-tab-color-active; + transform: translateZ(0); +} + +.tabs-md[tabsHighlight=true] .tab-highlight.animate { + transition-duration: 300ms; +} + +.tabs-md[tabsHighlight=true][tabsPlacement=bottom] > .tabbar > .tab-highlight { + top: 0; +} + + +// Material Design Tabs Color Mixin +// -------------------------------------------------- + +@mixin tabbar-md($color-name, $color-base, $color-contrast) { + + .tabs-md-#{$color-name} .tabbar { + background-color: $color-base; + } + + .tabs-md-#{$color-name} .tab-button, + .tabs-md-#{$color-name} .tab-button-icon { + color: rgba($color-contrast, $tabs-md-tab-opacity); + } + + .tabs-md-#{$color-name} .tab-button:hover:not(.disable-hover), + .tabs-md-#{$color-name} .tab-button[aria-selected=true], + .tabs-md-#{$color-name} .tab-button[aria-selected=true] .tab-button-icon { + color: $color-contrast; + } + + .tabs-md-#{$color-name}[tabsHighlight=true] .tab-highlight { + background: $color-contrast; + } + +} + + +// Material Design Tabbar Color Generation +// -------------------------------------------------- + +@each $color-name, $color-base, $color-contrast in get-colors($colors-md) { + @include tabbar-md($color-name, $color-base, $color-contrast); +} diff --git a/packages/core/src/components/tabs/tabs.scss b/packages/core/src/components/tabs/tabs.scss new file mode 100644 index 0000000000..73473a3572 --- /dev/null +++ b/packages/core/src/components/tabs/tabs.scss @@ -0,0 +1,153 @@ +@import "../../themes/ionic.globals"; + +// Tabs +// -------------------------------------------------- + +.tabbar { + @include position(null, null, 0, 0); + + position: absolute; + + z-index: $z-index-toolbar; + display: flex; + + width: 100%; + + // default to hidden until ready + //opacity: 0; +} + +.tabbar-hidden .tabbar { + display: none; +} + +.tabbar.show-tabbar { + opacity: 1; +} + +[tabsPlacement=top] > .tabbar { + top: 0; + bottom: auto; +} + +.tab-button { + @include margin(0); + @include text-align(center); + @include border-radius(0); + + position: relative; + z-index: 0; + display: flex; + overflow: hidden; + + flex: 1; + flex-direction: column; + align-items: center; + align-self: center; + justify-content: center; + + border: 0; + + text-decoration: none; + background: none; + cursor: pointer; + + user-select: none; +} + +.tab-disabled { + pointer-events: none; +} + +.tab-disabled ion-badge, +.tab-disabled ion-icon, +.tab-disabled span { + opacity: .4; +} + +.tab-button-text { + @include margin(3px, null, 2px, null); +} + +.tab-button-text, +.tab-button-icon { + //display: none; + overflow: hidden; + + align-self: center; + + min-width: 26px; + max-width: 100%; + + text-overflow: ellipsis; + white-space: nowrap; +} + +.has-icon .tab-button-icon, +.has-title .tab-button-text { + display: block; +} + +.has-title-only .tab-button-text { + white-space: normal; +} + +[tabsLayout=icon-bottom] .tab-button .tab-button-icon { + order: 10; +} + +[tabsLayout=icon-left] .tab-button, // deprecated +[tabsLayout=icon-right] .tab-button, // deprecated +[tabsLayout=icon-start] .tab-button, +[tabsLayout=icon-end] .tab-button { + flex-direction: row; +} + +[tabsLayout=icon-left] .tab-button .tab-button-icon, // deprecated +[tabsLayout=icon-start] .tab-button .tab-button-icon { + @include padding-horizontal(null, 8px); + @include text-align(end); +} + +[tabsLayout=icon-right] .tab-button .tab-button-icon, // deprecated +[tabsLayout=icon-end] .tab-button .tab-button-icon { + @include padding-horizontal(8px, null); + @include text-align(start); + + order: 10; +} + +.tab-hidden, +.tab-highlight, +[tabsLayout=icon-hide] .tab-button-icon, +[tabsLayout=title-hide] .tab-button-text { + display: none; +} + +// Tab Badges +// -------------------------------------------------- + +.tab-badge { + @include position(6%, 4%, null, null); // 4% fallback + @include position(null, calc(50% - 50px), null, null); + @include padding(1px, 6px); + + position: absolute; + + height: auto; + + font-size: 12px; + line-height: 16px; +} + +.has-icon .tab-badge { + @include position(null, calc(50% - 30px), null, null); +} + +[tabsLayout=icon-bottom] .tab-badge, +[tabsLayout=icon-left] .tab-badge, // deprecated +[tabsLayout=icon-right] .tab-badge, // deprecated +[tabsLayout=icon-start] .tab-badge, +[tabsLayout=icon-end] .tab-badge { + @include position(null, calc(50% - 50px), null, null); +} diff --git a/packages/core/src/components/tabs/tabs.tsx b/packages/core/src/components/tabs/tabs.tsx new file mode 100644 index 0000000000..3323f951b4 --- /dev/null +++ b/packages/core/src/components/tabs/tabs.tsx @@ -0,0 +1,96 @@ +import { Component, h, Listen } from '@stencil/core'; + +@Component({ + tag: 'ion-tabs', + styleUrls: { + ios: 'tabs.ios.scss', + md: 'tabs.md.scss', + wp: 'tabs.wp.scss' + }, + host: { + theme: 'tabs' + } +}) +export class Tabs { + // Current list of tabs + @State() tabs: [Tab] = [] + + /** + * @state {number} The selected tab + */ + @State() selectedTab: Tab; + + /** + * @state {number} The selected tab index + */ + @State() selectedIndex: number = 0; + + /** + * @prop {string} Set the tabbar layout: `icon-top`, `icon-start`, `icon-end`, `icon-bottom`, `icon-hide`, `title-hide`. + */ + @Prop() tabsLayout: string = 'icon-top' + + /** + * @prop {string} Set position of the tabbar: `top`, `bottom`. + */ + @Prop() tabsPlacement: string = 'bottom'; + + /** + * @prop {boolean} If true, show the tab highlight bar under the selected tab. + */ + @Prop() tabsHighlight: boolean = false; + + + /** + * @output {any} Emitted when the tab changes. + */ + @Prop() ionChange: Function; + + /** + * If selectedIndex was changed, grab the reference to the tab it points to. + */ + @PropDidChange('selectedIndex') + handleSelectedIndexChanged() { + this.selectedTab = this.tabs[this.selectedIndex] + } + + @Listen('ionTabDidLoad') + tabDidLoad(ev) { + const tab = ev.detail.tab; + + // First tab? Select it + if(this.tabs.length == 0) { + this.handleOnTabSelected(tab, 0); + } + + this.tabs = [ ...this.tabs, tab ]; + } + + @Listen('ionTabDidUnload') + tabDidUnload(ev) { + this.tabs = this.tabs.filter(t => t !== ev.detail.tab) + } + + handleOnTabSelected(tab, index) { + // Select just this tab + this.tabs.forEach(t => t.isSelected = false); + tab.isSelected = true; + + // Store the selected tab and index + this.selectedTab = tab; + this.selectedIndex = index; + + // Fire a change event + this.ionChange && this.ionChange(tab) + } + + render() { + return [ + , + + ] + } +} diff --git a/packages/core/src/components/tabs/tabs.wp.scss b/packages/core/src/components/tabs/tabs.wp.scss new file mode 100644 index 0000000000..5193034371 --- /dev/null +++ b/packages/core/src/components/tabs/tabs.wp.scss @@ -0,0 +1,178 @@ +@import "../../themes/ionic.globals.wp"; +@import "./tabs"; + + +// Windows Tabs +// -------------------------------------------------- + +// deprecated +$tabs-wp-tab-padding: null !default; + +/// @prop - Padding top on the tab button +$tabs-wp-tab-padding-top: 12px !default; + +/// @prop - Padding end on the tab button +$tabs-wp-tab-padding-end: 10px !default; + +/// @prop - Padding bottom on the tab button +$tabs-wp-tab-padding-bottom: 5px !default; + +/// @prop - Padding start on the tab button +$tabs-wp-tab-padding-start: 10px !default; + +/// @prop - Minimum height of the tab button +$tabs-wp-tab-min-height: 4.8rem !default; + +/// @prop - Font size of the tab button text +$tabs-wp-tab-font-size: 1.2rem !default; + +/// @prop - Font weight of the tab button text +$tabs-wp-tab-font-weight: normal !default; + +/// @prop - Opacity of the inactive tab button +$tabs-wp-tab-opacity: .7 !default; + +/// @prop - Text color of the inactive tab button +$tabs-wp-tab-color: rgba($tabs-wp-tab-color-inactive, $tabs-wp-tab-opacity) !default; + +/// @prop - Text color of the active tab button +$tabs-wp-tab-color-active: $tabs-wp-tab-color-active !default; + +/// @prop - Icon color of the inactive tab button +$tabs-wp-tab-icon-color: rgba($tabs-wp-tab-color-inactive, $tabs-wp-tab-opacity) !default; + +/// @prop - Icon color of the active tab button +$tabs-wp-tab-icon-color-active: $tabs-wp-tab-color-active !default; + +/// @prop - Border on the inactive tab button (border-top when [tabsPlacement=bottom] and border-bottom when [tabsPlacement=top]) +$tabs-wp-tab-border: 2px solid $tabs-wp-border-color !default; + +/// @prop - Border color on the active tab button (border-top when [tabsPlacement=bottom] and border-bottom when [tabsPlacement=top]) +$tabs-wp-tab-border-color-active: $tabs-wp-tab-color-active !default; + +/// @prop - Background of the tab button when pressed +$tabs-wp-tab-background-activated: rgba(0, 0, 0, .1) !default; + +/// @prop - Size of the tab button icon +$tabs-wp-tab-icon-size: 2.4rem !default; + + +.tabs-wp .tabbar { + background: $tabs-wp-background; +} + +.tabs-wp .tab-button { + @include border-radius(0); + + min-height: $tabs-wp-tab-min-height; + + border-bottom: $tabs-wp-tab-border; + font-size: $tabs-wp-tab-font-size; + font-weight: $tabs-wp-tab-font-weight; + color: $tabs-wp-tab-color; + box-shadow: none; + + @include deprecated-variable(padding, $tabs-wp-tab-padding) { + @include padding($tabs-wp-tab-padding-top, $tabs-wp-tab-padding-end, $tabs-wp-tab-padding-bottom, $tabs-wp-tab-padding-start); + } +} + +.tabs-wp .tab-button[aria-selected=true] { + border-bottom-color: $tabs-wp-tab-border-color-active; + color: $tabs-wp-tab-color-active; +} + +.tabs-wp .tab-button.activated { + background: $tabs-wp-tab-background-activated; +} + +.tabs-wp[tabsPlacement=bottom] .tab-button { + border-top: $tabs-wp-tab-border; + border-bottom-width: 0; +} + +.tabs-wp[tabsPlacement=bottom] .tab-button[aria-selected=true] { + border-top-color: $tabs-wp-tab-border-color-active; +} + + +// Windows Tab Button Text +// -------------------------------------------------- + +.tabs-wp .tab-button-text { + @include margin(5px, null); +} + + +// Windows Tab Button Icon +// -------------------------------------------------- + +.tabs-wp .tab-button-icon { + min-width: $tabs-wp-tab-icon-size; + + font-size: $tabs-wp-tab-icon-size; + + color: $tabs-wp-tab-icon-color; +} + +.tabs-wp .tab-button[aria-selected=true] .tab-button-icon { + color: $tabs-wp-tab-icon-color-active; +} + +.tabs-wp[tabsLayout=icon-bottom] .tab-button { + @include padding(8px, null, 8px, null); +} + +.tabs-wp[tabsLayout=icon-right] .tab-button, // deprecated +.tabs-wp[tabsLayout=icon-left] .tab-button, // deprecated +.tabs-wp[tabsLayout=icon-end] .tab-button, +.tabs-wp[tabsLayout=icon-start] .tab-button { + @include padding(null, null, 10px, null); +} + +.tabs-wp[tabsLayout=icon-right] .tab-button ion-icon, // deprecated +.tabs-wp[tabsLayout=icon-left] .tab-button ion-icon, // deprecated +.tabs-wp[tabsLayout=icon-end] .tab-button ion-icon, +.tabs-wp[tabsLayout=icon-start] .tab-button ion-icon { + min-width: 24px; +} + +.tabs-wp[tabsLayout=icon-hide] .tab-button, +.tabs-wp[tabsLayout=title-hide] .tab-button, +.tabs-wp .tab-button.icon-only, +.tabs-wp .tab-button.has-title-only { + @include padding(6px, 10px); +} + + +// Windows Tabbar Color Mixin +// -------------------------------------------------- + +@mixin tabbar-wp($color-name, $color-base, $color-contrast) { + + .tabs-wp-#{$color-name} .tabbar { + background-color: $color-base; + } + + .tabs-wp-#{$color-name} .tab-button, + .tabs-wp-#{$color-name} .tab-button-icon { + color: rgba($color-contrast, $tabs-wp-tab-opacity); + } + + .tabs-wp-#{$color-name} .tab-button:hover:not(.disable-hover), + .tabs-wp-#{$color-name} .tab-button:hover:not(.disable-hover) .tab-button-icon, + .tabs-wp-#{$color-name} .tab-button[aria-selected=true], + .tabs-wp-#{$color-name} .tab-button[aria-selected=true] .tab-button-icon { + border-color: $color-contrast; + color: $color-contrast; + } + +} + + +// Windows Tabbar Color Generation +// -------------------------------------------------- + +@each $color-name, $color-base, $color-contrast in get-colors($colors-wp) { + @include tabbar-wp($color-name, $color-base, $color-contrast); +}