Skip to content

Feature/theme spreads #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@ declare module "react-css-themr"
getWrappedInstance(): React.Component<P, S>;
}

interface ThemedComponentClass<P, S> extends React.ComponentClass<P>
{
new(props?: P, context?: any): ThemedComponent<P, S>;
}
interface ThemedComponentClass<P, S> extends React.ComponentClass<P> {
new(props?: P, context?: any): ThemedComponent<P, S>;
}

export function themr(
identifier: string,
defaultTheme?: {},
options?: IThemrOptions
): <P, S>(component: new(props?: P, context?: any) => React.Component<P, S>) => ThemedComponentClass<P, S>;
export function themr(
identifier: string | number | symbol,
defaultTheme?: {},
options?: IThemrOptions
): <P, S>(component: (new(props?: P, context?: any) => React.Component<P, S>) | React.SFC<P>) => ThemedComponentClass<P, S>;
}
138 changes: 103 additions & 35 deletions lib/components/themr.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,51 +190,119 @@ exports.default = function (componentName, localTheme) {
};

/**
* Merges two themes by concatenating values with the same keys
* @param {TReactCSSThemrTheme} [original] - Original theme object
* @param {TReactCSSThemrTheme} [mixin] - Mixing theme object
* @returns {TReactCSSThemrTheme} - Merged resulting theme
* Merges passed themes by concatenating string keys and processing nested themes
* @param {...TReactCSSThemrTheme} themes - Themes
* @returns {TReactCSSThemrTheme} - Resulting theme
*/


function themeable() {
var original = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var mixin = arguments[1];

//don't merge if no mixin is passed
if (!mixin) return original;

//merge themes by concatenating values with the same keys
return Object.keys(mixin).reduce(

//merging reducer
function (result, key) {
var _extends3;
for (var _len2 = arguments.length, themes = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
themes[_key2] = arguments[_key2];
}

var originalValue = original[key] || '';
var mixinValue = mixin[key] || '';
return themes.reduce(function (acc, theme) {
return merge(acc, theme);
}, {});
}

var newValue = void 0;
/**
* @param {TReactCSSThemrTheme} [original] - Original theme
* @param {TReactCSSThemrTheme} [mixin] - Mixin theme
* @returns {TReactCSSThemrTheme} - resulting theme
*/
function merge() {
var original = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var mixin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

//make a copy to avoid mutations of nested objects
//also strip all functions injected by isomorphic-style-loader
var result = Object.keys(original).reduce(function (acc, key) {
var value = original[key];
if (typeof value !== 'function') {
acc[key] = value;
}
return acc;
}, {});

//traverse mixin keys and merge them to resulting theme
Object.keys(mixin).forEach(function (key) {
//there's no need to set any defaults here
var originalValue = result[key];
var mixinValue = mixin[key];

switch (typeof mixinValue === 'undefined' ? 'undefined' : _typeof(mixinValue)) {
case 'object':
{
//possibly nested theme object
switch (typeof originalValue === 'undefined' ? 'undefined' : _typeof(originalValue)) {
case 'object':
{
//exactly nested theme object - go recursive
result[key] = merge(originalValue, mixinValue);
break;
}

case 'undefined':
{
//original does not contain this nested key - just take it as is
result[key] = mixinValue;
break;
}

default:
{
//can't merge an object with a non-object
throw new Error('You are merging object ' + key + ' with a non-object ' + originalValue);
}
}
break;
}

//when you are mixing an string with a object it should fail
(0, _invariant2.default)(!(typeof originalValue === 'string' && (typeof mixinValue === 'undefined' ? 'undefined' : _typeof(mixinValue)) === 'object'), 'You are merging a string "' + originalValue + '" with an Object,' + 'Make sure you are passing the proper theme descriptors.');
case 'undefined': //fallthrough - handles accidentally unset values which may come from props
case 'function':
{
//this handles issue when isomorphic-style-loader addes helper functions to css-module
break; //just skip
}

//check if values are nested objects
if ((typeof originalValue === 'undefined' ? 'undefined' : _typeof(originalValue)) === 'object' && (typeof mixinValue === 'undefined' ? 'undefined' : _typeof(mixinValue)) === 'object') {
//go recursive
newValue = themeable(originalValue, mixinValue);
} else {
//either concat or take mixin value
newValue = originalValue.split(' ').concat(mixinValue.split(' ')).filter(function (item, pos, self) {
return self.indexOf(item) === pos && item !== '';
}).join(' ');
default:
{
//plain values
switch (typeof originalValue === 'undefined' ? 'undefined' : _typeof(originalValue)) {
case 'object':
{
//can't merge a non-object with an object
throw new Error('You are merging non-object ' + mixinValue + ' with an object ' + key);
}

case 'undefined':
{
//mixin key is new to original theme - take it as is
result[key] = mixinValue;
break;
}
case 'function':
{
//this handles issue when isomorphic-style-loader addes helper functions to css-module
break; //just skip
}

default:
{
//finally we can merge
result[key] = originalValue.split(' ').concat(mixinValue.split(' ')).filter(function (item, pos, self) {
return self.indexOf(item) === pos && item !== '';
}).join(' ');
break;
}
}
break;
}
}
});

return _extends({}, result, (_extends3 = {}, _extends3[key] = newValue, _extends3));
},

//use original theme as an acc
original);
return result;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "react-css-themr-legacy",
"description": "React CSS Themr",
"homepage": "https://github.com/mitoyarzun/react-css-themr#readme",
"version": "1.8.2",
"version": "1.8.3",
"main": "./lib",
"author": {
"email": "[email protected]",
Expand All @@ -27,6 +27,7 @@
"invariant": "^2.2.1"
},
"devDependencies": {
"@types/react": "~15.0.4",
"babel-cli": "^6.7.7",
"babel-core": "^6.18.0",
"babel-eslint": "^7.1.1",
Expand Down
127 changes: 87 additions & 40 deletions src/components/themr.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
getNamespacedTheme(props) {
const { themeNamespace, theme } = props
if (!themeNamespace) return theme
if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
'themeNamespace prop should be used only with theme prop.')

return Object.keys(theme)
Expand Down Expand Up @@ -165,52 +165,99 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
}

/**
* Merges two themes by concatenating values with the same keys
* @param {TReactCSSThemrTheme} [original] - Original theme object
* @param {TReactCSSThemrTheme} [mixin] - Mixing theme object
* @returns {TReactCSSThemrTheme} - Merged resulting theme
* Merges passed themes by concatenating string keys and processing nested themes
* @param {...TReactCSSThemrTheme} themes - Themes
* @returns {TReactCSSThemrTheme} - Resulting theme
*/
export function themeable(original = {}, mixin) {
//don't merge if no mixin is passed
if (!mixin) return original

//merge themes by concatenating values with the same keys
return Object.keys(mixin).reduce(

//merging reducer
(result, key) => {
const originalValue = original[key] || ''
const mixinValue = mixin[key] || ''

let newValue
export function themeable(...themes) {
return themes.reduce((acc, theme) => merge(acc, theme), {})
}

//when you are mixing an string with a object it should fail
invariant(!(typeof originalValue === 'string' && typeof mixinValue === 'object'),
`You are merging a string "${originalValue}" with an Object,` +
'Make sure you are passing the proper theme descriptors.'
)
/**
* @param {TReactCSSThemrTheme} [original] - Original theme
* @param {TReactCSSThemrTheme} [mixin] - Mixin theme
* @returns {TReactCSSThemrTheme} - resulting theme
*/
function merge(original = {}, mixin = {}) {
//make a copy to avoid mutations of nested objects
//also strip all functions injected by isomorphic-style-loader
const result = Object.keys(original).reduce((acc, key) => {
const value = original[key]
if (typeof value !== 'function') {
acc[key] = value
}
return acc
}, {})

//traverse mixin keys and merge them to resulting theme
Object.keys(mixin).forEach(key => {
//there's no need to set any defaults here
const originalValue = result[key]
const mixinValue = mixin[key]

switch (typeof mixinValue) {
case 'object': {
//possibly nested theme object
switch (typeof originalValue) {
case 'object': {
//exactly nested theme object - go recursive
result[key] = merge(originalValue, mixinValue)
break
}

case 'undefined': {
//original does not contain this nested key - just take it as is
result[key] = mixinValue
break
}

default: {
//can't merge an object with a non-object
throw new Error(`You are merging object ${key} with a non-object ${originalValue}`)
}
}
break
}

//check if values are nested objects
if (typeof originalValue === 'object' && typeof mixinValue === 'object') {
//go recursive
newValue = themeable(originalValue, mixinValue)
} else {
//either concat or take mixin value
newValue = originalValue.split(' ')
.concat(mixinValue.split(' '))
.filter((item, pos, self) => self.indexOf(item) === pos && item !== '')
.join(' ')
case 'undefined': //fallthrough - handles accidentally unset values which may come from props
case 'function': {
//this handles issue when isomorphic-style-loader addes helper functions to css-module
break //just skip
}

return {
...result,
[key]: newValue
default: {
//plain values
switch (typeof originalValue) {
case 'object': {
//can't merge a non-object with an object
throw new Error(`You are merging non-object ${mixinValue} with an object ${key}`)
}

case 'undefined': {
//mixin key is new to original theme - take it as is
result[key] = mixinValue
break
}
case 'function': {
//this handles issue when isomorphic-style-loader addes helper functions to css-module
break //just skip
}

default: {
//finally we can merge
result[key] = originalValue.split(' ')
.concat(mixinValue.split(' '))
.filter((item, pos, self) => self.indexOf(item) === pos && item !== '')
.join(' ')
break
}
}
break
}
},
}
})

//use original theme as an acc
original
)
return result
}

/**
Expand Down
Loading