MOBILE-2310 core: Implement and apply core-tabs and core-tab
parent
e1bc11e44b
commit
cbc983637d
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
// -------------------------
|
||||
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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':
|
||||
|
|
Loading…
Reference in New Issue