Skip to content

Commit

Permalink
Add Theme variants (#684)
Browse files Browse the repository at this point in the history
* theme-variant-config

* correct set

* mixin changes

* allow theme config to be passed via theme prop

* fix function override

* use template string

* pr comments

* tests for themed mixin / middleware

* type theme injector

* fix code coverage
  • Loading branch information
tomdye authored Mar 24, 2020
1 parent 2176206 commit 3442c85
Show file tree
Hide file tree
Showing 6 changed files with 430 additions and 41 deletions.
41 changes: 13 additions & 28 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions src/core/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ export interface Theme {
[key: string]: object;
}

export interface Variant {
root: string;
}

export interface ThemeWithVariant {
css: Theme | ThemeWithVariants;
variant: Variant | string;
}

export interface ThemeWithVariants {
css: Theme;
variants: {
default: Variant;
[key: string]: Variant;
};
}

export interface Classes {
[widgetKey: string]: {
[classKey: string]: SupportedClassName[];
Expand Down
59 changes: 50 additions & 9 deletions src/core/middleware/theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Theme, Classes, ClassNames } from './../interfaces';
import { Theme, Classes, ClassNames, ThemeWithVariant, ThemeWithVariants, Variant } from './../interfaces';
import { create, invalidator, diffProperty, getRegistry } from '../vdom';
import icache from './icache';
import injector from './injector';
Expand All @@ -10,15 +10,27 @@ import Registry from '../Registry';
export { Theme, Classes, ClassNames } from './../interfaces';

export interface ThemeProperties {
theme?: Theme;
theme?: Theme | ThemeWithVariant;
classes?: Classes;
}

export const THEME_KEY = ' _key';

export const INJECTED_THEME_KEY = '__theme_injector';

function registerThemeInjector(theme: any, themeRegistry: Registry): Injector {
function isThemeWithVariant(theme: Theme | ThemeWithVariant): theme is ThemeWithVariant {
return theme && theme.hasOwnProperty('variant');
}

function isThemeWithVariants(theme: Theme | ThemeWithVariants): theme is ThemeWithVariants {
return theme && theme.hasOwnProperty('variants');
}

function isVariantModule(variant: string | Variant): variant is Variant {
return typeof variant !== 'string';
}

function registerThemeInjector(theme: Theme | ThemeWithVariant | undefined, themeRegistry: Registry): Injector {
const themeInjector = new Injector(theme);
themeRegistry.defineInjector(INJECTED_THEME_KEY, (invalidator) => {
themeInjector.setInvalidator(invalidator);
Expand All @@ -40,6 +52,7 @@ export const theme = factory(
icache.clear();
invalidator();
}

if (!next.theme && themeInjector) {
return themeInjector.get();
}
Expand Down Expand Up @@ -75,6 +88,21 @@ export const theme = factory(
icache.clear();
invalidator();
});

function set(theme: Theme): void;
function set<T extends ThemeWithVariants>(theme: T, variant?: keyof T['variants']): void;
function set<T extends ThemeWithVariants>(theme: Theme | T, variant?: keyof T['variants']): void {
const currentTheme = injector.get<Injector<Theme | ThemeWithVariant | undefined>>(INJECTED_THEME_KEY);

if (currentTheme) {
if (isThemeWithVariants(theme)) {
theme = { css: theme.css, variant: theme.variants[`${variant || 'default'}`] };
}

currentTheme.set(theme);
}
}

return {
classes<T extends ClassNames>(css: T): T {
let theme = icache.get<T>(css);
Expand All @@ -85,6 +113,11 @@ export const theme = factory(
themeKeys.add(key);
theme = classes as T;
let { theme: currentTheme, classes: currentClasses } = properties();

if (currentTheme && isThemeWithVariant(currentTheme)) {
currentTheme = isThemeWithVariants(currentTheme.css) ? currentTheme.css.css : currentTheme.css;
}

if (currentTheme && currentTheme[key]) {
theme = { ...theme, ...currentTheme[key] };
}
Expand All @@ -100,14 +133,22 @@ export const theme = factory(
icache.set(css, theme, false);
return theme;
},
set(css: Theme): void {
const currentTheme = injector.get<Injector<Theme | undefined>>(INJECTED_THEME_KEY);
if (currentTheme) {
currentTheme.set(css);
variant() {
const { theme } = properties();

if (theme && isThemeWithVariant(theme)) {
if (isVariantModule(theme.variant)) {
return theme.variant.root;
}

if (isThemeWithVariants(theme.css)) {
return theme.css.variants[theme.variant].root;
}
}
},
get(): Theme | undefined {
const currentTheme = injector.get<Injector<Theme | undefined>>(INJECTED_THEME_KEY);
set,
get(): Theme | ThemeWithVariant | undefined {
const currentTheme = injector.get<Injector<Theme | ThemeWithVariant | undefined>>(INJECTED_THEME_KEY);
if (currentTheme) {
return currentTheme.get();
}
Expand Down
48 changes: 44 additions & 4 deletions src/core/mixins/Themed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { Theme, Classes, ClassNames, Constructor, SupportedClassName } from './../interfaces';
import {
Theme,
Classes,
ClassNames,
Constructor,
SupportedClassName,
ThemeWithVariant,
ThemeWithVariants,
Variant
} from './../interfaces';
import { Registry } from './../Registry';
import { Injector } from './../Injector';
import { inject } from './../decorators/inject';
Expand All @@ -13,7 +22,7 @@ export { Theme, Classes, ClassNames } from './../interfaces';
*/
export interface ThemedProperties<T = ClassNames> {
/** Overriding custom theme for the widget */
theme?: Theme;
theme?: Theme | ThemeWithVariant;
/** Map of widget keys and associated overriding classes */
classes?: Classes;
/** Extra classes to be applied to the widget */
Expand All @@ -24,12 +33,25 @@ export const THEME_KEY = ' _key';

export const INJECTED_THEME_KEY = '__theme_injector';

function isThemeVariant(theme: Theme | ThemeWithVariant): theme is ThemeWithVariant {
return theme.hasOwnProperty('variant');
}

function isThemeVariantConfig(theme: Theme | ThemeWithVariants): theme is ThemeWithVariants {
return theme.hasOwnProperty('variants');
}

function isVariantModule(variant: string | Variant): variant is Variant {
return typeof variant !== 'string';
}

/**
* Interface for the ThemedMixin
*/
export interface ThemedMixin<T = ClassNames> {
theme(classes: SupportedClassName): SupportedClassName;
theme(classes: SupportedClassName[]): SupportedClassName[];
variant(): string | undefined;
properties: ThemedProperties<T>;
}

Expand Down Expand Up @@ -70,7 +92,7 @@ function createThemeClassesLookup(classes: ClassNames[]): ClassNames {
*
* @returns the theme injector used to set the theme
*/
export function registerThemeInjector(theme: any, themeRegistry: Registry): Injector {
export function registerThemeInjector(theme: Theme | ThemeWithVariant, themeRegistry: Registry): Injector {
const themeInjector = new Injector(theme);
themeRegistry.defineInjector(INJECTED_THEME_KEY, (invalidator) => {
themeInjector.setInvalidator(invalidator);
Expand Down Expand Up @@ -140,6 +162,16 @@ export function ThemedMixin<E, T extends Constructor<WidgetBase<ThemedProperties
return this._getThemeClass(classes);
}

public variant() {
const { theme } = this.properties;

if (theme && isThemeVariant(theme)) {
if (isVariantModule(theme.variant)) {
return theme.variant.root;
}
}
}

/**
* Function fired when `theme` or `extraClasses` are changed.
*/
Expand Down Expand Up @@ -199,7 +231,15 @@ export function ThemedMixin<E, T extends Constructor<WidgetBase<ThemedProperties
}

private _recalculateThemeClasses() {
const { theme = {}, classes = {} } = this.properties;
let { theme: themeProp = {}, classes = {} } = this.properties;
let theme: Theme;

if (isThemeVariant(themeProp)) {
theme = isThemeVariantConfig(themeProp.css) ? themeProp.css.css : themeProp.css;
} else {
theme = themeProp;
}

if (!this._registeredBaseTheme) {
const baseThemes = this.getDecorator('baseThemeClasses');
if (baseThemes.length === 0) {
Expand Down
Loading

0 comments on commit 3442c85

Please sign in to comment.