Skip to content

Commit 133152a

Browse files
javivelascodavid-slayte
authored andcommitted
Add mapThemrProps option
1 parent 07eedd7 commit 133152a

File tree

3 files changed

+75
-21
lines changed

3 files changed

+75
-21
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,13 @@ Makes available a `theme` context to use in styled components. The shape of the
158158
159159
Returns a `function` to wrap a component and make it themeable.
160160

161-
The returned component accepts a `theme`, `composeTheme` and `innerRef` props apart from the props of the original component. They former two are used to provide a `theme` to the component and to configure the style composition, which can be configured via options too, while the latter is used to pass a ref callback to the decorated component. The function arguments are:
161+
The returned component accepts a `theme`, `composeTheme`, `innerRef` and `mapThemrProps` props apart from the props of the original component. They former two are used to provide a `theme` to the component and to configure the style composition, which can be configured via options too. `innerRef` is used to pass a ref callback to the decorated component and `mapThemrProps` is a function that can be used to map properties to the decorated component. The function arguments are:
162162

163163
- `Identifier` *(String)* used to provide a unique identifier to the component that will be used to get a theme from context.
164164
- `[defaultTheme]` (*Object*) is classname object resolved from CSS modules. It will be used as the default theme to calculate a new theme that will be passed to the component.
165165
- `[options]` (*Object*) If specified it allows to customize the behavior:
166166
- [`composeTheme = 'deeply'`] *(String)* allows to customize the way themes are merged or to disable merging completely. The accepted values are `deeply` to deeply merge themes, `softly` to softly merge themes and `false` to disable theme merging.
167+
- [`mapThemrProps = (props, theme) => ({ ref, theme })`] *(Function)* allows to customize how properties are passed down to the decorated component. By default, themr extracts all own properties passing down just `innerRef` as `ref` and the generated theme as `theme`. If you are decorating a component that needs to map the reference or any other custom property, this function is called with *all* properties given to the component plus the generated `theme` in the second parameter. It should return the properties you want to pass.
167168
168169
## About
169170

src/components/themr.js

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const COMPOSE_SOFTLY = 'softly'
1717
const DONT_COMPOSE = false
1818

1919
const DEFAULT_OPTIONS = {
20-
composeTheme: COMPOSE_DEEPLY
20+
composeTheme: COMPOSE_DEEPLY,
21+
mapThemrProps: defaultMapThemrProps
2122
}
2223

2324
const THEMR_CONFIG = typeof Symbol !== 'undefined' ?
@@ -32,7 +33,10 @@ const THEMR_CONFIG = typeof Symbol !== 'undefined' ?
3233
* @returns {function(ThemedComponent:Function):Function} - ThemedComponent
3334
*/
3435
export default (componentName, localTheme, options = {}) => (ThemedComponent) => {
35-
const { composeTheme: optionComposeTheme } = { ...DEFAULT_OPTIONS, ...options }
36+
const {
37+
composeTheme: optionComposeTheme,
38+
mapThemrProps: optionMapThemrProps
39+
} = { ...DEFAULT_OPTIONS, ...options }
3640
validateComposeOption(optionComposeTheme)
3741

3842
let config = ThemedComponent[THEMR_CONFIG]
@@ -61,12 +65,14 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
6165
composeTheme: PropTypes.oneOf([ COMPOSE_DEEPLY, COMPOSE_SOFTLY, DONT_COMPOSE ]),
6266
innerRef: PropTypes.func,
6367
theme: PropTypes.object,
64-
themeNamespace: PropTypes.string
68+
themeNamespace: PropTypes.string,
69+
mapThemrProps: PropTypes.func
6570
}
6671

6772
static defaultProps = {
6873
...ThemedComponent.defaultProps,
69-
composeTheme: optionComposeTheme
74+
composeTheme: optionComposeTheme,
75+
mapThemrProps: optionMapThemrProps
7076
}
7177

7278
constructor(...args) {
@@ -106,14 +112,6 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
106112
: {}
107113
}
108114

109-
getPropsForComponent() {
110-
//exclude themr-only props
111-
//noinspection JSUnusedLocalSymbols
112-
const { composeTheme, innerRef, themeNamespace, ...props } = this.props //eslint-disable-line no-unused-vars
113-
114-
return props
115-
}
116-
117115
getTheme(props) {
118116
return props.composeTheme === COMPOSE_SOFTLY
119117
? {
@@ -145,14 +143,10 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
145143
}
146144

147145
render() {
148-
const { innerRef } = this.props
149-
const props = this.getPropsForComponent()
150-
151-
return React.createElement(ThemedComponent, {
152-
...props,
153-
ref: innerRef,
154-
theme: this.theme_
155-
})
146+
return React.createElement(
147+
ThemedComponent,
148+
this.props.mapThemrProps(this.props, this.theme_)
149+
)
156150
}
157151
}
158152

@@ -283,3 +277,18 @@ function removeNamespace(key, themeNamespace) {
283277
const capitalized = key.substr(themeNamespace.length)
284278
return capitalized.slice(0, 1).toLowerCase() + capitalized.slice(1)
285279
}
280+
281+
function defaultMapThemrProps(ownProps, theme) {
282+
const {
283+
composeTheme, //eslint-disable-line no-unused-vars
284+
innerRef,
285+
themeNamespace, //eslint-disable-line no-unused-vars
286+
...rest
287+
} = ownProps
288+
289+
return {
290+
...rest,
291+
ref: innerRef,
292+
theme
293+
}
294+
}

test/components/themr.spec.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,50 @@ describe('Themr decorator function', () => {
293293
expect(spy.withArgs(stub).calledOnce).toBe(true)
294294
})
295295

296+
it('allows to customize props passing using mapThemrProps from props', () => {
297+
class Container extends Component {
298+
render() {
299+
return <Passthrough {...this.props} />
300+
}
301+
}
302+
303+
const spy = sinon.stub()
304+
const hoc = C => ({ withRef, ...rest }) => (<C ref={withRef} {...rest} />)
305+
const customMapper = (props, theme) => {
306+
const { composeTheme, innerRef, mapThemrProps, themeNamespace, ...rest } = props //eslint-disable-line no-unused-vars
307+
return { withRef: innerRef, theme, className: 'fooClass', ...rest }
308+
}
309+
const theme = {}
310+
const DecoratedContainer = hoc(Container)
311+
const ThemedDecoratedContainer = themr('Container', theme)(DecoratedContainer)
312+
const tree = TestUtils.renderIntoDocument(<ThemedDecoratedContainer innerRef={spy} mapThemrProps={customMapper} />)
313+
const stub = TestUtils.findRenderedComponentWithType(tree, Container)
314+
expect(spy.withArgs(stub).calledOnce).toBe(true)
315+
expect(stub.props).toMatch({ theme, className: 'fooClass' })
316+
})
317+
318+
it('allows to customize props passing using mapThemrProps from options', () => {
319+
class Container extends Component {
320+
render() {
321+
return <Passthrough {...this.props} />
322+
}
323+
}
324+
325+
const spy = sinon.stub()
326+
const hoc = C => ({ withRef, ...rest }) => (<C ref={withRef} {...rest} />)
327+
const customMapper = (props, theme) => {
328+
const { composeTheme, innerRef, mapThemrProps, themeNamespace, ...rest } = props //eslint-disable-line no-unused-vars
329+
return { withRef: innerRef, theme, className: 'fooClass', ...rest }
330+
}
331+
const theme = {}
332+
const DecoratedContainer = hoc(Container)
333+
const ThemedDecoratedContainer = themr('Container', {}, { mapThemrProps: customMapper })(DecoratedContainer)
334+
const tree = TestUtils.renderIntoDocument(<ThemedDecoratedContainer innerRef={spy} />)
335+
const stub = TestUtils.findRenderedComponentWithType(tree, Container)
336+
expect(spy.withArgs(stub).calledOnce).toBe(true)
337+
expect(stub.props).toMatch({ theme, className: 'fooClass' })
338+
})
339+
296340
it('should throw if themeNamespace passed without theme', () => {
297341
const theme = { Container: { foo: 'foo_1234' } }
298342

0 commit comments

Comments
 (0)