MOBILE-3565 core-tabs: First version of the component
parent
29e3f373ac
commit
7ef503fab6
|
@ -28,6 +28,8 @@ import { CoreRecaptchaComponent } from './recaptcha/recaptcha';
|
|||
import { CoreRecaptchaModalComponent } from './recaptcha/recaptchamodal';
|
||||
import { CoreShowPasswordComponent } from './show-password/show-password';
|
||||
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
|
||||
import { CoreTabsComponent } from './tabs/tabs';
|
||||
|
||||
import { CoreDirectivesModule } from '@app/directives/directives.module';
|
||||
import { CorePipesModule } from '@app/pipes/pipes.module';
|
||||
|
||||
|
@ -44,6 +46,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
|
|||
CoreRecaptchaModalComponent,
|
||||
CoreShowPasswordComponent,
|
||||
CoreEmptyBoxComponent,
|
||||
CoreTabsComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -64,6 +67,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
|
|||
CoreRecaptchaModalComponent,
|
||||
CoreShowPasswordComponent,
|
||||
CoreEmptyBoxComponent,
|
||||
CoreTabsComponent,
|
||||
],
|
||||
})
|
||||
export class CoreComponentsModule {}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<ion-tabs>
|
||||
<ion-tab-bar slot="top" class="core-tabs-bar" [hidden]="!tabs || numTabsShown < 1">
|
||||
<ion-spinner *ngIf="!hideUntil"></ion-spinner>
|
||||
<ion-row *ngIf="hideUntil">
|
||||
<ion-col class="col-with-arrow ion-no-padding" (click)="slidePrev()" size="1">
|
||||
<ion-icon *ngIf="showPrevButton" name="fa-chevron-left"></ion-icon>
|
||||
</ion-col>
|
||||
<ion-col class="ion-no-padding" size="10">
|
||||
<ion-slides (ionSlideDidChange)="slideChanged()" [options]="slideOpts" [dir]="direction" role="tablist"
|
||||
[attr.aria-label]="description" aria-hidden="false">
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<ion-slide [hidden]="!hideUntil" [attr.aria-selected]="selected == tab.id" class="tab-slide"
|
||||
[attr.aria-label]="tab.title | translate" role="tab" [attr.aria-controls]="tab.id" [id]="tab.id + '-tab'"
|
||||
[tabindex]="selected == tab.id ? null : -1">
|
||||
|
||||
<ion-tab-button (ionTabButtonClick)="selectTab(tab.id, $event)" [tab]="tab.page" [layout]="layout"
|
||||
class="{{tab.class}}">
|
||||
<ion-icon *ngIf="tab.icon" [name]="tab.icon"></ion-icon>
|
||||
<ion-label>{{ tab.title | translate}}</ion-label>
|
||||
<ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge>
|
||||
</ion-tab-button>
|
||||
</ion-slide>
|
||||
</ng-container>
|
||||
</ion-slides>
|
||||
</ion-col>
|
||||
<ion-col class="col-with-arrow ion-no-padding" (click)="slideNext()" size="1">
|
||||
<ion-icon *ngIf="showNextButton" name="fa-chevron-right"></ion-icon>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
|
@ -0,0 +1,72 @@
|
|||
:host {
|
||||
--tabs-background: var(--background);
|
||||
--tabs-color: var(--color);
|
||||
height: 100%;
|
||||
display: block;
|
||||
|
||||
ion-tabs {
|
||||
background: transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ion-tab-bar.core-tabs-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: var(--tabs-background);
|
||||
color: var(--tabs-color);
|
||||
-webkit-filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
|
||||
filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
|
||||
border: 0;
|
||||
|
||||
ion-row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-slide {
|
||||
border-bottom: 2px solid transparent;
|
||||
min-width: 100px;
|
||||
min-height: 56px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
ion-tab-button {
|
||||
ion-label {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
line-height: 1.2em;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-selected=true] {
|
||||
color: var(--color-active);
|
||||
border-bottom-color: var(--border-color-active);
|
||||
ion-tab-button {
|
||||
color: var(--color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ion-col {
|
||||
text-align: center;
|
||||
line-height: 1.6rem;
|
||||
|
||||
&.col-with-arrow {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.tabs-hidden {
|
||||
display: none !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,629 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
AfterViewInit,
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
} from '@angular/core';
|
||||
import { Platform, IonSlides, IonTabs, NavController } from '@ionic/angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreConfig } from '@services/config';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreUtils } from '@/app/services/utils/utils';
|
||||
import { NavigationOptions } from '@ionic/angular/providers/nav-controller';
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
/**
|
||||
* This component displays some top scrollable tabs that will autohide on vertical scroll.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <core-tabs selectedIndex="1" [tabs]="tabs"></core-tabs>
|
||||
*
|
||||
* Tab contents will only be shown if that tab is selected.
|
||||
*
|
||||
* @todo: Test behaviour when tabs are added late.
|
||||
* @todo: Test RTL and tab history.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-tabs',
|
||||
templateUrl: 'core-tabs.html',
|
||||
styleUrls: ['tabs.scss'],
|
||||
})
|
||||
export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
|
||||
|
||||
|
||||
// Minimum tab's width to display fully the word "Competencies" which is the longest tab in the app.
|
||||
protected static readonly MIN_TAB_WIDTH = 107;
|
||||
// Max height that allows tab hiding.
|
||||
protected static readonly MAX_HEIGHT_TO_HIDE_TABS = 768;
|
||||
|
||||
@Input() protected selectedIndex = 0; // Index of the tab to select.
|
||||
@Input() hideUntil = false; // Determine when should the contents be shown.
|
||||
/**
|
||||
* Determine tabs layout.
|
||||
*/
|
||||
@Input() layout: 'icon-top' | 'icon-start' | 'icon-end' | 'icon-bottom' | 'icon-hide' | 'label-hide' = 'icon-hide';
|
||||
@Input() tabs: CoreTab[] = [];
|
||||
@Output() protected ionChange: EventEmitter<CoreTab> = new EventEmitter<CoreTab>(); // Emitted when the tab changes.
|
||||
|
||||
@ViewChild(IonSlides) protected slides?: IonSlides;
|
||||
@ViewChild(IonTabs) protected ionTabs?: IonTabs;
|
||||
|
||||
selected?: string; // Selected tab id.
|
||||
showPrevButton = false;
|
||||
showNextButton = false;
|
||||
maxSlides = 3;
|
||||
numTabsShown = 0;
|
||||
direction = 'ltr';
|
||||
description = '';
|
||||
lastScroll = 0;
|
||||
slideOpts = {
|
||||
initialSlide: 0,
|
||||
slidesPerView: 3,
|
||||
centerInsufficientSlides: true,
|
||||
};
|
||||
|
||||
protected initialized = false;
|
||||
protected afterViewInitTriggered = false;
|
||||
|
||||
protected tabBarHeight = 0;
|
||||
protected tabBarElement?: HTMLIonTabBarElement; // The top tab bar element.
|
||||
protected tabsElement?: HTMLIonTabsElement; // The ionTabs native Element.
|
||||
protected tabsShown = true;
|
||||
protected resizeFunction?: EventListenerOrEventListenerObject;
|
||||
protected isDestroyed = false;
|
||||
protected isCurrentView = true;
|
||||
protected shouldSlideToInitial = false; // Whether we need to slide to the initial slide because it's out of view.
|
||||
protected hasSliddenToInitial = false; // Whether we've already slidden to the initial slide or there was no need.
|
||||
protected selectHistory: string[] = [];
|
||||
|
||||
protected firstSelectedTab?: string; // ID of the first selected tab to control history.
|
||||
protected unregisterBackButtonAction: any;
|
||||
protected languageChangedSubscription: Subscription;
|
||||
protected isInTransition = false; // Weather Slides is in transition.
|
||||
protected slidesSwiper: any;
|
||||
protected slidesSwiperLoaded = false;
|
||||
|
||||
constructor(
|
||||
protected element: ElementRef,
|
||||
platform: Platform,
|
||||
translate: TranslateService,
|
||||
protected navCtrl: NavController,
|
||||
) {
|
||||
this.direction = platform.isRTL ? 'rtl' : 'ltr';
|
||||
|
||||
// Change the side when the language changes.
|
||||
this.languageChangedSubscription = translate.onLangChange.subscribe(() => {
|
||||
setTimeout(() => {
|
||||
this.direction = platform.isRTL ? 'rtl' : 'ltr';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.tabs.forEach((tab) => {
|
||||
this.initTab(tab);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Init tab info.
|
||||
*
|
||||
* @param tab Tab class.
|
||||
*/
|
||||
protected initTab(tab: CoreTab): void {
|
||||
tab.id = tab.id || 'core-tab-' + CoreUtils.instance.getUniqueId('CoreTabsComponent');
|
||||
if (typeof tab.enabled == 'undefined') {
|
||||
tab.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View has been initialized.
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
if (this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tabBarElement = this.element.nativeElement.querySelector('ion-tab-bar');
|
||||
this.tabsElement = this.element.nativeElement.querySelector('ion-tabs');
|
||||
|
||||
this.slidesSwiper = await this.slides?.getSwiper();
|
||||
this.slidesSwiper.once('progress', () => {
|
||||
this.slidesSwiperLoaded = true;
|
||||
this.calculateSlides();
|
||||
});
|
||||
|
||||
this.afterViewInitTriggered = true;
|
||||
|
||||
if (!this.initialized && this.hideUntil) {
|
||||
// Tabs should be shown, initialize them.
|
||||
await this.initializeTabs();
|
||||
}
|
||||
|
||||
this.resizeFunction = this.windowResized.bind(this);
|
||||
|
||||
window.addEventListener('resize', this.resizeFunction!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(): void {
|
||||
this.tabs.forEach((tab) => {
|
||||
this.initTab(tab);
|
||||
});
|
||||
|
||||
// We need to wait for ngAfterViewInit because we need core-tab components to be executed.
|
||||
if (!this.initialized && this.hideUntil && this.afterViewInitTriggered) {
|
||||
// Tabs should be shown, initialize them.
|
||||
// Use a setTimeout so child core-tab update their inputs before initializing the tabs.
|
||||
setTimeout(() => {
|
||||
this.initializeTabs();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User entered the page that contains the component.
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
this.isCurrentView = true;
|
||||
|
||||
this.calculateSlides();
|
||||
|
||||
this.registerBackButtonAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register back button action.
|
||||
*/
|
||||
protected registerBackButtonAction(): void {
|
||||
this.unregisterBackButtonAction = CoreApp.instance.registerBackButtonAction(() => {
|
||||
// The previous page in history is not the last one, we need the previous one.
|
||||
if (this.selectHistory.length > 1) {
|
||||
const tabIndex = this.selectHistory[this.selectHistory.length - 2];
|
||||
|
||||
// Remove curent and previous tabs from history.
|
||||
this.selectHistory = this.selectHistory.filter((tabId) => this.selected != tabId && tabIndex != tabId);
|
||||
|
||||
this.selectTab(tabIndex);
|
||||
|
||||
return true;
|
||||
} else if (this.selected != this.firstSelectedTab) {
|
||||
// All history is gone but we are not in the first selected tab.
|
||||
this.selectHistory = [];
|
||||
|
||||
this.selectTab(this.firstSelectedTab!);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, 750);
|
||||
}
|
||||
|
||||
/**
|
||||
* User left the page that contains the component.
|
||||
*/
|
||||
ionViewDidLeave(): void {
|
||||
// Unregister the custom back button action for this page
|
||||
this.unregisterBackButtonAction && this.unregisterBackButtonAction();
|
||||
|
||||
this.isCurrentView = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate slides.
|
||||
*/
|
||||
protected async calculateSlides(): Promise<void> {
|
||||
if (!this.isCurrentView || !this.initialized) {
|
||||
// Don't calculate if component isn't in current view, the calculations are wrong.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.tabsShown) {
|
||||
if (window.innerHeight >= CoreTabsComponent.MAX_HEIGHT_TO_HIDE_TABS) {
|
||||
// Ensure tabbar is shown.
|
||||
this.tabsShown = true;
|
||||
this.tabBarElement!.classList.remove('tabs-hidden');
|
||||
this.lastScroll = 0;
|
||||
}
|
||||
}
|
||||
|
||||
await this.calculateMaxSlides();
|
||||
|
||||
this.updateSlides();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the tab bar height.
|
||||
*/
|
||||
protected calculateTabBarHeight(): void {
|
||||
if (!this.tabBarElement || !this.tabsElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tabBarHeight = this.tabBarElement.offsetHeight;
|
||||
|
||||
if (this.tabsShown) {
|
||||
// Smooth translation.
|
||||
this.tabsElement.style.top = - this.lastScroll + 'px';
|
||||
this.tabsElement.style.height = 'calc(100% + ' + scroll + 'px';
|
||||
} else {
|
||||
this.tabBarElement.classList.add('tabs-hidden');
|
||||
this.tabsElement.style.top = '0';
|
||||
this.tabsElement.style.height = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tab on a index.
|
||||
*
|
||||
* @param tabId Tab ID.
|
||||
* @return Selected tab.
|
||||
*/
|
||||
protected getTabIndex(tabId: string): number {
|
||||
return this.tabs.findIndex((tab) => tabId == tab.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current selected tab.
|
||||
*
|
||||
* @return Selected tab.
|
||||
*/
|
||||
getSelected(): CoreTab | undefined {
|
||||
const index = this.selected && this.getTabIndex(this.selected);
|
||||
|
||||
return index && index >= 0 ? this.tabs[index] : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the tabs, determining the first tab to be shown.
|
||||
*/
|
||||
protected async initializeTabs(): Promise<void> {
|
||||
let selectedTab: CoreTab | undefined = this.tabs[this.selectedIndex || 0] || undefined;
|
||||
|
||||
if (!selectedTab || !selectedTab.enabled) {
|
||||
// The tab is not enabled or not shown. Get the first tab that is enabled.
|
||||
selectedTab = this.tabs.find((tab) => tab.enabled) || undefined;
|
||||
}
|
||||
|
||||
if (!selectedTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.firstSelectedTab = selectedTab.id;
|
||||
this.selectTab(this.firstSelectedTab);
|
||||
|
||||
// Setup tab scrolling.
|
||||
this.calculateTabBarHeight();
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Check which arrows should be shown.
|
||||
this.calculateSlides();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed when the slides are changed.
|
||||
*/
|
||||
async slideChanged(): Promise<void> {
|
||||
if (!this.slidesSwiperLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isInTransition = false;
|
||||
const slidesCount = await this.slides?.length() || 0;
|
||||
if (slidesCount > 0) {
|
||||
this.showPrevButton = !await this.slides?.isBeginning();
|
||||
this.showNextButton = !await this.slides?.isEnd();
|
||||
} else {
|
||||
this.showPrevButton = false;
|
||||
this.showNextButton = false;
|
||||
}
|
||||
|
||||
const currentIndex = await this.slides!.getActiveIndex();
|
||||
if (this.shouldSlideToInitial && currentIndex != this.selectedIndex) {
|
||||
// Current tab has changed, don't slide to initial anymore.
|
||||
this.shouldSlideToInitial = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the number of slides to show.
|
||||
*/
|
||||
protected async updateSlides(): Promise<void> {
|
||||
this.numTabsShown = this.tabs.reduce((prev: number, current: CoreTab) => current.enabled ? prev + 1 : prev, 0);
|
||||
|
||||
this.slideOpts.slidesPerView = Math.min(this.maxSlides, this.numTabsShown);
|
||||
this.slidesSwiper.params.slidesPerView = this.slideOpts.slidesPerView;
|
||||
|
||||
this.calculateTabBarHeight();
|
||||
await this.slides!.update();
|
||||
|
||||
if (!this.hasSliddenToInitial && this.selectedIndex && this.selectedIndex >= this.slideOpts.slidesPerView) {
|
||||
this.hasSliddenToInitial = true;
|
||||
this.shouldSlideToInitial = true;
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.shouldSlideToInitial) {
|
||||
this.slides!.slideTo(this.selectedIndex, 0);
|
||||
this.shouldSlideToInitial = false;
|
||||
}
|
||||
}, 400);
|
||||
|
||||
return;
|
||||
} else if (this.selectedIndex) {
|
||||
this.hasSliddenToInitial = true;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.slideChanged(); // Call slide changed again, sometimes the slide active index takes a while to be updated.
|
||||
}, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of slides that can fit on the screen.
|
||||
*/
|
||||
protected async calculateMaxSlides(): Promise<void> {
|
||||
if (!this.slidesSwiperLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.maxSlides = 3;
|
||||
const width = this.slidesSwiper.width;
|
||||
if (width) {
|
||||
const fontSize = await
|
||||
CoreConfig.instance.get(CoreConstants.SETTINGS_FONT_SIZE, CoreConstants.CONFIG.font_sizes[0]);
|
||||
|
||||
this.maxSlides = Math.floor(width / (fontSize / CoreConstants.CONFIG.font_sizes[0] *
|
||||
CoreTabsComponent.MIN_TAB_WIDTH));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that shows the next tab.
|
||||
*/
|
||||
async slideNext(): Promise<void> {
|
||||
// Stop if slides are in transition.
|
||||
if (!this.showNextButton || this.isInTransition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.slides!.isBeginning()) {
|
||||
// Slide to the second page.
|
||||
this.slides!.slideTo(this.maxSlides);
|
||||
} else {
|
||||
const currentIndex = await this.slides!.getActiveIndex();
|
||||
if (typeof currentIndex !== 'undefined') {
|
||||
const nextSlideIndex = currentIndex + this.maxSlides;
|
||||
this.isInTransition = true;
|
||||
if (nextSlideIndex < this.numTabsShown) {
|
||||
// Slide to the next page.
|
||||
await this.slides!.slideTo(nextSlideIndex);
|
||||
} else {
|
||||
// Slide to the latest slide.
|
||||
await this.slides!.slideTo(this.numTabsShown - 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that shows the previous tab.
|
||||
*/
|
||||
async slidePrev(): Promise<void> {
|
||||
// Stop if slides are in transition.
|
||||
if (!this.showPrevButton || this.isInTransition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.slides!.isEnd()) {
|
||||
this.slides!.slideTo(this.numTabsShown - this.maxSlides * 2);
|
||||
// Slide to the previous of the latest page.
|
||||
} else {
|
||||
const currentIndex = await this.slides!.getActiveIndex();
|
||||
if (typeof currentIndex !== 'undefined') {
|
||||
const prevSlideIndex = currentIndex - this.maxSlides;
|
||||
this.isInTransition = true;
|
||||
if (prevSlideIndex >= 0) {
|
||||
// Slide to the previous page.
|
||||
await this.slides!.slideTo(prevSlideIndex);
|
||||
} else {
|
||||
// Slide to the first page.
|
||||
await this.slides!.slideTo(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide the tabs. This is used when the user is scrolling inside a tab.
|
||||
*
|
||||
* @param scrollEvent Scroll event to check scroll position.
|
||||
* @param content Content element to check measures.
|
||||
*/
|
||||
protected showHideTabs(scrollEvent: CustomEvent, content: HTMLElement): void {
|
||||
if (!this.tabBarElement || !this.tabsElement || !content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always show on very tall screens.
|
||||
if (window.innerHeight >= CoreTabsComponent.MAX_HEIGHT_TO_HIDE_TABS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.tabBarHeight && this.tabBarElement.offsetHeight != this.tabBarHeight) {
|
||||
// Wrong tab height, recalculate it.
|
||||
this.calculateTabBarHeight();
|
||||
}
|
||||
|
||||
if (!this.tabBarHeight) {
|
||||
// We don't have the tab bar height, this means the tab bar isn't shown.
|
||||
return;
|
||||
}
|
||||
|
||||
const scroll = parseInt(scrollEvent.detail.scrollTop, 10);
|
||||
if (scroll <= 0) {
|
||||
// Ensure tabbar is shown.
|
||||
this.tabsElement.style.top = '0';
|
||||
this.tabsElement.style.height = '';
|
||||
this.tabBarElement!.classList.remove('tabs-hidden');
|
||||
this.tabsShown = true;
|
||||
this.lastScroll = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (scroll == this.lastScroll) {
|
||||
// Ensure scroll has been modified to avoid flicks.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.tabsShown && scroll > this.tabBarHeight) {
|
||||
this.tabsShown = false;
|
||||
|
||||
// Hide tabs.
|
||||
this.tabBarElement.classList.add('tabs-hidden');
|
||||
this.tabsElement.style.top = '0';
|
||||
this.tabsElement.style.height = '';
|
||||
} else if (!this.tabsShown && scroll <= this.tabBarHeight) {
|
||||
this.tabsShown = true;
|
||||
this.tabBarElement!.classList.remove('tabs-hidden');
|
||||
}
|
||||
|
||||
if (this.tabsShown && content.scrollHeight > content.clientHeight + (this.tabBarHeight - scroll)) {
|
||||
// Smooth translation.
|
||||
this.tabsElement.style.top = - scroll + 'px';
|
||||
this.tabsElement.style.height = 'calc(100% + ' + scroll + 'px';
|
||||
}
|
||||
// Use lastScroll after moving the tabs to avoid flickering.
|
||||
this.lastScroll = parseInt(scrollEvent.detail.scrollTop, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab selected.
|
||||
*
|
||||
* @param tabId Selected tab index.
|
||||
* @param e Event.
|
||||
*/
|
||||
async selectTab(tabId: string, e?: Event): Promise<void> {
|
||||
let index = this.tabs.findIndex((tab) => tabId == tab.id);
|
||||
if (index < 0 || index >= this.tabs.length) {
|
||||
if (this.selected) {
|
||||
// Invalid index do not change tab.
|
||||
e && e.preventDefault();
|
||||
e && e.stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Index isn't valid, select the first one.
|
||||
index = 0;
|
||||
}
|
||||
|
||||
const selectedTab = this.tabs[index];
|
||||
if (tabId == this.selected || !selectedTab || !selectedTab.enabled) {
|
||||
// Already selected or not enabled.
|
||||
|
||||
e && e.preventDefault();
|
||||
e && e.stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selected) {
|
||||
await this.slides!.slideTo(index);
|
||||
}
|
||||
|
||||
const pageParams: NavigationOptions = {};
|
||||
if (selectedTab.pageParams) {
|
||||
pageParams.queryParams = selectedTab.pageParams;
|
||||
}
|
||||
const ok = await this.navCtrl.navigateForward(selectedTab.page, pageParams);
|
||||
|
||||
if (ok) {
|
||||
this.selectHistory.push(tabId);
|
||||
this.selected = tabId;
|
||||
this.selectedIndex = index;
|
||||
|
||||
this.ionChange.emit(selectedTab);
|
||||
|
||||
const content = this.ionTabs!.outlet.nativeEl.querySelector('ion-content');
|
||||
|
||||
if (content) {
|
||||
const scroll = await content.getScrollElement();
|
||||
content.scrollEvents = true;
|
||||
content.addEventListener('ionScroll', (e: CustomEvent): void => {
|
||||
this.showHideTabs(e, scroll);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt tabs to a window resize.
|
||||
*/
|
||||
protected windowResized(): void {
|
||||
setTimeout(() => {
|
||||
this.calculateSlides();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
|
||||
if (this.resizeFunction) {
|
||||
window.removeEventListener('resize', this.resizeFunction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Core Tab class.
|
||||
*/
|
||||
class CoreTab {
|
||||
|
||||
id = ''; // Unique tab id.
|
||||
class = ''; // Class, if needed.
|
||||
title = ''; // The translatable tab title.
|
||||
icon?: string; // The tab icon.
|
||||
badge?: string; // A badge to add in the tab.
|
||||
badgeStyle?: string; // The badge color.
|
||||
enabled = true; // Whether the tab is enabled.
|
||||
page = ''; // Page to navigate to.
|
||||
pageParams?: Params; // Page params.
|
||||
|
||||
}
|
|
@ -13,9 +13,27 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CoreHomeDelegate } from '../mainmenu/services/home.delegate';
|
||||
import { CoreCoursesDashboardHandler } from './handlers/dashboard';
|
||||
import { CoreCoursesDashboardPage } from './pages/dashboard/dashboard.page';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: CoreCoursesDashboardPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
declarations: [],
|
||||
})
|
||||
export class CoreCoursesModule { }
|
||||
export class CoreCoursesModule {
|
||||
|
||||
constructor(homeDelegate: CoreHomeDelegate) {
|
||||
homeDelegate.registerHandler(new CoreCoursesDashboardHandler());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { CoreHomeHandler, CoreHomeHandlerData } from '@core/mainmenu/services/home.delegate';
|
||||
|
||||
/**
|
||||
* Handler to add Home into main menu.
|
||||
*/
|
||||
export class CoreCoursesDashboardHandler implements CoreHomeHandler {
|
||||
|
||||
name = 'CoreCoursesDashboard';
|
||||
priority = 1100;
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return this.isEnabledForSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a certain site.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async isEnabledForSite(siteId?: string): Promise<boolean> {
|
||||
// @todo
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return Data needed to render the handler.
|
||||
*/
|
||||
getDisplayData(): CoreHomeHandlerData {
|
||||
return {
|
||||
title: 'core.courses.mymoodle',
|
||||
page: 'home/dashboard',
|
||||
class: 'core-courses-dashboard-handler',
|
||||
icon: 'fa-tachometer-alt',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<ion-content>
|
||||
<!-- @todo -->
|
||||
<core-empty-box icon="fa-home" [message]="'core.courses.nocourses' | translate">
|
||||
<div>Dashboard</div>
|
||||
</core-empty-box>
|
||||
</ion-content>
|
|
@ -0,0 +1,47 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
import { CoreCoursesDashboardPage } from './dashboard.page';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreCoursesDashboardPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreCoursesDashboardPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreCoursesDashboardPageModule {}
|
|
@ -0,0 +1,36 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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, OnInit } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Page that displays the Home.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-courses-dashboard',
|
||||
templateUrl: 'dashboard.html',
|
||||
styleUrls: ['dashboard.scss'],
|
||||
})
|
||||
export class CoreCoursesDashboardPage implements OnInit {
|
||||
|
||||
siteName = 'Hello world';
|
||||
|
||||
/**
|
||||
* Initialize the component.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// @todo
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,12 @@
|
|||
</ion-header>
|
||||
<ion-content>
|
||||
<!-- @todo -->
|
||||
<core-loading [hideUntil]="loaded" *ngIf="tabs.length > 0">
|
||||
<core-tabs [selectedIndex]="selectedTab" [hideUntil]="loaded" [tabs]="tabs"></core-tabs>
|
||||
<ng-container *ngIf="tabs.length == 0">
|
||||
<core-empty-box icon="fa-home" [message]="'core.courses.nocourses' | translate">
|
||||
<div>Home page</div>
|
||||
</core-empty-box>
|
||||
</ng-container>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -27,6 +27,13 @@ const routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
component: CoreHomePage,
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard', // @todo: Add this route dynamically.
|
||||
loadChildren: () =>
|
||||
import('@core/courses/pages/dashboard/dashboard.page.module').then(m => m.CoreCoursesDashboardPageModule),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CoreHomeDelegate, CoreHomeHandlerToDisplay } from '../../services/home.delegate';
|
||||
|
||||
/**
|
||||
* Page that displays the Home.
|
||||
|
@ -24,13 +27,65 @@ import { Component, OnInit } from '@angular/core';
|
|||
})
|
||||
export class CoreHomePage implements OnInit {
|
||||
|
||||
siteName = 'Hello world';
|
||||
siteName!: string;
|
||||
tabs: CoreHomeHandlerToDisplay[] = [];
|
||||
loaded = false;
|
||||
selectedTab?: number;
|
||||
|
||||
protected subscription?: Subscription;
|
||||
|
||||
constructor(
|
||||
protected homeDelegate: CoreHomeDelegate,
|
||||
) {
|
||||
this.loadSiteName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the component.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// @todo
|
||||
this.subscription = this.homeDelegate.getHandlers().subscribe((handlers) => {
|
||||
handlers && this.initHandlers(handlers);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Init handlers on change (size or handlers).
|
||||
*/
|
||||
initHandlers(handlers: CoreHomeHandlerToDisplay[]): void {
|
||||
// Re-build the list of tabs. If a handler is already in the list, use existing object to prevent re-creating the tab.
|
||||
const newTabs: CoreHomeHandlerToDisplay[] = handlers.map((handler) => {
|
||||
// Check if the handler is already in the tabs list. If so, use it.
|
||||
const tab = this.tabs.find((tab) => tab.title == handler.title);
|
||||
|
||||
return tab || handler;
|
||||
})
|
||||
// Sort them by priority so new handlers are in the right position.
|
||||
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
if (typeof this.selectedTab == 'undefined') {
|
||||
let maxPriority = 0;
|
||||
let maxIndex = 0;
|
||||
newTabs.forEach((tab, index) => {
|
||||
if ((tab.selectPriority || 0) > maxPriority) {
|
||||
maxPriority = tab.selectPriority || 0;
|
||||
maxIndex = index;
|
||||
}
|
||||
});
|
||||
|
||||
this.selectedTab = maxIndex;
|
||||
}
|
||||
|
||||
this.tabs = newTabs;
|
||||
|
||||
this.loaded = this.homeDelegate.areHandlersLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the site name.
|
||||
*/
|
||||
protected loadSiteName(): void {
|
||||
this.siteName = CoreSites.instance.getCurrentSite()!.getSiteName();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { Injectable } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import { Subject, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
|
||||
/**
|
||||
* Interface that all main menu handlers must implement.
|
||||
*/
|
||||
export interface CoreHomeHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* The highest priority is displayed first.
|
||||
*/
|
||||
priority?: number;
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return Data.
|
||||
*/
|
||||
getDisplayData(): CoreHomeHandlerData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data needed to render a main menu handler. It's returned by the handler.
|
||||
*/
|
||||
export interface CoreHomeHandlerData {
|
||||
/**
|
||||
* Name of the page to load for the handler.
|
||||
*/
|
||||
page: string;
|
||||
|
||||
/**
|
||||
* Title to display for the handler.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Class to add to the displayed handler.
|
||||
*/
|
||||
class?: string;
|
||||
|
||||
/**
|
||||
* If true, the badge number is being loaded. Only used if showBadge is true.
|
||||
*/
|
||||
loading?: boolean;
|
||||
|
||||
/**
|
||||
* Params to pass to the page.
|
||||
*/
|
||||
pageParams?: Params;
|
||||
|
||||
/**
|
||||
* If the handler has badge to show or not.
|
||||
*/
|
||||
showBadge?: boolean;
|
||||
|
||||
/**
|
||||
* Text to display on the badge. Only used if showBadge is true.
|
||||
*/
|
||||
badge?: string;
|
||||
|
||||
/**
|
||||
* Name of the icon to display for the handler.
|
||||
*/
|
||||
icon?: string; // Name of the icon to display in the tab.
|
||||
}
|
||||
|
||||
/**
|
||||
* Data returned by the delegate for each handler.
|
||||
*/
|
||||
export interface CoreHomeHandlerToDisplay extends CoreHomeHandlerData {
|
||||
/**
|
||||
* Name of the handler.
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* Priority of the handler.
|
||||
*/
|
||||
priority?: number;
|
||||
|
||||
/**
|
||||
* Priority to select handler.
|
||||
*/
|
||||
selectPriority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin
|
||||
* and notify an update in the data.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CoreHomeDelegate extends CoreDelegate {
|
||||
|
||||
protected loaded = false;
|
||||
protected siteHandlers: Subject<CoreHomeHandlerToDisplay[]> = new BehaviorSubject<CoreHomeHandlerToDisplay[]>([]);
|
||||
protected featurePrefix = 'CoreHomeDelegate_';
|
||||
|
||||
constructor() {
|
||||
super('CoreHomeDelegate', true);
|
||||
|
||||
CoreEvents.on(CoreEvents.LOGOUT, this.clearSiteHandlers.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handlers are loaded.
|
||||
*
|
||||
* @return True if handlers are loaded, false otherwise.
|
||||
*/
|
||||
areHandlersLoaded(): boolean {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear current site handlers. Reserved for core use.
|
||||
*/
|
||||
protected clearSiteHandlers(): void {
|
||||
this.loaded = false;
|
||||
this.siteHandlers.next([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the handlers for the current site.
|
||||
*
|
||||
* @return An observable that will receive the handlers.
|
||||
*/
|
||||
getHandlers(): Subject<CoreHomeHandlerToDisplay[]> {
|
||||
return this.siteHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update handlers Data.
|
||||
*/
|
||||
updateData(): void {
|
||||
const displayData: CoreHomeHandlerToDisplay[] = [];
|
||||
|
||||
for (const name in this.enabledHandlers) {
|
||||
const handler = <CoreHomeHandler> this.enabledHandlers[name];
|
||||
const data = <CoreHomeHandlerToDisplay> handler.getDisplayData();
|
||||
|
||||
data.name = name;
|
||||
data.priority = handler.priority;
|
||||
|
||||
displayData.push(data);
|
||||
}
|
||||
|
||||
// Sort them by priority.
|
||||
displayData.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
this.loaded = true;
|
||||
this.siteHandlers.next(displayData);
|
||||
}
|
||||
|
||||
}
|
|
@ -753,12 +753,12 @@ export class CoreDomUtilsProvider {
|
|||
* @param findFunction The function used to find the element.
|
||||
* @return Resolved if found, rejected if too many tries.
|
||||
*/
|
||||
waitElementToExist(findFunction: () => HTMLElement): Promise<HTMLElement> {
|
||||
waitElementToExist(findFunction: () => HTMLElement | null): Promise<HTMLElement> {
|
||||
const promiseInterval = CoreUtils.instance.promiseDefer<HTMLElement>();
|
||||
let tries = 100;
|
||||
|
||||
const clear = setInterval(() => {
|
||||
const element: HTMLElement = findFunction();
|
||||
const element: HTMLElement | null = findFunction();
|
||||
|
||||
if (element) {
|
||||
clearInterval(clear);
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
--yellow: var(--custom-yellow, #fbad1a); // Accent (never text).
|
||||
--purple: var(--custom-purple, #8e24aa); // Accent (never text).
|
||||
|
||||
--core-color: var(--custom-main-color, #f98012);
|
||||
--core-color: var(--custom-main-color, var(--orange));
|
||||
|
||||
--ion-color-primary: var(--core-color);
|
||||
--ion-color-primary-rgb: 249,128,18;
|
||||
|
@ -92,21 +92,34 @@
|
|||
--ion-text-color-rgb: 58,58,58;
|
||||
|
||||
ion-content {
|
||||
--background: #e9e9e9;
|
||||
--background: var(--gray-light);
|
||||
}
|
||||
|
||||
ion-tab-bar {
|
||||
--background: #626262;
|
||||
--color: #ffffff;
|
||||
--background: var(--custom-bottom-tabs-background, var(--gray-darker));
|
||||
--color: var(--custom-bottom-tabs-color, var(--white));
|
||||
}
|
||||
|
||||
ion-toolbar {
|
||||
--color: var(--ion-color-primary-contrast);
|
||||
--background: var(--ion-color-primary);
|
||||
--color: var(--custom-toolbar-color, var(--ion-color-primary-contrast));
|
||||
--background: var(--custom-toolbar-background, var(--ion-color-primary));
|
||||
}
|
||||
|
||||
--core-login-background: var(--custom-login-background, white);
|
||||
--core-login-text-color: var(--custom-login-text-color, #3a3a3a);
|
||||
core-tabs {
|
||||
--background: var(--custom-tabs-background, var(--white));
|
||||
ion-slide {
|
||||
--background: var(--custom-tab-background, var(--white));
|
||||
--color: var(--custom-tab-background, var(--gray-dark));
|
||||
--border-color: var(--custom-tab-border-color, var(--gray));
|
||||
--color-active: var(--custom-tab-color-active, var(--core-color));
|
||||
--border-color-active: var(--custom-tab-border-color-active, var(--color-active));
|
||||
}
|
||||
}
|
||||
|
||||
--drop-shadow: var(--custom-drop-shadow, 0, 0, 0, 0.2);
|
||||
|
||||
--core-login-background: var(--custom-login-background, var(--white));
|
||||
--core-login-text-color: var(--custom-login-text-color, var(--black));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -114,7 +127,7 @@
|
|||
* -------------------------------------------
|
||||
*/
|
||||
:root body.dark {
|
||||
--ion-background-color: #3a3a3a;
|
||||
--ion-background-color: #1e1e1e;
|
||||
--ion-background-color-rgb: 18,18,18;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
|
@ -144,7 +157,6 @@
|
|||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-card-background: #1c1c1d;
|
||||
|
@ -153,6 +165,15 @@
|
|||
--background: var(--ion-background-color);
|
||||
}
|
||||
|
||||
core-tabs {
|
||||
--background: var(--custom-tabs-background, #3a3a3a);
|
||||
ion-slide {
|
||||
--background: var(--custom-tab-background, #3a3a3a);
|
||||
--color: var(--custom-tab-background, var(--white));
|
||||
--border-color: var(--custom-tab-border-color, var(--gray-light));
|
||||
}
|
||||
}
|
||||
|
||||
--core-login-background: var(--custom-login-background, #3a3a3a);
|
||||
--core-login-text-color: var(--custom-login-text-color, white);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue