From cbc983637d68dd6c7db37bf968fad366ab27572e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 12 Jan 2018 12:27:57 +0100 Subject: [PATCH] MOBILE-2310 core: Implement and apply core-tabs and core-tab --- src/app/app.ios.scss | 12 -- src/app/app.scss | 26 --- src/components/components.module.ts | 10 +- src/components/tabs/tab.ts | 64 ++++++ src/components/tabs/tabs.html | 12 ++ src/components/tabs/tabs.ios.scss | 11 ++ src/components/tabs/tabs.scss | 32 +++ src/components/tabs/tabs.ts | 183 ++++++++++++++++++ .../pages/my-overview/my-overview.html | 118 +++++------ .../courses/pages/my-overview/my-overview.ts | 9 +- 10 files changed, 374 insertions(+), 103 deletions(-) create mode 100644 src/components/tabs/tab.ts create mode 100644 src/components/tabs/tabs.html create mode 100644 src/components/tabs/tabs.ios.scss create mode 100644 src/components/tabs/tabs.scss create mode 100644 src/components/tabs/tabs.ts diff --git a/src/app/app.ios.scss b/src/app/app.ios.scss index 84a20c0c0..662eb401e 100644 --- a/src/app/app.ios.scss +++ b/src/app/app.ios.scss @@ -12,18 +12,6 @@ height: calc(100% - #{($card-ios-margin-end + $card-ios-margin-start)}); } -// Top tabs -// ------------------------- -.ios .core-top-tabbar { - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - > a { - font-size: 1.6rem; - } -} - .bar-buttons core-context-menu .button-clear-ios { color: $toolbar-ios-button-color; } diff --git a/src/app/app.scss b/src/app/app.scss index 3149f8c43..b12ba8d55 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -255,32 +255,6 @@ ion-select { position: relative } -// Top tabs -// ------------------------- - -.core-top-tabbar { - @include position(null, null, 0, 0); - - z-index: $z-index-toolbar; - display: flex; - width: 100%; - background: $core-top-tabs-background; - - > a { - @extend .tab-button; - - background: $core-top-tabs-background; - color: $core-top-tabs-color !important; - border-bottom: 1px solid $core-top-tabs-border; - font-size: 1.6rem; - - &[aria-selected=true] { - color: $core-top-tabs-color-active !important; - border-bottom: 2px solid $core-top-tabs-color-active; - } - } -} - // File uploader. // ------------------------- diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 0ce4dbc4d..a9b03da57 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -34,6 +34,8 @@ import { CoreCoursePickerMenuPopoverComponent } from './course-picker-menu/cours import { CoreChronoComponent } from './chrono/chrono'; import { CoreLocalFileComponent } from './local-file/local-file'; import { CoreSitePickerComponent } from './site-picker/site-picker'; +import { CoreTabsComponent } from './tabs/tabs'; +import { CoreTabComponent } from './tabs/tab'; @NgModule({ declarations: [ @@ -53,7 +55,9 @@ import { CoreSitePickerComponent } from './site-picker/site-picker'; CoreCoursePickerMenuPopoverComponent, CoreChronoComponent, CoreLocalFileComponent, - CoreSitePickerComponent + CoreSitePickerComponent, + CoreTabsComponent, + CoreTabComponent ], entryComponents: [ CoreContextMenuPopoverComponent, @@ -80,7 +84,9 @@ import { CoreSitePickerComponent } from './site-picker/site-picker'; CoreContextMenuItemComponent, CoreChronoComponent, CoreLocalFileComponent, - CoreSitePickerComponent + CoreSitePickerComponent, + CoreTabsComponent, + CoreTabComponent ] }) export class CoreComponentsModule {} diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts new file mode 100644 index 000000000..16a9a297e --- /dev/null +++ b/src/components/tabs/tab.ts @@ -0,0 +1,64 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter } from '@angular/core'; +import { CoreTabsComponent } from './tabs'; + +/** + * A tab to use inside core-tabs. The content of this tab will be displayed when the tab is selected. + * + * You must provide either a title or an icon for the tab. + * + * Example usage: + * + * + * + * + * + * + */ +@Component({ + selector: 'core-tab', + template: '' +}) +export class CoreTabComponent implements OnInit, OnDestroy { + @Input() title?: string; // The tab title. + @Input() icon?: string; // The tab icon. + @Input() badge?: string; // A badge to add in the tab. + @Input() badgeStyle?: string; // The badge color. + @Input() enabled?: boolean = true; // Whether the tab is enabled. + @Input() show?: boolean = true; // Whether the tab should be shown. + @Input() id?: string; // An ID to identify the tab. + @Output() ionSelect: EventEmitter = new EventEmitter(); + + element: HTMLElement; // The core-tab element. + + constructor(private tabs: CoreTabsComponent, element: ElementRef) { + this.element = element.nativeElement; + } + + /** + * Component being initialized. + */ + ngOnInit() { + this.tabs.addTab(this); + } + + /** + * Component destroyed. + */ + ngOnDestroy() { + this.tabs.removeTab(this); + } +} diff --git a/src/components/tabs/tabs.html b/src/components/tabs/tabs.html new file mode 100644 index 000000000..9bac58750 --- /dev/null +++ b/src/components/tabs/tabs.html @@ -0,0 +1,12 @@ + +
+ +
\ No newline at end of file diff --git a/src/components/tabs/tabs.ios.scss b/src/components/tabs/tabs.ios.scss new file mode 100644 index 000000000..b9134b986 --- /dev/null +++ b/src/components/tabs/tabs.ios.scss @@ -0,0 +1,11 @@ +core-tabs { + .core-tabs-bar { + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + > a { + font-size: 1.6rem; + } + } +} diff --git a/src/components/tabs/tabs.scss b/src/components/tabs/tabs.scss new file mode 100644 index 000000000..b7daa7804 --- /dev/null +++ b/src/components/tabs/tabs.scss @@ -0,0 +1,32 @@ +core-tabs { + .core-tabs-bar { + @include position(null, null, 0, 0); + + z-index: $z-index-toolbar; + display: flex; + width: 100%; + background: $core-top-tabs-background; + + > a { + @extend .tab-button; + + background: $core-top-tabs-background; + color: $core-top-tabs-color !important; + border-bottom: 1px solid $core-top-tabs-border; + font-size: 1.6rem; + + &[aria-selected=true] { + color: $core-top-tabs-color-active !important; + border-bottom: 2px solid $core-top-tabs-color-active; + } + } + } + + core-tab { + display: none; + + &.selected { + display: block; + } + } +} diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts new file mode 100644 index 000000000..93ab502fa --- /dev/null +++ b/src/components/tabs/tabs.ts @@ -0,0 +1,183 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core'; +import { CoreTabComponent } from './tab'; + +/** + * This component displays some tabs that usually share data between them. + * + * If your tabs don't share any data then you should probably use ion-tabs. This component doesn't use different ion-nav + * for each tab, so it will not load pages. + * + * Example usage: + * + * + * + * + * + * + * + * Obviously, the tab contents will only be shown if that tab is selected. + */ +@Component({ + selector: 'core-tabs', + templateUrl: 'tabs.html' +}) +export class CoreTabsComponent implements OnInit, AfterViewInit { + @Input() selectedIndex?: number = 0; // Index of the tab to select. + @Output() ionChange: EventEmitter = new EventEmitter(); // Emitted when the tab changes. + @ViewChild('originalTabs') originalTabsRef: ElementRef; + + tabs: CoreTabComponent[] = []; // List of tabs. + selected: number; // Selected tab number. + protected originalTabsContainer: HTMLElement; // The container of the original tabs. It will include each tab's content. + + constructor() {} + + /** + * Component being initialized. + */ + ngOnInit() { + this.originalTabsContainer = this.originalTabsRef.nativeElement; + } + + /** + * View has been initialized. + */ + ngAfterViewInit() { + let selectedIndex = this.selectedIndex || 0, + selectedTab = this.tabs[selectedIndex]; + + if (!selectedTab.enabled || !selectedTab.show) { + // The tab is not enabled or not shown. Get the first tab that is enabled. + selectedTab = this.tabs.find((tab, index) => { + if (tab.enabled && tab.show) { + selectedIndex = index; + return true; + } + return false; + }); + } + + if (selectedTab) { + this.selectTab(selectedIndex); + } + } + + /** + * Add a new tab if it isn't already in the list of tabs. + * + * @param {CoreTabComponent} tab The tab to add. + */ + addTab(tab: CoreTabComponent) : void { + // Check if tab is already in the list. + if (this.getIndex(tab) == -1) { + this.tabs.push(tab); + this.sortTabs(); + } + } + + /** + * Get the index of tab. + * + * @param {any} tab [description] + * @return {number} [description] + */ + getIndex(tab: any) : number { + for (let i = 0; i < this.tabs.length; i++) { + let t = this.tabs[i]; + if (t === tab || (typeof t.id != 'undefined' && t.id === tab.id)) { + return i; + } + } + return -1; + } + + /** + * Get the current selected tab. + * + * @return {CoreTabComponent} Selected tab. + */ + getSelected() : CoreTabComponent { + return this.tabs[this.selected]; + } + + /** + * Remove a tab from the list of tabs. + * + * @param {CoreTabComponent} tab The tab to remove. + */ + removeTab(tab: CoreTabComponent) : void { + const index = this.getIndex(tab); + this.tabs.splice(index, 1); + } + + /** + * Select a certain tab. + * + * @param {number} index The index of the tab to select. + */ + selectTab(index: number) : void { + if (index == this.selected) { + // Already selected. + return; + } + + if (index < 0 || index >= this.tabs.length) { + // Index isn't valid, select the first one. + index = 0; + } + + const currenTab = this.getSelected(), + newTab = this.tabs[index]; + + if (!newTab.enabled || !newTab.show) { + // The tab isn't enabled or shown, stop. + return; + } + + if (currenTab) { + // Unselect previous selected tab. + currenTab.element.classList.remove('selected'); + } + + this.selected = index; + newTab.element.classList.add('selected'); + newTab.ionSelect.emit(newTab); + this.ionChange.emit(newTab); + } + + /** + * Sort the tabs, keeping the same order as in the original list. + */ + protected sortTabs() { + if (this.originalTabsContainer) { + let newTabs = [], + newSelected; + + this.tabs.forEach((tab, index) => { + let originalIndex = Array.prototype.indexOf.call(this.originalTabsContainer.children, tab.element); + if (originalIndex != -1) { + newTabs[originalIndex] = tab; + if (this.selected == index) { + newSelected = originalIndex; + } + } + }); + + this.tabs = newTabs; + } + } +} diff --git a/src/core/courses/pages/my-overview/my-overview.html b/src/core/courses/pages/my-overview/my-overview.html index 976a68284..b1ac565b5 100644 --- a/src/core/courses/pages/my-overview/my-overview.html +++ b/src/core/courses/pages/my-overview/my-overview.html @@ -17,64 +17,66 @@ - - + + + +
+ + {{ 'core.courses.sortbydates' | translate }} + {{ 'core.courses.sortbycourses' | translate }} + +
+ + + + + + + + + + + + + + + +
-
-
- - {{ 'core.courses.sortbydates' | translate }} - {{ 'core.courses.sortbycourses' | translate }} - -
- - - - - - - - - - - - - - - -
- -
- - {{ 'core.courses.inprogress' | translate }} - {{ 'core.courses.future' | translate }} - {{ 'core.courses.past' | translate }} - - -
-
- - - - -
-
- - - - - - - + + + +
+ + {{ 'core.courses.inprogress' | translate }} + {{ 'core.courses.future' | translate }} + {{ 'core.courses.past' | translate }} + + +
+
+ + + + +
+
+ + + + + + + + + + + +
+
+
+ - - - -
-
diff --git a/src/core/courses/pages/my-overview/my-overview.ts b/src/core/courses/pages/my-overview/my-overview.ts index 21a7c423d..16f83ec21 100644 --- a/src/core/courses/pages/my-overview/my-overview.ts +++ b/src/core/courses/pages/my-overview/my-overview.ts @@ -51,6 +51,7 @@ export class CoreCoursesMyOverviewPage { showFilter = false; searchEnabled: boolean; filteredCourses: any[]; + tabs = []; protected prefetchIconInitialized = false; protected myCoursesObserver; @@ -65,8 +66,6 @@ export class CoreCoursesMyOverviewPage { ionViewDidLoad() { this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); - this.switchTab(this.tabShown); - // @todo: Course download. } @@ -260,11 +259,11 @@ export class CoreCoursesMyOverviewPage { } /** - * Change tab being viewed. + * The tab has changed. * - * @param {string} tab Tab to display. + * @param {string} tab Name of the new tab. */ - switchTab(tab: string) { + tabChanged(tab: string) { this.tabShown = tab; switch (this.tabShown) { case 'timeline':