MOBILE-2310 core: Implement and apply core-tabs and core-tab

main
Dani Palou 2018-01-12 12:27:57 +01:00
parent e1bc11e44b
commit cbc983637d
10 changed files with 374 additions and 103 deletions

View File

@ -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;
}

View File

@ -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.
// -------------------------

View File

@ -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 {}

View File

@ -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:
*
* <core-tabs selectedIndex="1">
* <core-tab [title]="'core.courses.timeline' | translate" (ionSelect)="switchTab('timeline')">
* <!-- Tab contents. -->
* </core-tab>
* </core-tabs>
*/
@Component({
selector: 'core-tab',
template: '<ng-content></ng-content>'
})
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<CoreTabComponent> = new EventEmitter<CoreTabComponent>();
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);
}
}

View File

@ -0,0 +1,12 @@
<div class="core-tabs-bar">
<ng-container *ngFor="let tab of tabs; let idx = index">
<a *ngIf="tab.show" [attr.aria-selected]="selected == idx" (click)="selectTab(idx)">
<ion-icon *ngIf="tab.icon" [name]="tab.icon"></ion-icon>
<span *ngIf="tab.title">{{ tab.title }}</span>
<ion-badge *ngIf="tab.badge" [color]="tab.badgeStyle" class="tab-badge">{{tab.badge}}</ion-badge>
</a>
</ng-container>
</div>
<div #originalTabs>
<ng-content></ng-content>
</div>

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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:
*
* <core-tabs selectedIndex="1">
* <core-tab [title]="'core.courses.timeline' | translate" (ionSelect)="switchTab('timeline')">
* <!-- Tab contents. -->
* </core-tab>
* </core-tabs>
*
* 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<CoreTabComponent> = new EventEmitter<CoreTabComponent>(); // 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;
}
}
}

View File

@ -17,23 +17,19 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<!-- @todo: Use a component to render the tabs. -->
<div class="core-top-tabbar">
<a [attr.aria-selected]="tabShown == 'timeline'" (click)="switchTab('timeline')">{{ 'core.courses.timeline' | translate }}</a>
<a [attr.aria-selected]="tabShown == 'courses'" (click)="switchTab('courses')">{{ 'core.courses.courses' | translate }}</a>
</div>
<div [hidden]="tabShown != 'timeline'">
<core-tabs selectedIndex="1">
<!-- Timeline tab. -->
<core-tab [title]="'core.courses.timeline' | translate" (ionSelect)="tabChanged('timeline')">
<div no-padding [hidden]="!(timeline.loaded || timelineCourses.loaded)">
<ion-select [(ngModel)]="timeline.sort" (ngModelChange)="switchSort()">
<ion-option value="sortbydates">{{ 'core.courses.sortbydates' | translate }}</ion-option>
<ion-option value="sortbycourses">{{ 'core.courses.sortbycourses' | translate }}</ion-option>
</ion-select>
</div>
<core-loading [hideUntil]="timeline.loaded" [hidden]="tabShown != 'timeline' || timeline.sort != 'sortbydates'" class="core-loading-center">
<core-loading [hideUntil]="timeline.loaded" [hidden]="timeline.sort != 'sortbydates'" class="core-loading-center">
<core-courses-overview-events [events]="timeline.events" showCourse="true" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMoreTimeline()"></core-courses-overview-events>
</core-loading>
<core-loading [hideUntil]="timelineCourses.loaded" [hidden]="tabShown != 'timeline' || timeline.sort != 'sortbycourses'" class="core-loading-center">
<core-loading [hideUntil]="timelineCourses.loaded" [hidden]="timeline.sort != 'sortbycourses'" class="core-loading-center">
<ion-grid no-padding>
<ion-row no-padding>
<ion-col *ngFor="let course of timelineCourses.courses" no-padding col-12 col-md-6>
@ -45,8 +41,11 @@
</ion-grid>
<core-empty-box *ngIf="timelineCourses.courses.length == 0" image="assets/img/icons/courses.svg" [message]="'core.courses.nocoursesoverview' | translate"></core-empty-box>
</core-loading>
</div>
<core-loading [hideUntil]="courses.loaded" [hidden]="tabShown != 'courses'" class="core-loading-center">
</core-tab>
<!-- Courses tab. -->
<core-tab [title]="'core.courses.courses' | translate" (ionSelect)="tabChanged('courses')">
<core-loading [hideUntil]="courses.loaded" class="core-loading-center">
<div no-padding class="clearfix" [hidden]="showFilter">
<ion-select [title]="'core.show' | translate" [(ngModel)]="courses.selected" float-start (ngModelChange)="selectedChanged()">
<ion-option value="inprogress">{{ 'core.courses.inprogress' | translate }}</ion-option>
@ -77,4 +76,7 @@
<core-empty-box *ngIf="courses[courses.selected].length == 0 && courses.selected == 'past'" image="assets/img/icons/courses.svg" [message]="'core.courses.nocoursespast' | translate"></core-empty-box>
</div>
</core-loading>
</core-tab>
</core-tabs>
</ion-content>

View File

@ -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':