Skip to content

Commit

Permalink
feat(theme): add Dynamic styles
Browse files Browse the repository at this point in the history
  • Loading branch information
alyleui committed Aug 10, 2018
1 parent 52c26c4 commit 09f9de8
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
<button ly-button raised bg="accent">Accent</button>
<button ly-button raised bg="warn">Warn</button>
<button ly-button raised disabled>Disabled</button>

<p>Sizes</p>
<button ly-button raised bg="primary" size="small">small</button>
<button ly-button raised bg="primary" size="medium">medium</button>
<button ly-button raised bg="primary" size="large">large</button>
<p>
custom
</p>
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/routes-app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export class RoutesAppService {
routes: [
{ route: 'theming', name: 'Theming' },
{ route: 'multiple-themes', name: 'Multiple themes' },
{ route: 'bg-color', name: 'bg & color' }
{ route: 'bg-color', name: 'Bg & Color' },
{ route: 'dynamic-styles', name: 'Dynamic styles', status: 'alpha'}
]
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>
dynamic-styles works!
</p>
<a [className]="classes.myStylesRoot">myStylesRoot</a>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { DynamicStylesComponent } from './dynamic-styles.component';

describe('DynamicStylesComponent', () => {
let component: DynamicStylesComponent;
let fixture: ComponentFixture<DynamicStylesComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DynamicStylesComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(DynamicStylesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Component, OnInit, Injectable, ElementRef, Renderer2 } from '@angular/core';
import { LyTheme2, DynamicStyles } from '@alyle/ui';

const myStyles = (theme) => ({
root: {
color: theme.primary.default,
'&:hover': {
color: theme.accent.default
}
}
});

@Injectable({
providedIn: 'root'
})
export class DynamicStylesService {
constructor(
public theme: LyTheme2
) {
this.theme.addStyleSheet(myStyles, 'myStyles');
}
}

@Component({
selector: 'aui-dynamic-styles',
templateUrl: './dynamic-styles.component.html',
styleUrls: ['./dynamic-styles.component.scss']
})
export class DynamicStylesComponent implements OnInit {
classes = this.theme.classes;
constructor(
private dynamicStylesService: DynamicStylesService,
private theme: LyTheme2
) { }

ngOnInit() {
}

}
7 changes: 3 additions & 4 deletions src/app/docs/docs.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { BasicTabsModule } from '@docs/layout/tabs-demo/basic-tabs/basic-tabs.mo
import { DocsRoutingModule } from '@docs/docs.routing';
import { SharedModule } from '../shared/shared.module';
import { TabsWithLazyLoadingModule } from '@docs/layout/tabs-demo/tabs-with-lazy-loading/tabs-with-lazy-loading.module';
import { DynamicStylesComponent } from './customization/dynamic-styles/dynamic-styles.component';

@NgModule({
imports: [
Expand All @@ -23,10 +24,8 @@ import { TabsWithLazyLoadingModule } from '@docs/layout/tabs-demo/tabs-with-lazy
declarations: [
ThemingComponent,
/** Tabs */
TabsDemoComponent
],
exports: [
ThemingComponent,
TabsDemoComponent,
DynamicStylesComponent
]
})
export class DocsModule { }
7 changes: 7 additions & 0 deletions src/app/docs/docs.routing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TabsDemoComponent } from '@docs/layout/tabs-demo/tabs-demo.component';
import { DynamicStylesComponent } from '@docs/customization/dynamic-styles/dynamic-styles.component';

const routes: Routes = [
/** layout */
Expand All @@ -9,6 +10,12 @@ const routes: Routes = [
children: [
{ path: 'tabs', component: TabsDemoComponent },
]
},
{
path: 'customization',
children: [
{ path: 'dynamic-styles', component: DynamicStylesComponent },
]
}
];

Expand Down
1 change: 1 addition & 0 deletions src/lib/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export * from './src/styles/core-styles';
export * from './src/undefined';
export * from './src/media/invert-media-query';
export * from './src/style-utils';
export * from './src/theme/dynamic-styles';
12 changes: 9 additions & 3 deletions src/lib/src/theme/core-theme.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable, Optional, Inject, Renderer2, RendererFactory2, isDevMode } from '@angular/core';
import { THEME_CONFIG, ThemeConfig, THEME_CONFIG_EXTRA, LY_THEME_CONFIG, LyThemeConfig } from './theme-config';
import { Injectable, Optional, Inject, Renderer2, RendererFactory2, isDevMode, ViewEncapsulation } from '@angular/core';
import { THEME_CONFIG, ThemeConfig, LY_THEME_CONFIG, LyThemeConfig } from './theme-config';
import { DOCUMENT } from '@angular/common';
import { StyleContent, StyleData, DataStyle, Style, MultipleStyles } from '../theme.service';
import { Platform } from '../platform';
Expand All @@ -26,7 +26,13 @@ export class CoreTheme {
if (!themeConfig) {
throw new Error('LY_THEME_CONFIG undefined');
}
this.renderer = this.rendererFactory.createRenderer(null, null);
this.renderer = this.rendererFactory.createRenderer(null, {
id: 'ly',
encapsulation: ViewEncapsulation.Native,
styles: [],
data: {}
});
console.log(this.renderer);
if (Platform.isBrowser) {
const mediaStyleContainer = _document.body.querySelector('ly-media-style-container');
const primaryStyleContainer = _document.body.querySelector('ly-primary-style-container');
Expand Down
22 changes: 22 additions & 0 deletions src/lib/src/theme/dynamic-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ElementRef, Renderer2 } from '@angular/core';
import { LyTheme2 } from './theme2.service';
import { Style } from '@alyle/ui';

export class DynamicStyles {
get classes() {
return this.theme.classes;
}
constructor(
private theme: LyTheme2
) {

}
setUpStyle<T>(id: string, style: Style<T>, el?: any, instance?: string) {
const newClass = this.theme.setUpStyle<T>(id, style);
if (instance) {
el.classList.remove(instance);
}
el.classList.add(newClass);
return newClass;
}
}
1 change: 0 additions & 1 deletion src/lib/src/theme/theme-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { InjectionToken } from '@angular/core';

export const THEME_CONFIG = new InjectionToken<ThemeConfig | ThemeConfig[]>('ly.theme.config.root');
export const LY_THEME_CONFIG = new InjectionToken<LyThemeConfig>('ly_theme_config');
export const THEME_CONFIG_EXTRA = new InjectionToken<ThemeConfig | ThemeConfig[]>('ly.theme.config.extra');
export const LY_THEME_NAME = new InjectionToken<string>('ly.theme.name');

export interface ThemeConfig {
Expand Down
144 changes: 143 additions & 1 deletion src/lib/src/theme/theme2.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,29 @@ import { DataStyle, Style } from '../theme.service';
import { LyThemeContainer } from './theme.directive';
import { InvertMediaQuery } from '../media/invert-media-query';

export interface StyleItem {
id: string;
el: any;
styles: StylesFn2<any> | Styles2;
}

const STYLE_MAP: {
[key: string]: Map<string, StyleItem>
} = {};
const CLASSES_MAP: {
[key: string]: string
} = {};
let nextId = 0;
@Injectable()
export class LyTheme2 {
config: ThemeConfig;
_styleMap: Map<string, DataStyle>;
prefix = 'k';
private _styleMap2: Map<string, StyleItem>;

get classes() {
return CLASSES_MAP;
}

constructor(
public core: CoreTheme,
Expand All @@ -20,6 +39,9 @@ export class LyTheme2 {
}
setUpTheme(themeName: string) {
if (!this.config) {
this._styleMap2 = themeName in STYLE_MAP
? STYLE_MAP[themeName]
: STYLE_MAP[themeName] = new Map();
this.config = this.core.get(themeName);
this._styleMap = new Map<string, DataStyle>();
}
Expand All @@ -42,6 +64,22 @@ export class LyTheme2 {
const name = this.config.name;
return this.core._ĸreateStyle<T>(this.config, key, styles, this._styleMap, name, this.core.secondaryStyleContainer, media, invertMediaQuery);
}

/**
* Add a new dynamic style, use only within @Input()
* @param id Unique id
* @param style Styles
* @param el Element
* @param instance The instance of this, this replaces the existing style with a new one when it changes
*/
addStyle<T>(id: string, style: Style<T>, el?: any, instance?: string) {
const newClass = this.setUpStyle<T>(id, style);
if (instance) {
el.classList.remove(instance);
}
el.classList.add(newClass);
return newClass;
}
colorOf(value: string): string {
return get(this.config, value);
}
Expand All @@ -54,11 +92,102 @@ export class LyTheme2 {
}
setTheme(nam: string) {
this.config = this.core.get(nam);
this._styleMap.forEach((dataStyle, key) => {
this._styleMap2.forEach(dataStyle => {
dataStyle.el.innerText = this._createStyleContent2(dataStyle.styles, dataStyle.id).content;
});
this._styleMap.forEach((dataStyle) => {
dataStyle.styleElement.innerText = this.core._createStyleContent(this.config, dataStyle.style, dataStyle.id);
});
}
/**
* Add new add a new style sheet
* @param styles styles
* @param id unique id for group
*/
addStyleSheet<T>(styles: StylesFn2<T> | Styles2, id?: string) {
const newId = id || '';
const styleContent = this._createStyleContent2(styles, newId);
const styleText = this.core.renderer.createText(styleContent.content);
const styleElement = this.core.renderer.createElement('style');
this.core.renderer.appendChild(styleElement, styleText);
this.core.renderer.appendChild(this.core.primaryStyleContainer, styleElement);
this._styleMap2.set(styleContent.key, {
id: newId,
el: styleElement,
styles
});
}

_createStyleContent2<T>(styles: StylesFn2<T> | Styles2, id: string) {
if (typeof styles === 'function') {
return groupStyleToString(styles(this.config), id);
} else {
return groupStyleToString(styles, id);
}
}
}

export interface Styles2 {
[key: string]: Styles2 | string;
}
export type StylesFn2<T> = (T) => Styles2;

function groupStyleToString(styles: Styles2, id: string) {
let content = '';
let newKey = '';
for (const key in styles) {
if (styles.hasOwnProperty(key)) {
newKey += key;
// if (styleMap.has(key)) {
// content += styleMap.get(key);
// } else {
const value = styles[key];
if (typeof value === 'object') {
const classId = id + capitalizeFirstLetter(key);
const className = classId in CLASSES_MAP
? CLASSES_MAP[classId]
: CLASSES_MAP[classId] = `e${nextId++}`;
const style = styleToString(value as Styles2, `.${className}`);
// styleMap.set((key), style);
content += style;
} else {
console.log('value is string', value);
}
// }
}
}
return {
key: id + newKey,
content
};
}

/**
* {color:'red'} to .className{color: red}
*/
function styleToString(ob: Object, className: string, parentClassName?: string) {
let content = '';
let keyAndValue = '';
for (const styleKey in ob) {
if (ob.hasOwnProperty(styleKey)) {
const element = ob[styleKey];
if (typeof element === 'object') {
content += styleToString(element as Styles2, styleKey, className);
} else {
keyAndValue += `${styleKey}:${element};`;
}
}
}
let newClassName = '';
if (parentClassName) {
newClassName += className.indexOf('&') === 0 ? `${parentClassName}${className.slice(1)}` : `${parentClassName} .${className}`;
} else {
newClassName += className;
}
content += `${newClassName}{${keyAndValue}}`;
return content;
}


function get(obj: Object, path: any): string {
const _path: string[] = path instanceof Array ? path : path.split(':');
Expand All @@ -71,3 +200,16 @@ function get(obj: Object, path: any): string {
export function toHyphenCase(str: string) {
return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
}

export function capitalizeFirstLetter(str: string) {
return str[0].toUpperCase() + str.slice(1);
}

@Injectable({
providedIn: 'root'
})
export class LyClasses {
get classes() {
return CLASSES_MAP;
}
}

0 comments on commit 09f9de8

Please sign in to comment.