11import React , { Component , PropTypes } from 'react'
22import invariant from 'invariant'
33
4+ /**
5+ * @typedef {Object.<string, TReactCSSThemrTheme> } TReactCSSThemrTheme
6+ */
7+
8+ /**
9+ * @typedef {{} } TReactCSSThemrOptions
10+ * @property {String|Boolean } [composeTheme=COMPOSE_DEEPLY]
11+ * @property {Boolean } [withRef=false]
12+ */
13+
414const COMPOSE_DEEPLY = 'deeply'
515const COMPOSE_SOFTLY = 'softly'
616const DONT_COMPOSE = false
@@ -14,6 +24,13 @@ const THEMR_CONFIG = typeof Symbol !== 'undefined' ?
1424 Symbol ( 'THEMR_CONFIG' ) :
1525 '__REACT_CSS_THEMR_CONFIG__'
1626
27+ /**
28+ * Themr decorator
29+ * @param {String|Number|Symbol } componentName - Component name
30+ * @param {TReactCSSThemrTheme } localTheme - Base theme
31+ * @param {{} } options - Themr options
32+ * @returns {function(ThemedComponent:Function):Function } - ThemedComponent
33+ */
1734export default ( componentName , localTheme , options = { } ) => ( ThemedComponent ) => {
1835 const { composeTheme : optionComposeTheme , withRef : optionWithRef } = { ...DEFAULT_OPTIONS , ...options }
1936 validateComposeOption ( optionComposeTheme )
@@ -116,13 +133,52 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
116133 return Themed
117134}
118135
119- export function themeable ( style = { } , theme ) {
120- if ( ! theme ) return style
121- return Object . keys ( theme ) . reduce ( ( result , key ) => ( {
122- ...result , [ key ] : style [ key ] ? `${ style [ key ] } ${ theme [ key ] } ` : theme [ key ]
123- } ) , style )
136+ /**
137+ * Merges two themes by concatenating values with the same keys
138+ * @param {TReactCSSThemrTheme } original - Original theme object
139+ * @param {TReactCSSThemrTheme } mixin - Mixing theme object
140+ * @returns {TReactCSSThemrTheme } - Merged resulting theme
141+ */
142+ export function themeable ( original = { } , mixin ) {
143+ //don't merge if no mixin is passed
144+ if ( ! mixin ) return original
145+
146+ //merge themes by concatenating values with the same keys
147+ return Object . keys ( mixin ) . reduce (
148+
149+ //merging reducer
150+ ( result , key ) => {
151+ const originalValue = original [ key ]
152+ const mixinValue = mixin [ key ]
153+
154+ let newValue
155+
156+ //check if values are nested objects
157+ if ( typeof originalValue === 'object' && typeof mixinValue === 'object' ) {
158+ //go recursive
159+ newValue = themeable ( originalValue , mixinValue )
160+ } else {
161+ //either concat or take mixin value
162+ newValue = originalValue ? `${ originalValue } ${ mixinValue } ` : mixinValue
163+ }
164+
165+ return {
166+ ...result ,
167+ [ key ] : newValue
168+ }
169+ } ,
170+
171+ //use original theme as an acc
172+ original
173+ )
124174}
125175
176+ /**
177+ * Validates compose option
178+ * @param {String|Boolean } composeTheme - Compose them option
179+ * @throws
180+ * @returns {undefined }
181+ */
126182function validateComposeOption ( composeTheme ) {
127183 if ( [ COMPOSE_DEEPLY , COMPOSE_SOFTLY , DONT_COMPOSE ] . indexOf ( composeTheme ) === - 1 ) {
128184 throw new Error (
@@ -133,6 +189,12 @@ function validateComposeOption(composeTheme) {
133189 }
134190}
135191
192+ /**
193+ * Removes namespace from key
194+ * @param {String } key - Key
195+ * @param {String } themeNamespace - Theme namespace
196+ * @returns {String } - Key
197+ */
136198function removeNamespace ( key , themeNamespace ) {
137199 const capitalized = key . substr ( themeNamespace . length )
138200 return capitalized . slice ( 0 , 1 ) . toLowerCase ( ) + capitalized . slice ( 1 )
0 commit comments