diff --git a/README.md b/README.md
index afb5a09..232b5af 100644
--- a/README.md
+++ b/README.md
@@ -158,12 +158,13 @@ Makes available a `theme` context to use in styled components. The shape of the
Returns a `function` to wrap a component and make it themeable.
-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:
+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:
- `Identifier` *(String)* used to provide a unique identifier to the component that will be used to get a theme from context.
- `[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.
- `[options]` (*Object*) If specified it allows to customize the behavior:
- [`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.
+ - [`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.
## About
diff --git a/src/components/themr.js b/src/components/themr.js
index 3c7c309..3bde057 100644
--- a/src/components/themr.js
+++ b/src/components/themr.js
@@ -17,7 +17,8 @@ const COMPOSE_SOFTLY = 'softly'
const DONT_COMPOSE = false
const DEFAULT_OPTIONS = {
- composeTheme: COMPOSE_DEEPLY
+ composeTheme: COMPOSE_DEEPLY,
+ mapThemrProps: defaultMapThemrProps
}
const THEMR_CONFIG = typeof Symbol !== 'undefined' ?
@@ -32,7 +33,10 @@ const THEMR_CONFIG = typeof Symbol !== 'undefined' ?
* @returns {function(ThemedComponent:Function):Function} - ThemedComponent
*/
export default (componentName, localTheme, options = {}) => (ThemedComponent) => {
- const { composeTheme: optionComposeTheme } = { ...DEFAULT_OPTIONS, ...options }
+ const {
+ composeTheme: optionComposeTheme,
+ mapThemrProps: optionMapThemrProps
+ } = { ...DEFAULT_OPTIONS, ...options }
validateComposeOption(optionComposeTheme)
let config = ThemedComponent[THEMR_CONFIG]
@@ -61,12 +65,14 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
composeTheme: PropTypes.oneOf([ COMPOSE_DEEPLY, COMPOSE_SOFTLY, DONT_COMPOSE ]),
innerRef: PropTypes.func,
theme: PropTypes.object,
- themeNamespace: PropTypes.string
+ themeNamespace: PropTypes.string,
+ mapThemrProps: PropTypes.func
}
static defaultProps = {
...ThemedComponent.defaultProps,
- composeTheme: optionComposeTheme
+ composeTheme: optionComposeTheme,
+ mapThemrProps: optionMapThemrProps
}
constructor(...args) {
@@ -106,14 +112,6 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
: {}
}
- getPropsForComponent() {
- //exclude themr-only props
- //noinspection JSUnusedLocalSymbols
- const { composeTheme, innerRef, themeNamespace, ...props } = this.props //eslint-disable-line no-unused-vars
-
- return props
- }
-
getTheme(props) {
return props.composeTheme === COMPOSE_SOFTLY
? {
@@ -145,14 +143,10 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
}
render() {
- const { innerRef } = this.props
- const props = this.getPropsForComponent()
-
- return React.createElement(ThemedComponent, {
- ...props,
- ref: innerRef,
- theme: this.theme_
- })
+ return React.createElement(
+ ThemedComponent,
+ this.props.mapThemrProps(this.props, this.theme_)
+ )
}
}
@@ -283,3 +277,18 @@ function removeNamespace(key, themeNamespace) {
const capitalized = key.substr(themeNamespace.length)
return capitalized.slice(0, 1).toLowerCase() + capitalized.slice(1)
}
+
+function defaultMapThemrProps(ownProps, theme) {
+ const {
+ composeTheme, //eslint-disable-line no-unused-vars
+ innerRef,
+ themeNamespace, //eslint-disable-line no-unused-vars
+ ...rest
+ } = ownProps
+
+ return {
+ ...rest,
+ ref: innerRef,
+ theme
+ }
+}
diff --git a/test/components/themr.spec.js b/test/components/themr.spec.js
index 049b607..430bf01 100644
--- a/test/components/themr.spec.js
+++ b/test/components/themr.spec.js
@@ -293,6 +293,50 @@ describe('Themr decorator function', () => {
expect(spy.withArgs(stub).calledOnce).toBe(true)
})
+ it('allows to customize props passing using mapThemrProps from props', () => {
+ class Container extends Component {
+ render() {
+ return
+ }
+ }
+
+ const spy = sinon.stub()
+ const hoc = C => ({ withRef, ...rest }) => ()
+ const customMapper = (props, theme) => {
+ const { composeTheme, innerRef, mapThemrProps, themeNamespace, ...rest } = props //eslint-disable-line no-unused-vars
+ return { withRef: innerRef, theme, className: 'fooClass', ...rest }
+ }
+ const theme = {}
+ const DecoratedContainer = hoc(Container)
+ const ThemedDecoratedContainer = themr('Container', theme)(DecoratedContainer)
+ const tree = TestUtils.renderIntoDocument()
+ const stub = TestUtils.findRenderedComponentWithType(tree, Container)
+ expect(spy.withArgs(stub).calledOnce).toBe(true)
+ expect(stub.props).toMatch({ theme, className: 'fooClass' })
+ })
+
+ it('allows to customize props passing using mapThemrProps from options', () => {
+ class Container extends Component {
+ render() {
+ return
+ }
+ }
+
+ const spy = sinon.stub()
+ const hoc = C => ({ withRef, ...rest }) => ()
+ const customMapper = (props, theme) => {
+ const { composeTheme, innerRef, mapThemrProps, themeNamespace, ...rest } = props //eslint-disable-line no-unused-vars
+ return { withRef: innerRef, theme, className: 'fooClass', ...rest }
+ }
+ const theme = {}
+ const DecoratedContainer = hoc(Container)
+ const ThemedDecoratedContainer = themr('Container', {}, { mapThemrProps: customMapper })(DecoratedContainer)
+ const tree = TestUtils.renderIntoDocument()
+ const stub = TestUtils.findRenderedComponentWithType(tree, Container)
+ expect(spy.withArgs(stub).calledOnce).toBe(true)
+ expect(stub.props).toMatch({ theme, className: 'fooClass' })
+ })
+
it('should throw if themeNamespace passed without theme', () => {
const theme = { Container: { foo: 'foo_1234' } }