Skip to content

Commit bb5ddda

Browse files
author
Palash Karia
authored
Feature/theme spreads (#1)
1 parent 2dc8e78 commit bb5ddda

File tree

6 files changed

+306
-90
lines changed

6 files changed

+306
-90
lines changed

index.d.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,13 @@ declare module "react-css-themr"
2525
getWrappedInstance(): React.Component<P, S>;
2626
}
2727

28-
interface ThemedComponentClass<P, S> extends React.ComponentClass<P>
29-
{
30-
new(props?: P, context?: any): ThemedComponent<P, S>;
31-
}
28+
interface ThemedComponentClass<P, S> extends React.ComponentClass<P> {
29+
new(props?: P, context?: any): ThemedComponent<P, S>;
30+
}
3231

33-
export function themr(
34-
identifier: string,
35-
defaultTheme?: {},
36-
options?: IThemrOptions
37-
): <P, S>(component: new(props?: P, context?: any) => React.Component<P, S>) => ThemedComponentClass<P, S>;
32+
export function themr(
33+
identifier: string | number | symbol,
34+
defaultTheme?: {},
35+
options?: IThemrOptions
36+
): <P, S>(component: (new(props?: P, context?: any) => React.Component<P, S>) | React.SFC<P>) => ThemedComponentClass<P, S>;
3837
}

lib/components/themr.js

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -190,51 +190,119 @@ exports.default = function (componentName, localTheme) {
190190
};
191191

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

199198

200199
function themeable() {
201-
var original = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
202-
var mixin = arguments[1];
203-
204-
//don't merge if no mixin is passed
205-
if (!mixin) return original;
206-
207-
//merge themes by concatenating values with the same keys
208-
return Object.keys(mixin).reduce(
209-
210-
//merging reducer
211-
function (result, key) {
212-
var _extends3;
200+
for (var _len2 = arguments.length, themes = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
201+
themes[_key2] = arguments[_key2];
202+
}
213203

214-
var originalValue = original[key] || '';
215-
var mixinValue = mixin[key] || '';
204+
return themes.reduce(function (acc, theme) {
205+
return merge(acc, theme);
206+
}, {});
207+
}
216208

217-
var newValue = void 0;
209+
/**
210+
* @param {TReactCSSThemrTheme} [original] - Original theme
211+
* @param {TReactCSSThemrTheme} [mixin] - Mixin theme
212+
* @returns {TReactCSSThemrTheme} - resulting theme
213+
*/
214+
function merge() {
215+
var original = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
216+
var mixin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
217+
218+
//make a copy to avoid mutations of nested objects
219+
//also strip all functions injected by isomorphic-style-loader
220+
var result = Object.keys(original).reduce(function (acc, key) {
221+
var value = original[key];
222+
if (typeof value !== 'function') {
223+
acc[key] = value;
224+
}
225+
return acc;
226+
}, {});
227+
228+
//traverse mixin keys and merge them to resulting theme
229+
Object.keys(mixin).forEach(function (key) {
230+
//there's no need to set any defaults here
231+
var originalValue = result[key];
232+
var mixinValue = mixin[key];
233+
234+
switch (typeof mixinValue === 'undefined' ? 'undefined' : _typeof(mixinValue)) {
235+
case 'object':
236+
{
237+
//possibly nested theme object
238+
switch (typeof originalValue === 'undefined' ? 'undefined' : _typeof(originalValue)) {
239+
case 'object':
240+
{
241+
//exactly nested theme object - go recursive
242+
result[key] = merge(originalValue, mixinValue);
243+
break;
244+
}
245+
246+
case 'undefined':
247+
{
248+
//original does not contain this nested key - just take it as is
249+
result[key] = mixinValue;
250+
break;
251+
}
252+
253+
default:
254+
{
255+
//can't merge an object with a non-object
256+
throw new Error('You are merging object ' + key + ' with a non-object ' + originalValue);
257+
}
258+
}
259+
break;
260+
}
218261

219-
//when you are mixing an string with a object it should fail
220-
(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.');
262+
case 'undefined': //fallthrough - handles accidentally unset values which may come from props
263+
case 'function':
264+
{
265+
//this handles issue when isomorphic-style-loader addes helper functions to css-module
266+
break; //just skip
267+
}
221268

222-
//check if values are nested objects
223-
if ((typeof originalValue === 'undefined' ? 'undefined' : _typeof(originalValue)) === 'object' && (typeof mixinValue === 'undefined' ? 'undefined' : _typeof(mixinValue)) === 'object') {
224-
//go recursive
225-
newValue = themeable(originalValue, mixinValue);
226-
} else {
227-
//either concat or take mixin value
228-
newValue = originalValue.split(' ').concat(mixinValue.split(' ')).filter(function (item, pos, self) {
229-
return self.indexOf(item) === pos && item !== '';
230-
}).join(' ');
269+
default:
270+
{
271+
//plain values
272+
switch (typeof originalValue === 'undefined' ? 'undefined' : _typeof(originalValue)) {
273+
case 'object':
274+
{
275+
//can't merge a non-object with an object
276+
throw new Error('You are merging non-object ' + mixinValue + ' with an object ' + key);
277+
}
278+
279+
case 'undefined':
280+
{
281+
//mixin key is new to original theme - take it as is
282+
result[key] = mixinValue;
283+
break;
284+
}
285+
case 'function':
286+
{
287+
//this handles issue when isomorphic-style-loader addes helper functions to css-module
288+
break; //just skip
289+
}
290+
291+
default:
292+
{
293+
//finally we can merge
294+
result[key] = originalValue.split(' ').concat(mixinValue.split(' ')).filter(function (item, pos, self) {
295+
return self.indexOf(item) === pos && item !== '';
296+
}).join(' ');
297+
break;
298+
}
299+
}
300+
break;
301+
}
231302
}
303+
});
232304

233-
return _extends({}, result, (_extends3 = {}, _extends3[key] = newValue, _extends3));
234-
},
235-
236-
//use original theme as an acc
237-
original);
305+
return result;
238306
}
239307

240308
/**

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "react-css-themr-legacy",
33
"description": "React CSS Themr",
44
"homepage": "https://github.com/mitoyarzun/react-css-themr#readme",
5-
"version": "1.8.2",
5+
"version": "1.8.3",
66
"main": "./lib",
77
"author": {
88
"email": "[email protected]",
@@ -27,6 +27,7 @@
2727
"invariant": "^2.2.1"
2828
},
2929
"devDependencies": {
30+
"@types/react": "~15.0.4",
3031
"babel-cli": "^6.7.7",
3132
"babel-core": "^6.18.0",
3233
"babel-eslint": "^7.1.1",

src/components/themr.js

Lines changed: 87 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
8686
getNamespacedTheme(props) {
8787
const { themeNamespace, theme } = props
8888
if (!themeNamespace) return theme
89-
if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
89+
if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
9090
'themeNamespace prop should be used only with theme prop.')
9191

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

167167
/**
168-
* Merges two themes by concatenating values with the same keys
169-
* @param {TReactCSSThemrTheme} [original] - Original theme object
170-
* @param {TReactCSSThemrTheme} [mixin] - Mixing theme object
171-
* @returns {TReactCSSThemrTheme} - Merged resulting theme
168+
* Merges passed themes by concatenating string keys and processing nested themes
169+
* @param {...TReactCSSThemrTheme} themes - Themes
170+
* @returns {TReactCSSThemrTheme} - Resulting theme
172171
*/
173-
export function themeable(original = {}, mixin) {
174-
//don't merge if no mixin is passed
175-
if (!mixin) return original
176-
177-
//merge themes by concatenating values with the same keys
178-
return Object.keys(mixin).reduce(
179-
180-
//merging reducer
181-
(result, key) => {
182-
const originalValue = original[key] || ''
183-
const mixinValue = mixin[key] || ''
184-
185-
let newValue
172+
export function themeable(...themes) {
173+
return themes.reduce((acc, theme) => merge(acc, theme), {})
174+
}
186175

187-
//when you are mixing an string with a object it should fail
188-
invariant(!(typeof originalValue === 'string' && typeof mixinValue === 'object'),
189-
`You are merging a string "${originalValue}" with an Object,` +
190-
'Make sure you are passing the proper theme descriptors.'
191-
)
176+
/**
177+
* @param {TReactCSSThemrTheme} [original] - Original theme
178+
* @param {TReactCSSThemrTheme} [mixin] - Mixin theme
179+
* @returns {TReactCSSThemrTheme} - resulting theme
180+
*/
181+
function merge(original = {}, mixin = {}) {
182+
//make a copy to avoid mutations of nested objects
183+
//also strip all functions injected by isomorphic-style-loader
184+
const result = Object.keys(original).reduce((acc, key) => {
185+
const value = original[key]
186+
if (typeof value !== 'function') {
187+
acc[key] = value
188+
}
189+
return acc
190+
}, {})
191+
192+
//traverse mixin keys and merge them to resulting theme
193+
Object.keys(mixin).forEach(key => {
194+
//there's no need to set any defaults here
195+
const originalValue = result[key]
196+
const mixinValue = mixin[key]
197+
198+
switch (typeof mixinValue) {
199+
case 'object': {
200+
//possibly nested theme object
201+
switch (typeof originalValue) {
202+
case 'object': {
203+
//exactly nested theme object - go recursive
204+
result[key] = merge(originalValue, mixinValue)
205+
break
206+
}
207+
208+
case 'undefined': {
209+
//original does not contain this nested key - just take it as is
210+
result[key] = mixinValue
211+
break
212+
}
213+
214+
default: {
215+
//can't merge an object with a non-object
216+
throw new Error(`You are merging object ${key} with a non-object ${originalValue}`)
217+
}
218+
}
219+
break
220+
}
192221

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

205-
return {
206-
...result,
207-
[key]: newValue
228+
default: {
229+
//plain values
230+
switch (typeof originalValue) {
231+
case 'object': {
232+
//can't merge a non-object with an object
233+
throw new Error(`You are merging non-object ${mixinValue} with an object ${key}`)
234+
}
235+
236+
case 'undefined': {
237+
//mixin key is new to original theme - take it as is
238+
result[key] = mixinValue
239+
break
240+
}
241+
case 'function': {
242+
//this handles issue when isomorphic-style-loader addes helper functions to css-module
243+
break //just skip
244+
}
245+
246+
default: {
247+
//finally we can merge
248+
result[key] = originalValue.split(' ')
249+
.concat(mixinValue.split(' '))
250+
.filter((item, pos, self) => self.indexOf(item) === pos && item !== '')
251+
.join(' ')
252+
break
253+
}
254+
}
255+
break
208256
}
209-
},
257+
}
258+
})
210259

211-
//use original theme as an acc
212-
original
213-
)
260+
return result
214261
}
215262

216263
/**

0 commit comments

Comments
 (0)