Skip to content

Commit ab80dc2

Browse files
committed
Initial commit 💥
0 parents  commit ab80dc2

13 files changed

+420
-0
lines changed

Diff for: ‎.babelrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"presets": ["react", "es2015", "stage-0"],
3+
"plugins": [
4+
"transform-decorators-legacy"
5+
]
6+
}

Diff for: ‎.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
build
2+
lib
3+
node_modules
4+
npm-debug.log

Diff for: ‎.travis

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
language: node_js
2+
sudo: false
3+
cache:
4+
directories:
5+
- node_modules
6+
node_js:
7+
- "4"
8+
- "5"
9+
before_install:
10+
- npm install -g babel-cli

Diff for: ‎LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Javier Velasco
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Diff for: ‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# React CSS Themr

Diff for: ‎package.json

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "react-css-themr",
3+
"version": "0.0.1",
4+
"description": "React CSS Themr",
5+
"main": "lib/index.js",
6+
"files": [
7+
"lib",
8+
"src"
9+
],
10+
"scripts": {
11+
"build": "babel src --out-dir lib",
12+
"prepublish": "rimraf lib && npm run build",
13+
"test": "mocha --compilers js:babel-core/register --recursive --reporter spec --require ./test/setup.js",
14+
"test:watch": "npm test -- --watch"
15+
},
16+
"author": "Javi Velasco <[email protected]> (http://javivelasco.com/)",
17+
"license": "MIT",
18+
"devDependencies": {
19+
"babel": "^6.5.2",
20+
"babel-cli": "^6.7.7",
21+
"babel-core": "^6.7.7",
22+
"babel-eslint": "^6.0.3",
23+
"babel-plugin-add-module-exports": "^0.1.2",
24+
"babel-plugin-transform-decorators-legacy": "^1.3.4",
25+
"babel-preset-es2015": "^6.6.0",
26+
"babel-preset-react": "^6.5.0",
27+
"babel-preset-stage-0": "^6.5.0",
28+
"chai": "^3.5.0",
29+
"eslint": "^2.8.0",
30+
"expect": "^1.18.0",
31+
"jsdom": "^8.4.0",
32+
"mocha": "^2.4.5",
33+
"react": "^15.0.1",
34+
"react-addons-test-utils": "^15.0.1",
35+
"rimraf": "^2.5.2"
36+
},
37+
"peerDependencies": {
38+
"react": "^0.14.0 || ^15.0.0-0"
39+
}
40+
}

Diff for: ‎src/components/ThemeProvider.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Children, Component, PropTypes } from 'react';
2+
import themrShape from '../utils/themr-shape';
3+
4+
export default class ThemeProvider extends Component {
5+
static propTypes = {
6+
children: PropTypes.element.isRequired,
7+
theme: PropTypes.object.isRequired
8+
};
9+
10+
static defaultProps = {
11+
theme: {}
12+
};
13+
14+
static childContextTypes = {
15+
themr: themrShape.isRequired
16+
};
17+
18+
getChildContext () {
19+
return {
20+
themr: {
21+
theme: this.props.theme
22+
}
23+
};
24+
}
25+
26+
render () {
27+
return Children.only(this.props.children);
28+
}
29+
}

Diff for: ‎src/components/themr.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React, { Component, PropTypes } from 'react';
2+
3+
const DEFAULT_OPTIONS = {
4+
composeTheme: true
5+
};
6+
7+
export default (componentName, localTheme, options = DEFAULT_OPTIONS) => (ThemedComponent) => {
8+
const { composeTheme: optionComposeTheme } = options;
9+
return class Themed extends Component {
10+
static contextTypes = {
11+
themr: PropTypes.object
12+
};
13+
14+
static propTypes = {
15+
composeTheme: PropTypes.bool,
16+
theme: PropTypes.object
17+
};
18+
19+
static defaultProps = {
20+
composeTheme: optionComposeTheme
21+
};
22+
23+
getTheme() {
24+
if (!this.props.composeTheme && this.props.theme) return this.props.theme;
25+
if (!this.props.composeTheme && localTheme) return localTheme;
26+
const contextTheme = localTheme
27+
? themeable(this.context.themr.theme[componentName], localTheme)
28+
: this.context.themr.theme[componentName];
29+
return themeable(contextTheme, this.props.theme);
30+
}
31+
32+
render () {
33+
return React.createElement(ThemedComponent, {
34+
...this.props,
35+
theme: this.getTheme()
36+
});
37+
}
38+
}
39+
};
40+
41+
42+
function themeable(style = {}, theme) {
43+
if (!theme) return style;
44+
return [...Object.keys(theme), ...Object.keys(style)].reduce((result, key) => (
45+
theme[key] && style[key] && theme[key].indexOf(style[key]) === -1
46+
? { ...result, [key]: `${style[key]} ${theme[key]}` }
47+
: { ...result, [key]: theme[key] || style[key] }
48+
), {});
49+
}

Diff for: ‎src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as ThemeProvider } from './components/ThemeProvider';
2+
export { default as themr } from './components/themr';

Diff for: ‎src/utils/themr-shape.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { PropTypes } from 'react'
2+
3+
export default PropTypes.shape({
4+
theme: PropTypes.object.isRequired
5+
})

Diff for: ‎test/components/ThemeProvider.spec.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import expect from 'expect'
2+
import React, { PropTypes, Component } from 'react'
3+
import TestUtils from 'react-addons-test-utils'
4+
import { ThemeProvider } from '../../src/index'
5+
6+
describe('ThemeProvider', () => {
7+
class Child extends Component {
8+
render() {
9+
return <div />
10+
}
11+
}
12+
13+
Child.contextTypes = {
14+
themr: PropTypes.object.isRequired
15+
}
16+
17+
it('enforces a single child', () => {
18+
const theme = {}
19+
20+
// Ignore propTypes warnings
21+
const propTypes = ThemeProvider.propTypes
22+
ThemeProvider.propTypes = {}
23+
24+
try {
25+
expect(() => TestUtils.renderIntoDocument(
26+
<ThemeProvider theme={theme}>
27+
<div />
28+
</ThemeProvider>
29+
)).toNotThrow()
30+
31+
expect(() => TestUtils.renderIntoDocument(
32+
<ThemeProvider theme={theme}>
33+
</ThemeProvider>
34+
)).toThrow(/exactly one child/)
35+
36+
expect(() => TestUtils.renderIntoDocument(
37+
<ThemeProvider theme={theme}>
38+
<div />
39+
<div />
40+
</ThemeProvider>
41+
)).toThrow(/exactly one child/)
42+
} finally {
43+
ThemeProvider.propTypes = propTypes
44+
}
45+
})
46+
47+
it('should add the theme to the child context', () => {
48+
const theme = {}
49+
50+
TestUtils.renderIntoDocument(
51+
<ThemeProvider theme={theme}>
52+
<Child />
53+
</ThemeProvider>
54+
)
55+
56+
const spy = expect.spyOn(console, 'error')
57+
const tree = TestUtils.renderIntoDocument(
58+
<ThemeProvider theme={theme}>
59+
<Child />
60+
</ThemeProvider>
61+
)
62+
spy.destroy()
63+
expect(spy.calls.length).toBe(0)
64+
65+
const child = TestUtils.findRenderedComponentWithType(tree, Child)
66+
expect(child.context.themr.theme).toBe(theme)
67+
})
68+
})

0 commit comments

Comments
 (0)