From 00128b093dea1c10b2f81a4b0106e92b2c13c7c2 Mon Sep 17 00:00:00 2001 From: Oleg Orlov Date: Wed, 18 May 2016 18:20:56 +0300 Subject: [PATCH] add CSSModules decorator --- package.json | 3 ++ src/index.js | 2 ++ src/lib/CSSModules.js | 22 +++++++++++++ src/lib/css-modules/extendReactClass.js | 18 +++++++++++ src/lib/css-modules/linkElement.js | 33 ++++++++++++++++++++ src/lib/css-modules/wrapStatelessFunction.js | 14 +++++++++ test/index.js | 1 + 7 files changed, 93 insertions(+) create mode 100644 src/lib/CSSModules.js create mode 100644 src/lib/css-modules/extendReactClass.js create mode 100644 src/lib/css-modules/linkElement.js create mode 100644 src/lib/css-modules/wrapStatelessFunction.js diff --git a/package.json b/package.json index 3fbe6ad..3f0bdcd 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,12 @@ "dependencies": { "@eagle/redux-immutablejs": "0.1.0", "babel-runtime": "6.6.1", + "classnames": "2.2.5", + "hoist-non-react-statics": "1.0.6", "immutable": "3.8.1", "lodash.filter": "4.4.0", "lodash.foreach": "4.3.0", + "lodash.isfunction": "3.0.8", "lodash.map": "4.4.0", "lodash.mapvalues": "4.4.0", "lodash.reduce": "4.4.0", diff --git a/src/index.js b/src/index.js index 94ec857..ff1b605 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,7 @@ import createActions from './lib/createActions' import createReducer from './lib/createReducer' import createReducerActions from './lib/createReducerActions' import combineReducers from './lib/combineReducers' +import CSSModules from './lib/CSSModules' class TangoComponent extends ReactComponent { static defaultState = {}; @@ -29,6 +30,7 @@ const PropTypes = { export { TangoComponent as Component, connect, + CSSModules, PropTypes, Provider, combineReducers, diff --git a/src/lib/CSSModules.js b/src/lib/CSSModules.js new file mode 100644 index 0000000..0d0c334 --- /dev/null +++ b/src/lib/CSSModules.js @@ -0,0 +1,22 @@ + +import isFunction from 'lodash.isfunction' + +import extendReactClass from './css-modules/extendReactClass' +import wrapStatelessFunction from './css-modules/wrapStatelessFunction' + +const isReactComponent = (maybeReactComponent) => + 'prototype' in maybeReactComponent && isFunction(maybeReactComponent.prototype.render) + +export default (styles, options) => Component => { + let decoratedClass + + if (isReactComponent(Component)) { + decoratedClass = extendReactClass(Component, styles, options) + } else { + decoratedClass = wrapStatelessFunction(Component, styles, options) + } + + decoratedClass.displayName = Component.displayName || Component.name + + return decoratedClass +} diff --git a/src/lib/css-modules/extendReactClass.js b/src/lib/css-modules/extendReactClass.js new file mode 100644 index 0000000..bf1cc51 --- /dev/null +++ b/src/lib/css-modules/extendReactClass.js @@ -0,0 +1,18 @@ +import hoistNonReactStatics from 'hoist-non-react-statics' + +import linkElement from './linkElement' + +export default (Component, styles, options) => { + class WrappedComponent extends Component { + render() { + const renderResult = super.render() + + if (renderResult) { + return linkElement(renderResult, styles, options) + } + return renderResult + } + } + + return hoistNonReactStatics(WrappedComponent, Component) +} diff --git a/src/lib/css-modules/linkElement.js b/src/lib/css-modules/linkElement.js new file mode 100644 index 0000000..394df4c --- /dev/null +++ b/src/lib/css-modules/linkElement.js @@ -0,0 +1,33 @@ +import { Children, isValidElement, cloneElement } from 'react' +import classNames from 'classnames/bind' + +const linkElement = (element, styles, options) => { + const cx = classNames.bind(styles) + let className + let children + + if (isValidElement(element.props.children)) { + children = linkElement(element.props.children, styles, options) + } else { + children = Children.map(element.props.children, (node) => { + if (isValidElement(node)) { + return linkElement(node, styles, options) + } + return node + }) + } + + if (element.props.styleName) { + className = cx(element.props.styleName) + + if (element.props.className) { + className = `${element.props.className} ${className}` + } + + return cloneElement(element, { className }, children) + } + + return element +} + +export default linkElement diff --git a/src/lib/css-modules/wrapStatelessFunction.js b/src/lib/css-modules/wrapStatelessFunction.js new file mode 100644 index 0000000..e57a8f2 --- /dev/null +++ b/src/lib/css-modules/wrapStatelessFunction.js @@ -0,0 +1,14 @@ +import linkElement from './linkElement' + +export default (Component, styles, options) => { + const WrappedComponent = (props = {}, ...args) => { + const renderResult = Component(props, ...args) + + if (renderResult) { + return linkElement(renderResult, styles, options) + } + return renderResult + } + + return WrappedComponent +} diff --git a/test/index.js b/test/index.js index 4c54af0..47047b7 100644 --- a/test/index.js +++ b/test/index.js @@ -5,6 +5,7 @@ import * as tango from '../src' test('should export the right stuff', t => { t.truthy(tango.Component, 'Component') t.truthy(tango.connect, 'connect') + t.truthy(tango.CSSModules, 'CSSModules') t.truthy(tango.PropTypes, 'PropTypes') t.truthy(tango.Provider, 'Provider') t.truthy(tango.combineReducers, 'combineReducers')