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"
|
||||
],
|
||||
"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);
|
Loading…
Reference in New Issue