Skip to content

Commit 048bacc

Browse files
committed
fix(init): Initial commit.
Initial implementation of the ComponentQueries library.
1 parent 4a155a7 commit 048bacc

File tree

13 files changed

+578
-2
lines changed

13 files changed

+578
-2
lines changed

.babelrc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"presets": ["es2015", "stage-1", "react"],
3+
"ignore": [
4+
"/node_modules/"
5+
],
6+
"env": {
7+
"test": {
8+
"plugins": ["rewire"]
9+
}
10+
}
11+
}

.eslintrc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"parser": "babel-eslint",
3+
"extends": "airbnb",
4+
"env": {
5+
"browser": true,
6+
"es6": true,
7+
"mocha": true,
8+
"node": true
9+
},
10+
"ecmaFeatures": {
11+
"defaultParams": true
12+
},
13+
"rules": {
14+
"comma-dangle": 0,
15+
"func-names": 0,
16+
"indent": [2, 2, { "SwitchCase": 1 }],
17+
"new-cap": 0,
18+
"no-lone-blocks": 0,
19+
// until we have glob based rules we have to disable this rule, as our
20+
// assert library uses expressions in this format.
21+
// follow - https://github.com/eslint/eslint/issues/3611
22+
"no-unused-expressions": 0,
23+
"no-confusing-arrow": 0,
24+
"prefer-arrow-callback": 0,
25+
"quotes": [2, "backtick", "avoid-escape"],
26+
"space-infix-ops": 0
27+
}
28+
}

.gitignore

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Webpack stats file output used for bundle optimisation.
2+
lib/stats.json
3+
4+
#####=== Node ===#####
5+
6+
# Logs
7+
logs
8+
*.log
9+
10+
# Runtime data
11+
pids
12+
*.pid
13+
*.seed
14+
15+
# Directory for instrumented libs generated by jscoverage/JSCover
16+
lib-cov
17+
18+
# Coverage directory used by tools like istanbul
19+
coverage
20+
21+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22+
.grunt
23+
24+
# node-waf configuration
25+
.lock-wscript
26+
27+
# Compiled binary addons (http://nodejs.org/api/addons.html)
28+
build/Release
29+
30+
# Dependency directory
31+
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
32+
node_modules
33+
34+
# Debug log from npm
35+
npm-debug.log

.travis.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
sudo: false
2+
language: node_js
3+
cache:
4+
directories:
5+
- node_modules
6+
branches:
7+
only:
8+
- master
9+
notifications:
10+
email: false
11+
node_js:
12+
- '4'
13+
before_install:
14+
- npm i -g npm@^3.0.0
15+
before_script:
16+
- npm prune
17+
script:
18+
- npm run test:coverage
19+
- npm run build
20+
after_success:
21+
- npm run report-coverage
22+
- npm run semantic-release

README.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,39 @@
1-
# react-component-queries
2-
Provide props to your React Components based on their Width and/or Height.
1+
<p align='center'>
2+
<h1 align='center'>COMPONENT QUERIES</h1>
3+
<p align='center'>Provide props to your React Components based on their Width and/or Height.</p>
4+
</p>
5+
6+
[![Travis](https://img.shields.io/travis/ctrlplusb/react-component-queries.svg?style=flat-square)](https://travis-ci.org/ctrlplusb/react-component-queries)
7+
[![npm](https://img.shields.io/npm/v/react-component-queries.svg?style=flat-square)](http://npm.im/react-component-queries)
8+
[![MIT License](https://img.shields.io/npm/l/react-component-queries.svg?style=flat-square)](http://opensource.org/licenses/MIT)
9+
[![Codecov](https://img.shields.io/codecov/c/github/ctrlplusb/react-component-queries.svg?style=flat-square)](https://codecov.io/github/ctrlplusb/react-component-queries)
10+
[![Maintenance](https://img.shields.io/maintenance/yes/2016.svg?style=flat-square)]()
11+
12+
* Responsive Components!
13+
* Easy to use.
14+
* Extensive browser support.
15+
* Supports any Component type, i.e. stateless/class.
16+
* Works with React 0.14.x and 15.x.x.
17+
18+
## Simple Example
19+
20+
Below is a super simple example highlighting the use of the library. Read the Usage section in its entirety for a full description on configuration and usage.
21+
22+
```javascript
23+
import ComponentQueries from 'react-component-queries';
24+
25+
class MyComponent extends Component {
26+
render() {
27+
return (
28+
<div>My scale prop value is {this.props.scale}</div>
29+
);
30+
}
31+
}
32+
33+
export default ComponentQueries(
34+
// Provide as many query functions as you need.
35+
(width) => width <= 330 ? { scale: 'mobile' } : {},
36+
(width) => width > 330 && width <=960 ? { scale: 'tablet' } : {},
37+
(width) => width > 960 && width <=1024 ? { scale: 'desktop' } : {},
38+
)(MyComponent);
39+
```

package.json

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"name": "react-component-queries",
3+
"description": "Provide props to your Components based on their Width and/or Height.",
4+
"main": "lib/react-component-queries.js",
5+
"scripts": {
6+
"prebuild": "rm -rf lib && mkdir lib",
7+
"build": "NODE_ENV=production webpack",
8+
"commit": "git-cz",
9+
"test": "NODE_ENV=test mocha --compilers js:babel-register --recursive --require ./test/setup.js \"test/**/*.test.js\"",
10+
"test:coverage": "NODE_ENV=test babel-node $(npm bin)/isparta cover $(npm bin)/_mocha -- -R spec --require ./test/setup.js",
11+
"report-coverage": "cat ./coverage/lcov.info | $(npm bin)/codecov",
12+
"lint": "eslint src",
13+
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
14+
"example": "NODE_ENV=development node ./example"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "https://github.com/ctrlplusb/react-component-queries.git"
19+
},
20+
"keywords": [
21+
"react",
22+
"responsive",
23+
"hoc",
24+
"element-queries",
25+
"media-queries"
26+
],
27+
"author": "Sean Matheson <[email protected]>",
28+
"license": "MIT",
29+
"bugs": {
30+
"url": "https://github.com/ctrlplusb/react-component-queries/issues"
31+
},
32+
"homepage": "https://github.com/ctrlplusb/react-component-queries#readme",
33+
"devDependencies": {
34+
"babel-cli": "6.7.5",
35+
"babel-core": "6.7.6",
36+
"babel-eslint": "6.0.2",
37+
"babel-loader": "6.2.4",
38+
"babel-plugin-rewire": "1.0.0-rc-2",
39+
"babel-preset-es2015": "6.6.0",
40+
"babel-preset-react": "6.5.0",
41+
"babel-preset-stage-1": "6.5.0",
42+
"babel-register": "6.7.2",
43+
"chai": "3.5.0",
44+
"codecov.io": "0.1.6",
45+
"commitizen": "2.7.6",
46+
"compression": "1.6.1",
47+
"cz-conventional-changelog": "1.1.5",
48+
"enzyme": "2.2.0",
49+
"eslint": "2.8.0",
50+
"eslint-config-airbnb": "7.0.0",
51+
"eslint-loader": "1.3.0",
52+
"eslint-plugin-jsx-a11y": "0.6.2",
53+
"eslint-plugin-mocha": "2.2.0",
54+
"eslint-plugin-react": "4.3.0",
55+
"express": "4.13.4",
56+
"ghooks": "1.2.1",
57+
"isparta": "4.0.0",
58+
"jsdom": "8.4.0",
59+
"mocha": "2.4.5",
60+
"path": "0.12.7",
61+
"react": "15.0.1",
62+
"react-addons-test-utils": "15.0.1",
63+
"react-dom": "15.0.1",
64+
"semantic-release": "^6.2.1",
65+
"stats-webpack-plugin": "0.3.1",
66+
"webpack": "1.13.0",
67+
"webpack-dev-middleware": "1.6.1",
68+
"webpack-hot-middleware": "2.10.0"
69+
},
70+
"peerDependencies": {
71+
"react": "^0.14.0 || ^15.0.0",
72+
"react-dom": "^0.14.0 || ^15.0.0",
73+
"react-sizeme": "^1.0.0"
74+
},
75+
"czConfig": {
76+
"path": "node_modules/cz-conventional-changelog"
77+
},
78+
"config": {
79+
"ghooks": {
80+
"pre-commit": "npm run test"
81+
}
82+
},
83+
"dependencies": {
84+
"invariant": "^2.2.0"
85+
}
86+
}

src/ComponentQueries.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Libraries and Utils.
2+
import React, { Component, PropTypes } from 'react';
3+
import invariant from 'invariant';
4+
import SizeMe from 'react-sizeme';
5+
6+
/**
7+
* :: Queries -> Component -> Component
8+
*
9+
* This is a HOC that provides you with the mechanism to specify Component
10+
* queries. A Component query is a similar concept to media queries except it
11+
* operates on the Component's width/height rather than the entire viewport
12+
* width/height.
13+
*/
14+
function ComponentQueries(...queries) {
15+
invariant(
16+
queries.length > 0,
17+
`You must provide at least one query to ComponentQueries.`);
18+
invariant(
19+
queries.filter(q => typeof q !== `function`).length === 0,
20+
`All provided queries for ComponentQueries should be functions.`
21+
);
22+
23+
function query(width, height) {
24+
return queries.reduce((acc, cur) =>
25+
Object.assign({}, acc, cur(width, height))
26+
, {});
27+
}
28+
29+
return function WrapComponent(WrappedComponent) {
30+
class ComponentWithComponentQueries extends Component {
31+
state = {
32+
queryResult: {}
33+
}
34+
35+
componentWillMount() {
36+
this.runQueries(this.props.size);
37+
}
38+
39+
shouldComponentUpdate(nextProps) {
40+
const { size: currentSize } = this.props;
41+
const { size: nextSize } = nextProps;
42+
43+
if (this.hasSizeChanged(currentSize, nextSize)) {
44+
this.runQueries(nextSize);
45+
}
46+
47+
return true;
48+
}
49+
50+
hasSizeChanged(current, next) {
51+
const { height: cHeight, width: cWidth } = current;
52+
const { height: nHeight, width: nWidth } = next;
53+
54+
return (cHeight !== nHeight) || (cWidth !== nWidth);
55+
}
56+
57+
runQueries({ width, height }) {
58+
const queryResult = query(width, height);
59+
60+
this.setState({ queryResult });
61+
}
62+
63+
render() {
64+
const { size, ...otherProps } = this.props; // eslint-disable-line no-unused-vars
65+
66+
return (
67+
<WrappedComponent
68+
{...this.state.queryResult}
69+
{...otherProps}
70+
/>
71+
);
72+
}
73+
}
74+
75+
ComponentWithComponentQueries.propTypes = {
76+
size: PropTypes.shape({
77+
width: PropTypes.number.isRequired,
78+
height: PropTypes.number.isRequired
79+
}).isRequired
80+
};
81+
82+
return SizeMe({
83+
monitorWidth: true,
84+
monitorHeight: true
85+
})(ComponentWithComponentQueries);
86+
};
87+
}
88+
89+
export default ComponentQueries;

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default from './ComponentQueries';

test/ComponentQueries.test.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { describeWithDOM } from './jsdom';
4+
import { mount } from 'enzyme';
5+
6+
describeWithDOM(`Given the ComponentQueries library`, () => {
7+
let ComponentQueries;
8+
9+
beforeEach(() => {
10+
ComponentQueries = require(`../src/index.js`).default;
11+
12+
// Set up our mocks.
13+
const ComponentQueriesRewireAPI = ComponentQueries.__RewireAPI__;
14+
15+
// Mock the SizeMe HOC to just return our ComponentQueries instance.
16+
ComponentQueriesRewireAPI.__Rewire__(`SizeMe`, () => x => x);
17+
});
18+
19+
describe(`When providing a configuration object`, () => {
20+
describe(`And no queries are provided`, () => {
21+
it(`Then an error should be thrown`, () => {
22+
const action = () => {
23+
ComponentQueries();
24+
};
25+
26+
expect(action).to.throw(/provide at least one query to ComponentQueries/);
27+
});
28+
});
29+
30+
describe(`And an invalid query type is provided`, () => {
31+
it(`Then an error should be thrown`, () => {
32+
const action = () => {
33+
ComponentQueries(`wrong!`);
34+
};
35+
36+
expect(action).to.throw(/queries for ComponentQueries should be functions/);
37+
});
38+
});
39+
});
40+
41+
describe(`When rendering a component queries component`, () => {
42+
it(`Then it should receive the appropriate props based on it's queries`, () => {
43+
let receivedProps;
44+
45+
const ComponentQueriedComponent = ComponentQueries(
46+
(width) => width <= 100 ? { foo: `bar` } : {},
47+
(width) => width > 100 && width <= 500 ? { bob: `baz` } : {}
48+
)((props) => { receivedProps = props; return <div></div>; });
49+
50+
// Initial render
51+
const mounted = mount(<ComponentQueriedComponent size={{ width: 100, height: 100 }} />);
52+
expect(receivedProps).to.eql({ foo: `bar` });
53+
54+
// Update size, but no size change
55+
mounted.setProps({ size: { width: 100, height: 100 } });
56+
expect(receivedProps).to.eql({ foo: `bar` });
57+
58+
// Update size, with change.
59+
mounted.setProps({ size: { width: 101, height: 100 } });
60+
expect(receivedProps).to.eql({ bob: `baz` });
61+
});
62+
});
63+
});

0 commit comments

Comments
 (0)