feat(docs-infra): port over material io theming logic to angular io (#41129)
brings in theming tools from material io into angular io in preparation of implementing darkmode PR Close #41129
This commit is contained in:
parent
e73fef4d0b
commit
85a627f741
|
@ -41,7 +41,17 @@
|
||||||
"src/google385281288605d160.html"
|
"src/google385281288605d160.html"
|
||||||
],
|
],
|
||||||
"styles": [
|
"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": [],
|
"scripts": [],
|
||||||
"budgets": [
|
"budgets": [
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<div id="top-of-page"></div>
|
<div id="top-of-page"></div>
|
||||||
|
|
||||||
<div *ngIf="isFetching" class="progress-bar-container">
|
<div *ngIf="isFetching" class="progress-bar-container">
|
||||||
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
|
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,6 +23,7 @@
|
||||||
</a>
|
</a>
|
||||||
<aio-top-menu *ngIf="showTopMenu" [nodes]="topMenuNodes" [currentNode]="currentNodes?.TopBar"></aio-top-menu>
|
<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-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">
|
<div class="toolbar-external-icons-container">
|
||||||
<a href="https://twitter.com/angular" title="Twitter" aria-label="Angular on twitter">
|
<a href="https://twitter.com/angular" title="Twitter" aria-label="Angular on twitter">
|
||||||
<mat-icon svgIcon="logos:twitter"></mat-icon>
|
<mat-icon svgIcon="logos:twitter"></mat-icon>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { SearchResultsComponent } from './search-results/search-results.component';
|
import { SearchResultsComponent } from './search-results/search-results.component';
|
||||||
import { SelectComponent } from './select/select.component';
|
import { SelectComponent } from './select/select.component';
|
||||||
|
import { ThemePickerComponent } from './theme-picker/theme-picker.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -9,11 +10,13 @@ import { SelectComponent } from './select/select.component';
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
SelectComponent
|
SelectComponent,
|
||||||
|
ThemePickerComponent
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
SelectComponent
|
SelectComponent,
|
||||||
|
ThemePickerComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SharedModule {}
|
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 'doc-viewer';
|
||||||
@import 'footer';
|
@import 'footer';
|
||||||
@import 'layout-global';
|
@import 'layout-global';
|
||||||
@import 'marketing-layout';
|
@import 'marketing-layout/marketing-layout';
|
||||||
@import 'not-found';
|
@import 'not-found';
|
||||||
@import 'sidenav';
|
@import 'sidenav';
|
||||||
@import 'top-menu';
|
@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
|
// ANGULAR LINE
|
||||||
.background-sky {
|
|
||||||
background-color: $blue;
|
|
||||||
background: $bluegradient;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-row .card {
|
.home-row .card {
|
||||||
@include card(70%, auto);
|
@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 '~@angular/material/theming';
|
||||||
|
@import 'app-theme';
|
||||||
// Plus imports for other components in your app.
|
// Plus imports for other components in your app.
|
||||||
|
|
||||||
// Include the base styles for Angular Material core. We include this here so that you only
|
// 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
|
// Alternatively, you can import and @include the theme mixins for each component
|
||||||
// that you are using.
|
// that you are using.
|
||||||
@include angular-material-theme($ng-io-theme);
|
@include angular-material-theme($ng-io-theme);
|
||||||
|
@include app-theme($ng-io-theme);
|
Loading…
Reference in New Issue