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 )
@@ -140,13 +157,52 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
140157 return Themed
141158}
142159
143- export function themeable ( style = { } , theme ) {
144- if ( ! theme ) return style
145- return Object . keys ( theme ) . reduce ( ( result , key ) => ( {
146- ...result , [ key ] : style [ key ] ? `${ style [ key ] } ${ theme [ key ] } ` : theme [ key ]
147- } ) , style )
160+ /**
161+ * Merges two themes by concatenating values with the same keys
162+ * @param {TReactCSSThemrTheme } original - Original theme object
163+ * @param {TReactCSSThemrTheme } mixin - Mixing theme object
164+ * @returns {TReactCSSThemrTheme } - Merged resulting theme
165+ */
166+ export function themeable ( original = { } , mixin ) {
167+ //don't merge if no mixin is passed
168+ if ( ! mixin ) return original
169+
170+ //merge themes by concatenating values with the same keys
171+ return Object . keys ( mixin ) . reduce (
172+
173+ //merging reducer
174+ ( result , key ) => {
175+ const originalValue = original [ key ]
176+ const mixinValue = mixin [ key ]
177+
178+ let newValue
179+
180+ //check if values are nested objects
181+ if ( typeof originalValue === 'object' && typeof mixinValue === 'object' ) {
182+ //go recursive
183+ newValue = themeable ( originalValue , mixinValue )
184+ } else {
185+ //either concat or take mixin value
186+ newValue = originalValue ? `${ originalValue } ${ mixinValue } ` : mixinValue
187+ }
188+
189+ return {
190+ ...result ,
191+ [ key ] : newValue
192+ }
193+ } ,
194+
195+ //use original theme as an acc
196+ original
197+ )
148198}
149199
200+ /**
201+ * Validates compose option
202+ * @param {String|Boolean } composeTheme - Compose them option
203+ * @throws
204+ * @returns {undefined }
205+ */
150206function validateComposeOption ( composeTheme ) {
151207 if ( [ COMPOSE_DEEPLY , COMPOSE_SOFTLY , DONT_COMPOSE ] . indexOf ( composeTheme ) === - 1 ) {
152208 throw new Error (
@@ -157,6 +213,12 @@ function validateComposeOption(composeTheme) {
157213 }
158214}
159215
216+ /**
217+ * Removes namespace from key
218+ * @param {String } key - Key
219+ * @param {String } themeNamespace - Theme namespace
220+ * @returns {String } - Key
221+ */
160222function removeNamespace ( key , themeNamespace ) {
161223 const capitalized = key . substr ( themeNamespace . length )
162224 return capitalized . slice ( 0 , 1 ) . toLowerCase ( ) + capitalized . slice ( 1 )
0 commit comments