feat(docs-infra): port over material io theming logic to angular io ()

brings in theming tools from material io into angular io in preparation of implementing darkmode

PR Close 
This commit is contained in:
AleksanderBodurri 2021-02-05 21:18:31 -05:00 committed by Andrew Kushnir
parent e73fef4d0b
commit 85a627f741
18 changed files with 262 additions and 10 deletions

@ -41,7 +41,17 @@
"src/google385281288605d160.html"
],
"styles": [
"src/styles/main.scss"
"src/styles/main.scss",
{
"inject": false,
"input": "src/styles/custom-themes/light.scss",
"bundleName": "assets/light"
},
{
"inject": false,
"input": "src/styles/custom-themes/dark.scss",
"bundleName": "assets/dark"
},
],
"scripts": [],
"budgets": [

@ -1,5 +1,4 @@
<div id="top-of-page"></div>
<div *ngIf="isFetching" class="progress-bar-container">
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
</div>
@ -24,6 +23,7 @@
</a>
<aio-top-menu *ngIf="showTopMenu" [nodes]="topMenuNodes" [currentNode]="currentNodes?.TopBar"></aio-top-menu>
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
<aio-theme-picker></aio-theme-picker>
<div class="toolbar-external-icons-container">
<a href="https://twitter.com/angular" title="Twitter" aria-label="Angular on twitter">
<mat-icon svgIcon="logos:twitter"></mat-icon>

@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SearchResultsComponent } from './search-results/search-results.component';
import { SelectComponent } from './select/select.component';
import { ThemePickerComponent } from './theme-picker/theme-picker.component';
@NgModule({
imports: [
@ -9,11 +10,13 @@ import { SelectComponent } from './select/select.component';
],
exports: [
SearchResultsComponent,
SelectComponent
SelectComponent,
ThemePickerComponent
],
declarations: [
SearchResultsComponent,
SelectComponent
SelectComponent,
ThemePickerComponent
]
})
export class SharedModule {}

@ -0,0 +1,3 @@
// Taken from Angular Material docs repo
export * from './style-manager';

@ -0,0 +1,56 @@
// Taken from Angular Material docs repo
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {inject, TestBed} from '@angular/core/testing';
import {StyleManager} from './style-manager';
describe('StyleManager', () => {
let styleManager: StyleManager;
beforeEach(() => TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [StyleManager]
}));
beforeEach(inject([StyleManager], (sm: StyleManager) => {
styleManager = sm;
}));
afterEach(() => {
const links = document.head.querySelectorAll('link');
for (const link of Array.prototype.slice.call(links)) {
if (link.className.includes('style-manager-')) {
document.head.removeChild(link);
}
}
});
it('should add stylesheet to head', () => {
styleManager.setStyle('test', 'test.css');
const styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);
});
it('should change existing stylesheet', () => {
styleManager.setStyle('test', 'test.css');
const styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);
styleManager.setStyle('test', 'new.css');
expect(styleEl.href.endsWith('new.css')).toBe(true);
});
it('should remove existing stylesheet', () => {
styleManager.setStyle('test', 'test.css');
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);
styleManager.removeStyle('test');
styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).toBeNull();
});
});

@ -0,0 +1,48 @@
// Taken from Angular Material docs repo
import {Injectable} from '@angular/core';
/**
* Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be
* removed or changed later.
*/
@Injectable()
export class StyleManager {
/**
* Set the stylesheet with the specified key.
*/
setStyle(key: string, href: string) {
getLinkElementForKey(key).setAttribute('href', href);
}
/**
* Remove the stylesheet with the specified key.
*/
removeStyle(key: string) {
const existingLinkElement = getExistingLinkElementByKey(key);
if (existingLinkElement) {
document.head.removeChild(existingLinkElement);
}
}
}
function getLinkElementForKey(key: string) {
return getExistingLinkElementByKey(key) || createLinkElementWithKey(key);
}
function getExistingLinkElementByKey(key: string) {
return document.head.querySelector(`link[rel="stylesheet"].${getClassNameForKey(key)}`);
}
function createLinkElementWithKey(key: string) {
const linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'stylesheet');
linkEl.classList.add(getClassNameForKey(key));
document.head.appendChild(linkEl);
return linkEl;
}
function getClassNameForKey(key: string) {
return `style-manager-${key}`;
}

@ -0,0 +1,3 @@
<button (click)="selectTheme({ 'light': 'dark', 'dark': 'light' }[currentTheme])">
Change theme (Current: {{ currentTheme }})
</button>

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ThemePickerComponent } from './theme-picker.component';
describe('ThemePickerComponent', () => {
let component: ThemePickerComponent;
let fixture: ComponentFixture<ThemePickerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ThemePickerComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ThemePickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { StyleManager } from './style-manager';
import { ThemeStorage } from './theme-storage/theme-storage';
@Component({
selector: 'aio-theme-picker',
templateUrl: './theme-picker.component.html',
providers: [ThemeStorage, StyleManager]
})
export class ThemePickerComponent implements OnInit {
currentTheme: 'light' | 'dark' = 'light';
constructor(private _themeStorage: ThemeStorage, private _styleManager: StyleManager) { }
ngOnInit(): void {
const theme = this._themeStorage.getStoredThemeName();
if (theme) {
this.selectTheme(theme);
}
}
selectTheme(theme: string) {
this.currentTheme = theme as 'light' | 'dark';
if (theme === 'light') {
this._styleManager.removeStyle('theme');
} else {
this._styleManager.setStyle('theme', `assets/${theme}.css`);
}
if (this.currentTheme) {
this._themeStorage.storeTheme(this.currentTheme);
}
}
}

@ -0,0 +1,3 @@
// Taken from Angular Material docs repo
export * from './theme-storage';

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
@Injectable()
export class ThemeStorage {
static storageKey = 'io-theme-storage-current-name';
storeTheme(theme: 'light' | 'dark') {
try {
window.localStorage[ThemeStorage.storageKey] = theme;
} catch { }
}
getStoredThemeName(): 'light' | 'dark' | null {
try {
return window.localStorage[ThemeStorage.storageKey] || null;
} catch {
return null;
}
}
clearStorage(): void {
try {
window.localStorage.removeItem(ThemeStorage.storageKey);
} catch { }
}
}

@ -6,7 +6,7 @@
@import 'doc-viewer';
@import 'footer';
@import 'layout-global';
@import 'marketing-layout';
@import 'marketing-layout/marketing-layout';
@import 'not-found';
@import 'sidenav';
@import 'top-menu';

@ -0,0 +1,13 @@
@mixin marketing-layout-theme($theme) {
$primary: map-get($theme, primary);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
.background-sky {
background-color: mat-color($primary, default);
background: linear-gradient(145deg, mat-color($primary, 900), mat-color($primary, 400));
color: mat-color($foreground, text);
}
}

@ -183,11 +183,6 @@ section#intro {
}
// ANGULAR LINE
.background-sky {
background-color: $blue;
background: $bluegradient;
color: $white;
}
.home-row .card {
@include card(70%, auto);

@ -0,0 +1,7 @@
@import '~@angular/material/theming';
@import '1-layouts/marketing-layout/marketing-layout-theme';
@mixin app-theme($theme) {
@include marketing-layout-theme($theme);
}

@ -0,0 +1,10 @@
@import '../app-theme';
$ng-io-primary: mat-palette($mat-red, 700, 600, 800);
$ng-io-accent: mat-palette($mat-blue, 700, 600, 800);
$ng-io-warn: mat-palette($mat-blue);
$ng-io-theme: mat-dark-theme($ng-io-primary, $ng-io-accent, $ng-io-warn);
@include angular-material-theme($ng-io-theme);
@include app-theme($ng-io-theme);

@ -0,0 +1,10 @@
@import '../app-theme';
$ng-io-primary: mat-palette($mat-blue, 700, 600, 800);
$ng-io-accent: mat-palette($mat-red, 700, 600, 800);
$ng-io-warn: mat-palette($mat-red);
$ng-io-theme: mat-light-theme($ng-io-primary, $ng-io-accent, $ng-io-warn);
@include angular-material-theme($ng-io-theme);
@include app-theme($ng-io-theme);

@ -1,4 +1,5 @@
@import '~@angular/material/theming';
@import 'app-theme';
// Plus imports for other components in your app.
// Include the base styles for Angular Material core. We include this here so that you only
@ -21,3 +22,4 @@ $ng-io-theme: mat-light-theme($ng-io-primary, $ng-io-accent, $ng-io-warn);
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($ng-io-theme);
@include app-theme($ng-io-theme);