react-decorators
Advanced tools
Comparing version 1.0.2 to 1.0.4
@@ -1,69 +0,19 @@ | ||
var classNames = require('classnames/bind'); | ||
var utils = require('./utils'); | ||
var React = require('react'); | ||
var propertiesTransformer = require('./lib/properties-transformer'); | ||
var _classNames = require('classnames/bind'); | ||
var classNames = require('./class-names'); | ||
/** | ||
* | ||
* @param styles | ||
* @returns {function(*=): *} | ||
*/ | ||
module.exports = function cssModules(styles) { | ||
return function(Component) { | ||
if (utils.isStatelessComponent(Component)) { | ||
return function(props) { | ||
return transformElement(Component(props), classNames.bind(styles)); | ||
}; | ||
} | ||
function StyledComponent() { | ||
Component.prototype.constructor.apply(this, arguments); | ||
} | ||
utils.classInheritance(StyledComponent, Component); | ||
StyledComponent.prototype.render = function() { | ||
return transformElement( | ||
Component.prototype.render.apply(this, arguments), | ||
classNames.bind(styles) | ||
); | ||
}; | ||
return StyledComponent; | ||
}; | ||
}; | ||
function transformElement(el, cx) { | ||
if (el) { | ||
if (el.props) { | ||
var props = Object.assign({}, el.props); | ||
Object.keys(props).forEach(function(propName) { | ||
if (React.isValidElement(props[propName])) { | ||
props[propName] = transformElement(props[propName], cx); | ||
} | ||
}); | ||
var cx = _classNames.bind(styles); | ||
return propertiesTransformer(Component, function(props) { | ||
if (props.className) { | ||
props.className = cx(splitStrings(props.className)); | ||
props.className = cx(classNames.splitStrings(props.className)); | ||
} | ||
el = React.cloneElement(el, props, recursiveTransform(props.children, cx)); | ||
} | ||
else { | ||
// no props currently means that this is in fact a portal, not an element | ||
// therefore we only transform the children, we cannot clone it | ||
el.children = recursiveTransform(el.children, cx); | ||
} | ||
} | ||
return el; | ||
} | ||
function recursiveTransform(el, cx) { | ||
if (React.isValidElement(el)) { | ||
return transformElement(el, cx); | ||
} | ||
if (Array.isArray(el)) { | ||
return React.Children.map(el, function(child) { | ||
return recursiveTransform(child, cx); | ||
}); | ||
} | ||
return el; | ||
} | ||
function splitStrings(className) { | ||
if (Array.isArray(className)) { | ||
return className.map(splitStrings); | ||
} | ||
if (typeof className === 'string') { | ||
return className.split(/\s+/g); | ||
} | ||
return className; | ||
} | ||
}; | ||
}; |
var lib = module.exports; | ||
lib.consumeContext = require('./consume-context'); | ||
lib.cssModules = require('./css-modules'); | ||
lib.injectContext = require('./inject-context'); | ||
lib.classNames = require('./class-names'); | ||
lib.cssModules = require('./css-modules'); |
{ | ||
"name": "react-decorators", | ||
"version": "1.0.2", | ||
"version": "1.0.4", | ||
"description": "A collection of react decorators to enhance components capabilities", | ||
@@ -8,3 +8,4 @@ "main": "./index.js", | ||
"preversion": "npm test", | ||
"test": "npx mocha --require babel-register tests/" | ||
"prepublish": "npm test", | ||
"test": "npx mocha --require ./tests/bootstrap tests/" | ||
}, | ||
@@ -24,3 +25,3 @@ "repository": { | ||
"author": "Ido Moshe", | ||
"license": "ISC", | ||
"license": "MIT", | ||
"bugs": { | ||
@@ -38,2 +39,3 @@ "url": "https://github.com/iMoses/react-decorators/issues" | ||
"babel-cli": "^6.26.0", | ||
"babel-plugin-rewire": "^1.1.0", | ||
"babel-plugin-transform-decorators-legacy": "^1.3.4", | ||
@@ -47,4 +49,5 @@ "babel-preset-env": "^1.6.1", | ||
"enzyme-adapter-react-16": "^1.1.1", | ||
"jsdom": "^11.9.0", | ||
"mocha": "^5.1.0" | ||
} | ||
} |
115
README.md
@@ -1,1 +0,114 @@ | ||
# React-decorators | ||
# React-decorators | ||
_A collection of react decorators to enhance components capabilities._ | ||
Feel free to open a PR with your own decorators. For large | ||
new features, please open an issue first. | ||
## Installation | ||
The package is currently available only on [npm](https://www.npmjs.com/). | ||
```shell | ||
npm install --save react-decorators | ||
``` | ||
[](https://www.npmjs.org/package/react-decorators) | ||
## Usage | ||
## Decorators | ||
- [classNames](#classnames) | ||
- [cssModules](#cssmodules) | ||
- [injectContext](#injectcontext) | ||
### classNames | ||
**What is does** | ||
Injects the [classnames](https://github.com/JedWatson/classnames) package directly into React's `className` property. | ||
> A simple JavaScript utility for conditionally joining classNames together. | ||
> <br/>...<br/> | ||
> The `classNames` function takes any number of arguments which can be a string or object. | ||
> <br/>...<br/> | ||
> If the value associated with a given key is falsy, that key won't be included in the output. | ||
```javascript | ||
@classNames | ||
class MyComponent extends React.Component { | ||
render() { | ||
return ( | ||
<div className="classnames-examples"> | ||
<span className={['foo', 'bar']} /> {/* class="foo bar" */} | ||
<span className={{selected: false, visible: true}} /> {/* class="visible" */} | ||
<span className={[null, {active: true}, false, [{nested: true}]]} /> {/* class="active nested" */} | ||
<span className={{hasClass: false}} /> {/* class="" */} | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
### cssModules | ||
**What is does** | ||
An extension of the [`classNames`](#classnames) decorator, it binds the | ||
[classnames](https://github.com/JedWatson/classnames) package to React's `className` property using the | ||
[alternate `bind` version](https://github.com/JedWatson/classnames#alternate-bind-version-for-css-modules) | ||
for [css-modules](https://github.com/css-modules/css-modules). | ||
``` | ||
import styles from './styles.css'; | ||
@cssModules(styles) | ||
class MyComponent extends React.Component { | ||
render() { | ||
return ( | ||
<div className="my-class"> | ||
// Content goes here | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
Although it mixing between ES2015+ `imports` and CommonJS `require`, | ||
I find this syntax to be very readable. | ||
```js | ||
@cssModules(require('./my-component.scss')) | ||
class MyComponent extends React.Component { ... } | ||
``` | ||
### injectContext | ||
**What is does** | ||
This decorator receives a map of property names to context consumers, | ||
and injects these the consumers values as properties to the base component. | ||
`injectContext({propName: Consumer[, ...]})` | ||
```js | ||
@injectContext({ | ||
theme: ThemeConsumer, | ||
}) | ||
class MyComponent extends React.Component { | ||
render() { | ||
return ( | ||
<div className={this.props.theme.container}> | ||
// Content goes here | ||
</div> | ||
); | ||
} | ||
} | ||
``` |
@@ -0,9 +1,8 @@ | ||
import cssModules from '../css-modules'; | ||
import { shallow, mount } from 'enzyme'; | ||
import ReactDOM from 'react-dom'; | ||
import { expect } from 'chai'; | ||
import { JSDOM } from 'jsdom'; | ||
import React from 'react'; | ||
import { expect } from 'chai'; | ||
import Enzyme, { shallow } from 'enzyme'; | ||
import Adapter from 'enzyme-adapter-react-16'; | ||
import cssModules from '../css-modules'; | ||
Enzyme.configure({adapter: new Adapter}); | ||
describe('@cssModules', () => { | ||
@@ -18,81 +17,57 @@ | ||
describe('React.Component', () => { | ||
const PropsComponent = props => ( | ||
<div className="spanClass"> | ||
{props.child} | ||
</div> | ||
); | ||
@cssModules(styles) | ||
class TestComponent extends React.Component { | ||
@cssModules(styles) | ||
class TestComponent extends React.Component { | ||
render() { | ||
const { divClass, spanClass, codeClass } = this.props; | ||
const PropsComponent = props => props.child; | ||
return ( | ||
<div className={divClass}> | ||
<span className={spanClass} /> | ||
<PropsComponent child={<code className={codeClass} />} /> | ||
</div> | ||
); | ||
} | ||
render() { | ||
const { divClass, spanClass, codeClass, children } = this.props; | ||
// const PropsComponent = props => props.child; | ||
return ( | ||
<div className={divClass}> | ||
<span className={spanClass} /> | ||
<PropsComponent child={<code className={codeClass} />} /> | ||
<code>{children}</code> | ||
</div> | ||
); | ||
} | ||
const el = shallow( | ||
<TestComponent | ||
divClass="div-class" | ||
codeClass="code-class" | ||
spanClass={[{spanClass: true, ignoreClass: false}, 'extra-class']} | ||
/> | ||
); | ||
} | ||
it('transform a string className', () => { | ||
expect(el.find('div').props().className).to.equal('div-class-name'); | ||
}); | ||
const el = shallow( | ||
<TestComponent | ||
divClass="div-class" | ||
codeClass="code-class" | ||
spanClass={[{spanClass: true, ignoreClass: false}, 'extra-class']}> | ||
Child | ||
</TestComponent> | ||
); | ||
it('transform a nested object/array className', () => { | ||
expect(el.find('span').props().className).to.include('span-class-name'); | ||
expect(el.find('span').props().className).to.include('extra-class'); | ||
expect(el.find('span').props().className).to.not.include('spanClass'); | ||
expect(el.find('span').props().className).to.not.include('ignoreClass'); | ||
expect(el.find('span').props().className).to.not.include('should-not-appear'); | ||
}); | ||
it('validate `children` is not being lost', () => { | ||
expect(el.find('code').text()).to.equal('Child'); | ||
}); | ||
it('transform a property element className', () => { | ||
expect(el | ||
.find('PropsComponent').shallow() | ||
.find('code').props().className | ||
).to.include('code-class-name'); | ||
}); | ||
it('transforms a string className', () => { | ||
expect(el.find('div').props().className).to.equal('div-class-name'); | ||
}); | ||
it('transform className inside a portal', () => {}); | ||
it('transform className inside fragments', () => {}); | ||
it('transforms a nested object/array className', () => { | ||
expect(el.find('span').props().className).to.include('span-class-name'); | ||
expect(el.find('span').props().className).to.include('extra-class'); | ||
expect(el.find('span').props().className).to.not.include('spanClass'); | ||
expect(el.find('span').props().className).to.not.include('ignoreClass'); | ||
expect(el.find('span').props().className).to.not.include('should-not-appear'); | ||
}); | ||
describe('Stateless Component', () => { | ||
const TestComponent = cssModules(styles)(props => | ||
<div className={props.divClass}> | ||
<span className={props.spanClass} /> | ||
</div> | ||
); | ||
const el = shallow( | ||
<TestComponent | ||
divClass="div-class" | ||
spanClass={[{spanClass: true, ignoreClass: false}, 'extra-class']} /> | ||
); | ||
it('transform a string className', () => { | ||
expect(el.find('div').props().className).to.equal('div-class-name'); | ||
}); | ||
it('transform a nested object/array className', () => { | ||
expect(el.find('span').props().className).to.include('span-class-name'); | ||
expect(el.find('span').props().className).to.include('extra-class'); | ||
expect(el.find('span').props().className).to.not.include('spanClass'); | ||
expect(el.find('span').props().className).to.not.include('ignoreClass'); | ||
expect(el.find('span').props().className).to.not.include('should-not-appear'); | ||
}); | ||
it('transforms a property element className', () => { | ||
expect(el | ||
.find('PropsComponent').shallow() | ||
.find('code').props().className | ||
).to.include('code-class-name'); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
22076
14
0
459
115
12
1