├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── docs
├── App.jsx
├── Box.jsx
├── CellDemo.jsx
├── Controls.jsx
├── Cta.jsx
├── Dev.jsx
├── GridDemo.jsx
├── Intro.jsx
├── ModularScaleDemo.jsx
├── NestedGrid.jsx
├── RatiosDemo.jsx
├── Readme.jsx
├── Section.jsx
├── Social.jsx
├── TypographyDemo.jsx
├── base.css
├── bundle.js
├── data.js
└── entry.js
├── index.html
├── package.json
├── src
├── Cell.js
├── Grid.js
└── index.js
├── test
├── Cell.spec.js
├── Grid.spec.js
├── index.js
└── karma.config.js
├── webpack.config.js
└── webpack.dev.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | dist
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jxnblk/rgx/9af96aa732f62f2dc81b964f75dc29ebf1f2b1c2/.npmignore
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5.7"
4 | addons:
5 | firefox: '42.0'
6 | before_script:
7 | - export DISPLAY=:99.0
8 | - sh -e /etc/init.d/xvfb start
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rgx
2 |
3 | React grid system – constraint-based responsive grid with no CSS and no media queries.
4 |
5 | [](https://travis-ci.org/jxnblk/rgx)
6 |
7 | ## About
8 |
9 | Rgx is an experimental, responsive grid system based on minimum and maximum widths and designed for content-out layout.
10 | Rgx is built purely in React and uses inline styles, with no CSS and no media queries.
11 | Each Grid row sets its child Cells to display inline block once the Grid is wide enough to fit all Cells’ minimum widths.
12 | Once set inline, each Cell’s width is based on the ratio of its own minimum width to the sum of minimum widths per row.
13 | Once a Cell hits its max-width, the remaining space is distributed to other Cells in the row.
14 | Since this isn’t based on viewport-based media queries, the Grid responds to its own width, similar to element queries.
15 |
16 | ## Getting Started
17 |
18 | ```bash
19 | npm i rgx
20 | ```
21 |
22 | ```js
23 | import React from 'react'
24 | import { Grid, Cell } from 'rgx'
25 |
26 | class Demo extends React.Component {
27 | render () {
28 | return (
29 |
30 | Min 256 Max 320 |
31 | Min 768 |
32 |
33 | )
34 | }
35 | }
36 |
37 | React.render(, document.querySelector('#demo'))
38 | ```
39 |
40 | ## Grid Component
41 |
42 | #### Props
43 | - `gutter` - pixel value to set negative margins on the Grid component and padding on Cell components to create gutters.
44 | - `min` - pixel value to set a default `min` prop for child Cells
45 |
46 | ## Cell Component
47 |
48 | #### Props
49 | - `min` - pixel value to set the min-width at which a Cell is displayed inline.
50 | - `max` - pixel value at which the Cell should not expand. Remaining space is distributed to other Cells.
51 | - `padding` - sets left and right padding. This is used by the Grid component when the `gutter` prop is set and the Cell has no padding set.
52 | - `width` - fraction value used by the Grid component to set a width. This can also be set manually when used independently from the Grid component
53 | - `inline` - boolean value used by the Grid component to display a Cell inline.
54 |
55 | ## Performance
56 |
57 | I have yet to do any performance audits, and since the Grid component listens to window resize events,
58 | this probably has some performance issues. Any help in that area would be greatly appreciated.
59 |
60 | MIT License
61 |
62 |
--------------------------------------------------------------------------------
/docs/App.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { Header, Footer } from 'blk'
4 | import Intro from './Intro.jsx'
5 | import ModularScaleDemo from './ModularScaleDemo.jsx'
6 | import GridDemo from './GridDemo.jsx'
7 | import TypographyDemo from './TypographyDemo.jsx'
8 | import NestedGrid from './NestedGrid.jsx'
9 | import RatiosDemo from './RatiosDemo.jsx'
10 | import CellDemo from './CellDemo.jsx'
11 | import Social from './Social.jsx'
12 | import Cta from './Cta.jsx'
13 | import css from './base.css'
14 |
15 | class App extends React.Component {
16 |
17 | constructor () {
18 | super ()
19 | this.state = {
20 | base: 16,
21 | }
22 | this.handleChange = this.handleChange.bind(this)
23 | }
24 |
25 | handleChange (e) {
26 | let state = this.state
27 | state[e.target.name] = parseFloat(e.target.value)
28 | this.setState(state)
29 | }
30 |
31 | render () {
32 | let props = this.props
33 | let state = this.state
34 |
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 |
51 | }
52 |
53 | export default App
54 |
55 |
--------------------------------------------------------------------------------
/docs/Box.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | class Box extends React.Component {
5 |
6 | render () {
7 | let style = {
8 | fontSize: 12,
9 | fontWeight: 'bold',
10 | textAlign: 'center',
11 | padding: '8px 4px',
12 | marginBottom: 16,
13 | border: '1px solid silver'
14 | }
15 | return (
16 |
17 | {this.props.children}
18 |
19 | )
20 | }
21 |
22 | }
23 |
24 | export default Box
25 |
26 |
--------------------------------------------------------------------------------
/docs/CellDemo.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { Grid, Cell } from '..'
4 | import Section from './Section.jsx'
5 | import Box from './Box.jsx'
6 |
7 | class CellDemo extends React.Component {
8 |
9 | render () {
10 | let props = this.props
11 | return (
12 |
13 | Cell
14 |
15 |
16 |
17 |
18 | {' | '}
19 |
20 |
21 | |
22 |
23 |
24 |
25 | {' | '}
26 |
27 |
28 | |
29 |
30 |
31 |
32 | {' | '}
33 |
34 |
35 | |
36 |
37 |
38 |
39 |
40 | The Cell component can be used independently to manually arrange elements inline with a set percentage based width.
41 |
42 | |
43 | |
44 |
45 |
46 | )
47 | }
48 |
49 | }
50 |
51 | export default CellDemo
52 |
53 |
--------------------------------------------------------------------------------
/docs/Controls.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { Range } from 'rebass'
4 | import { Grid, Cell } from '..'
5 |
6 | class Controls extends React.Component {
7 |
8 | render () {
9 | let props = this.props
10 | return (
11 |
12 |
13 |
14 |
21 | |
22 |
23 | |
24 |
25 |
26 | )
27 | }
28 |
29 | }
30 |
31 | export default Controls
32 |
33 |
--------------------------------------------------------------------------------
/docs/Cta.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import Section from './Section.jsx'
4 |
5 | class Cta extends React.Component {
6 |
7 | render () {
8 | return (
9 |
10 | Get Started
11 | npm i rgx
12 |
13 | Read the docs on GitHub to learn more.
14 |
15 |
17 | GitHub
18 |
19 |
20 | )
21 | }
22 |
23 | }
24 |
25 | export default Cta
26 |
27 |
--------------------------------------------------------------------------------
/docs/Dev.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { Grid, Cell } from '..'
4 | import Box from './Box.jsx'
5 |
6 | class Dev extends React.Component {
7 |
8 | render () {
9 | let styles = {
10 | container: {
11 | paddingTop: 48,
12 | paddingBottom: 48,
13 | marginBottom: 48,
14 | border: '1px solid red'
15 | }
16 | }
17 | return (
18 |
19 |
20 |
21 | min/max
22 | |
23 |
24 | min/max
25 | |
26 |
27 | min
28 | |
29 |
30 |
31 |
32 | 128/256
33 | |
34 |
35 | 128
36 | |
37 |
38 |
39 |
40 | 256
41 | |
42 |
43 | 256
44 | |
45 |
46 | 256/320
47 | |
48 |
49 |
50 |
51 | 160
52 | |
53 |
54 | 320/768
55 | |
56 |
57 | 128
58 | |
59 |
60 |
61 |
62 | 128/160
63 | |
64 |
65 | 128/192
66 | |
67 |
68 | min 96
69 | |
70 |
71 | min 256
72 | |
73 |
74 |
75 | )
76 | }
77 |
78 | }
79 |
80 | export default Dev
81 |
82 |
--------------------------------------------------------------------------------
/docs/GridDemo.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { Grid, Cell } from '..'
4 | import Box from './Box.jsx'
5 |
6 | class GridDemo extends React.Component {
7 |
8 | render () {
9 | let props = this.props
10 | return (
11 |
12 | {props.grid.cells.map(function(cell, i) {
13 | return (
14 |
17 | {cell.min} min
18 | |
19 | )
20 | })}
21 |
22 | )
23 | }
24 |
25 | }
26 |
27 | GridDemo.propTypes = {
28 | gutter: React.PropTypes.number,
29 | grid: React.PropTypes.object
30 | }
31 |
32 | export default GridDemo
33 |
34 |
--------------------------------------------------------------------------------
/docs/Intro.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import Section from './Section.jsx'
4 |
5 | class Intro extends React.Component {
6 |
7 | render () {
8 | return (
9 |
10 |
11 | Rgx is an experimental, responsive grid system based on minimum and maximum widths and designed for content-out layout.
12 | Rgx is built purely in React and uses inline styles, with no CSS and no media queries.
13 | Each Grid row sets its child Cells to display inline block once the Grid is wide enough to fit all Cells’ minimum widths.
14 | Once set inline, each Cell’s width is based on the ratio of its own minimum width to the sum of minimum widths per row.
15 | Once a Cell hits its max-width, the remaining space is distributed to other Cells in the row.
16 | Since this isn’t based on viewport-based media queries, the Grid responds to its own width, similar to element queries.
17 |
18 |
19 | )
20 | }
21 |
22 | }
23 |
24 | export default Intro
25 |
26 |
--------------------------------------------------------------------------------
/docs/ModularScaleDemo.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ms from 'simple-modular-scale'
4 | import { Grid, Cell } from '..'
5 | import GridDemo from './GridDemo.jsx'
6 | import Section from './Section.jsx'
7 |
8 | class ModularScaleDemo extends React.Component {
9 |
10 | render () {
11 | let props = this.props
12 | let scale = ms({
13 | base: 4 * props.base,
14 | ratios: [3/2, 4/3],
15 | length: 8
16 | })
17 |
18 | let g1 = []
19 | for (var i = scale.length - 1; i > -1; i--) {
20 | let l = scale.length - i
21 | let cells = []
22 | let min = scale[i]
23 | for (var j = 0; j < l; j++) {
24 | cells.push({ min: min })
25 | }
26 | g1.push({ cells: cells })
27 | }
28 |
29 | return (
30 |
31 | {g1.map(function(grid, i) {
32 | return (
33 |
36 | )
37 | })}
38 |
39 |
40 |
41 | Each Cell has a min prop that defines the minimum width at which it can be set inline as a column.
42 | Once set inline, each Cell’s width is determined as the ratio of its minimum width to the total for all Cells in a Grid row.
43 |
44 |
45 | Sizes in this demo are based on a modular scale: {scale.join(' : ')}
46 |
47 | |
48 | |
49 |
50 |
51 | )
52 | }
53 |
54 | }
55 |
56 | export default ModularScaleDemo
57 |
58 |
--------------------------------------------------------------------------------
/docs/NestedGrid.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { Grid, Cell } from '..'
4 | import Section from './Section.jsx'
5 | import Box from './Box.jsx'
6 |
7 | class NestedGrid extends React.Component {
8 |
9 | render () {
10 | let props = this.props
11 | return (
12 |
13 | Nested Grid
14 |
15 |
16 | 256 min
17 | |
18 |
19 |
20 |
21 | 256 min
22 | |
23 |
24 | 256 min
25 | |
26 |
27 | |
28 |
29 |
30 |
31 | 256/320 min/max
32 | |
33 |
34 |
35 |
36 | 256 min
37 | |
38 |
39 | 256 min
40 | |
41 |
42 | |
43 |
44 |
45 |
46 |
47 | The Cells in this demo all have a min value of 256, and two Cells are nested within another.
48 | The two top-level Cells take up 50% of the width, and the nested Cells are 50% of the parent Cell.
49 | Since the collapsing behavior is based on the container Grid’s width, the nested Cells will collapse before the parent Cells do.
50 | In the second row, the first Cell has a max of 320, and the remaining Cell stretches to fill the space.
51 |
52 | |
53 | |
54 |
55 |
56 | )
57 | }
58 |
59 | }
60 |
61 | export default NestedGrid
62 |
63 |
--------------------------------------------------------------------------------
/docs/RatiosDemo.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ms from 'simple-modular-scale'
4 | import { Grid, Cell } from '..'
5 | import Section from './Section.jsx'
6 | import GridDemo from './GridDemo.jsx'
7 |
8 | class RatiosDemo extends React.Component {
9 |
10 | render () {
11 | let props = this.props
12 | let scale = ms({
13 | base: 64,
14 | ratios: [3/2, 4/3],
15 | length: 8
16 | }).reverse()
17 | let grids = []
18 |
19 | for (var i = 0; i < scale.length - 1; i++) {
20 | if (i % 2 === 0) {
21 | grids.push({
22 | cells: [
23 | { min: scale[i] },
24 | { min: scale[i+1] }
25 | ]
26 | })
27 | }
28 | }
29 |
30 | return (
31 |
32 | Similar Ratios
33 | {grids.map(function(grid, i) {
34 | return (
35 |
38 | )
39 | })}
40 |
41 |
42 |
43 | Cells with similar ratios will align horizontally when they are set inline. Different `min` values will cause the Cells to collapse at different widths.
44 |
45 | |
46 | |
47 |
48 |
49 | )
50 | }
51 |
52 | }
53 |
54 | export default RatiosDemo
55 |
56 |
--------------------------------------------------------------------------------
/docs/Readme.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import md from '../README.md'
4 | import Section from './Section.jsx'
5 |
6 | class Readme extends React.Component {
7 |
8 | render () {
9 | let html = {
10 | __html: md
11 | }
12 | let style = {
13 | lineHeight: 1.625,
14 | maxWidth: '40em',
15 | }
16 | return (
17 |
21 | )
22 | }
23 |
24 | }
25 |
26 | export default Readme
27 |
28 |
--------------------------------------------------------------------------------
/docs/Section.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ms from 'simple-modular-scale'
4 |
5 | class Section extends React.Component {
6 |
7 | render () {
8 | let scale = ms()
9 | let style = {
10 | paddingTop: scale[3],
11 | paddingBottom: scale[3],
12 | }
13 | return (
14 |
15 | {this.props.children}
16 |
17 | )
18 | }
19 |
20 | }
21 |
22 | export default Section
23 |
24 |
--------------------------------------------------------------------------------
/docs/Social.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { TweetButton, GithubButton, CarbonAd } from 'blk'
4 |
5 | class Social extends React.Component {
6 |
7 | render () {
8 | return (
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 | }
25 |
26 | export default Social
27 |
28 |
--------------------------------------------------------------------------------
/docs/TypographyDemo.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ms from 'simple-modular-scale'
4 | import { Grid, Cell } from '..'
5 | import Section from './Section.jsx'
6 |
7 | class TypographyDemo extends React.Component {
8 |
9 | render () {
10 | let props = this.props
11 | let scale = ms({
12 | base: props.base,
13 | ratios: [ 9/8, 4/3, 4/3 ],
14 | length: 6
15 | })
16 | let font = 'Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif'
17 | let styles = {
18 | container: {
19 | fontFamily: font
20 | },
21 | heading: {
22 | fontFamily: font,
23 | fontSize: scale[5]
24 | },
25 | a: {
26 | fontFamily: font,
27 | fontSize: scale[2],
28 | marginTop: 0
29 | },
30 | b: {
31 | fontFamily: font,
32 | fontSize: scale[1],
33 | marginTop: 0
34 | },
35 | c: {
36 | fontFamily: font,
37 | fontSize: scale[0],
38 | marginTop: 0
39 | }
40 | }
41 | return (
42 |
43 |
44 |
Typography Demo
45 |
46 | In this example, font sizes are based on a modular scale, and each Cell’s min
property is set to the font size multiplied by 16.
47 |
48 |
49 |
50 | {scale[2]}px • {16 * scale[2]}/{32 * scale[2]}px min/max
51 |
52 | {this.props.bacon.substring(0, scale[2] / (1/16) )}...
53 |
54 | |
55 |
56 | {scale[1]}px • {16 * scale[1]}/{32 * scale[1]}px min/max
57 |
58 | {this.props.bacon.substring(0, scale[1] / (1/32))}...
59 |
60 | |
61 |
62 | {scale[0]}px • {16 * scale[0]}px min
63 |
64 | {this.props.bacon}
65 |
66 | |
67 | |
68 |
69 |
70 |
71 | )
72 | }
73 |
74 | }
75 |
76 | export default TypographyDemo
77 |
78 |
--------------------------------------------------------------------------------
/docs/base.css:
--------------------------------------------------------------------------------
1 |
2 | @import 'blk';
3 |
4 |
--------------------------------------------------------------------------------
/docs/data.js:
--------------------------------------------------------------------------------
1 |
2 | import pkg from '../package.json'
3 | import { capitalize } from 'lodash'
4 |
5 | export default {
6 | name: pkg.name,
7 | title: capitalize(pkg.name),
8 | href: 'http://jxnblk.com/rgx',
9 | description: pkg.description,
10 | links: [
11 | { href: pkg.homepage, text: 'GitHub' },
12 | { href: '///npmjs.com/package/' + pkg.name, text: 'npm' },
13 | ],
14 | homepage: pkg.homepage,
15 | bacon: 'Bacon ipsum dolor amet short loin capicola porchetta pork pork chop cow, tri-tip bresaola tenderloin short ribs picanha drumstick chicken t-bone. Bacon rump tail meatloaf, salami chicken shank swine short loin porchetta shankle kielbasa. Pork chop brisket kevin pancetta bacon, jowl sirloin leberkas. Tenderloin shoulder filet mignon kielbasa cupim brisket turducken tail drumstick. Sausage pig porchetta, pork turkey t-bone fatback kevin. Pork loin bacon rump venison, meatloaf salami doner pig pork belly chicken pancetta jowl leberkas t-bone. Porchetta andouille ham ball tip pork turducken tail pork chop fatback ground round doner t-bone.',
16 | ipsum: 'Leberkas spare ribs swine kevin turkey turducken landjaeger shoulder. Doner tongue bacon, drumstick alcatra beef pork loin swine frankfurter strip steak hamburger meatball. Turducken prosciutto shoulder sausage pastrami pig ham hock, beef ribeye tongue short ribs tri-tip ground round. Turducken brisket sausage prosciutto landjaeger, hamburger drumstick filet mignon ball tip sirloin jerky. Brisket venison hamburger jerky spare ribs, ribeye chicken bacon pig. Tenderloin tail swine cow pastrami tri-tip. Beef ribs meatloaf andouille pork loin ham tail beef, kielbasa alcatra swine tongue hamburger jerky sausage pork belly.',
17 | }
18 |
--------------------------------------------------------------------------------
/docs/entry.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import data from './data'
5 | import App from './App'
6 |
7 | ReactDOM.render(, document.querySelector('#app'))
8 |
9 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | rgx
6 |
7 |
8 |
9 |
10 |
13 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rgx",
3 | "version": "0.2.4",
4 | "description": "React grid system – constraint-based responsive grid with no CSS and no media queries",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "prepublish": "babel src --out-dir dist",
8 | "dev": "webpack-dev-server --progress --colors --config webpack.dev.config.js",
9 | "docs": "webpack -p --progress --colors",
10 | "start": "npm run dev",
11 | "mocha": "mocha test --compilers js:babel-register",
12 | "karma:watch": "karma start test/karma.config.js --no-single-run",
13 | "karma": "./node_modules/.bin/karma start test/karma.config.js",
14 | "test": "npm run mocha && npm run karma"
15 | },
16 | "author": "Brent Jackson",
17 | "license": "MIT",
18 | "keywords": [
19 | "constraint",
20 | "grid",
21 | "layout",
22 | "react",
23 | "react-component",
24 | "style"
25 | ],
26 | "devDependencies": {
27 | "babel-cli": "^6.5.1",
28 | "babel-core": "^6.5.2",
29 | "babel-loader": "^6.2.4",
30 | "babel-preset-es2015": "^6.5.0",
31 | "babel-preset-react": "^6.5.0",
32 | "babel-preset-stage-0": "^6.5.0",
33 | "babel-register": "^6.5.2",
34 | "basscss-color-input-range": "^1.0.0",
35 | "basscss-input-range": "^1.1.5",
36 | "blk": "^3.1.0",
37 | "css-loader": "^0.15.1",
38 | "cssnext-loader": "^1.0.1",
39 | "expect": "^1.10.0",
40 | "file-loader": "^0.8.4",
41 | "html-loader": "^0.3.0",
42 | "json-loader": "^0.5.2",
43 | "karma": "^0.13.9",
44 | "karma-chai": "^0.1.0",
45 | "karma-chai-plugins": "^0.6.0",
46 | "karma-chrome-launcher": "^0.2.0",
47 | "karma-cli": "^0.1.0",
48 | "karma-firefox-launcher": "^0.1.6",
49 | "karma-mocha": "^0.2.0",
50 | "karma-mocha-reporter": "^1.2.3",
51 | "karma-webpack": "^1.7.0",
52 | "lodash": "^4.5.1",
53 | "markdown-loader": "^0.1.3",
54 | "marked": "^0.3.3",
55 | "mocha": "^2.3.2",
56 | "node-libs-browser": "^0.5.2",
57 | "raw-loader": "^0.5.1",
58 | "react": "^0.14.7",
59 | "react-addons-test-utils": "^0.14.7",
60 | "react-dom": "^0.14.7",
61 | "react-hot-loader": "^1.3.0",
62 | "rebass": "^0.1.3",
63 | "simple-modular-scale": "^1.0.2",
64 | "style-loader": "^0.12.3",
65 | "webpack": "^1.10.1",
66 | "webpack-dev-server": "^1.10.1"
67 | },
68 | "peerDependencies": {
69 | "react": "^0.14.0"
70 | },
71 | "repository": {
72 | "type": "git",
73 | "url": "git+https://github.com/jxnblk/rgx.git"
74 | },
75 | "bugs": {
76 | "url": "https://github.com/jxnblk/rgx/issues"
77 | },
78 | "homepage": "https://github.com/jxnblk/rgx",
79 | "babel": {
80 | "presets": [
81 | "es2015",
82 | "stage-0",
83 | "react"
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Cell.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | /**
5 | * Child component of Grid that displays inline when
6 | * there is enough space in the container
7 | */
8 |
9 | class Cell extends React.Component {
10 |
11 | render () {
12 | const { inline, width, padding, children } = this.props
13 | const style = {
14 | boxSizing: 'border-box',
15 | display: inline ? 'inline-block' : 'block',
16 | width: inline ? `${width * 100}%` : '100%',
17 | verticalAlign: 'top',
18 | paddingLeft: padding,
19 | paddingRight: padding,
20 | position: 'relative'
21 | }
22 |
23 | return (
24 |
25 | {children}
26 |
27 | )
28 | }
29 | }
30 |
31 | Cell.propTypes = {
32 | /** Min-width to display inline */
33 | min: React.PropTypes.number,
34 | /** Max-width for Cell */
35 | max: React.PropTypes.number,
36 | /** Width of cell when inline is true - value should be 0–1 */
37 | width: React.PropTypes.number,
38 | /** Left and right padding for creating gutters */
39 | padding: React.PropTypes.number,
40 | /** Sets display inline-block and activates width prop */
41 | inline: React.PropTypes.bool,
42 | }
43 |
44 | Cell.defaultProps = {
45 | min: 640,
46 | max: null,
47 | width: 100,
48 | padding: 0,
49 | inline: false
50 | }
51 |
52 | export default Cell
53 |
54 |
--------------------------------------------------------------------------------
/src/Grid.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { throttle } from 'lodash'
4 |
5 | const win = typeof window !== 'undefined' ? window : false
6 |
7 | /**
8 | * Parent component for Cell that calculates available
9 | * width for setting Cells inline.
10 | */
11 |
12 | class Grid extends React.Component {
13 |
14 | constructor () {
15 | super ()
16 | this.updateWidth = this.updateWidth.bind(this)
17 | this.getMinTotal = this.getMinTotal.bind(this)
18 | this.state = {
19 | width: 768
20 | }
21 | }
22 |
23 | updateWidth () {
24 | const el = this.refs.root
25 | const { width } = el.getBoundingClientRect()
26 | this.setState({ width })
27 | }
28 |
29 | getMinTotal () {
30 | let total = 0
31 | const { children, min } = this.props
32 | React.Children.map(children, (child, i) => {
33 | let childMin = child.props.min || false
34 | if (!childMin) {
35 | childMin = min
36 | }
37 | total += childMin
38 | })
39 | return total
40 | }
41 |
42 | componentDidMount () {
43 | this.updateWidth()
44 | if (win) {
45 | this.startListeningForResize()
46 | }
47 | }
48 |
49 | componentWillUnmount () {
50 | if (win) {
51 | this.stopListeningForResize()
52 | }
53 | }
54 |
55 | componentDidUpdate (prevProps) {
56 | if (win && prevProps.throttleResize !== this.props.throttleResize) {
57 | this.stopListeningForResize()
58 | this.startListeningForResize()
59 | }
60 | }
61 |
62 | startListeningForResize () {
63 | this.throttledUpdateWidth = throttle(this.updateWidth, this.props.throttleResize)
64 | win.addEventListener('resize', this.throttledUpdateWidth)
65 | }
66 |
67 | stopListeningForResize () {
68 | win.removeEventListener('resize', this.throttledUpdateWidth)
69 | }
70 |
71 | render () {
72 | const { children, gutter } = this.props
73 | const { width } = this.state
74 | const style = {
75 | overflow: 'hidden',
76 | marginLeft: -gutter,
77 | marginRight: -gutter,
78 | position: 'relative'
79 | }
80 |
81 | // min width denominator
82 | const dmin = this.getMinTotal()
83 | // min values of max cells
84 | let maxmins = []
85 | // max values of max cells
86 | let maxes = []
87 |
88 | React.Children.map(children, (child) => {
89 | if (child.props.max && child.props.min / dmin * width > child.props.max) {
90 | maxes.push(child.props.max)
91 | maxmins.push(child.props.min)
92 | }
93 | })
94 |
95 | // sum of max cell values
96 | const maxSum = maxes.length ? maxes.reduce((a, b) => { return a + b }) : 0
97 | // sum of min values for max cells
98 | const maxminSum = maxmins.length ? maxmins.reduce((a, b) => { return a + b }) : 0
99 | // percent offset from remaining min cell widths
100 | const offset = (maxSum / width) / ((children ? children.length : 0) - maxes.length)
101 | const denominator = dmin - maxminSum
102 |
103 | // set child props
104 | const modifiedChildren = React.Children.map(children, (child) => {
105 | let childWidth = child.props.min / denominator - offset
106 | if (child.props.max && child.props.min / dmin * width > child.props.max) {
107 | childWidth = child.props.max / width
108 | }
109 | let childProps = {
110 | width: childWidth,
111 | inline: dmin < width
112 | }
113 | if (!child.props.padding) {
114 | childProps.padding = gutter
115 | }
116 | return React.cloneElement(child, childProps)
117 | })
118 |
119 | return (
120 |
123 | {modifiedChildren}
124 |
125 | )
126 | }
127 |
128 | }
129 |
130 | Grid.propTypes = {
131 | /** Sets a default min prop on child Cell components */
132 | min: React.PropTypes.number,
133 | /** Sets negative left and right margins to compensate for Cell padding prop */
134 | gutter: React.PropTypes.number,
135 | /** Milliseconds for throttling window resize listener */
136 | throttleResize: React.PropTypes.number,
137 | }
138 |
139 | Grid.defaultProps = {
140 | min: 640,
141 | gutter: 0,
142 | throttleResize: 200
143 | }
144 |
145 | export default Grid
146 |
147 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | export { default as Grid } from './Grid'
3 | export { default as Cell } from './Cell'
4 |
5 |
--------------------------------------------------------------------------------
/test/Cell.spec.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import TestUtils from 'react-addons-test-utils'
4 | import expect from 'expect'
5 | import Cell from '../src/Cell'
6 |
7 | const renderer = TestUtils.createRenderer()
8 |
9 | describe('Cell', () => {
10 | let cell
11 |
12 | describe('shallow tests', () => {
13 | beforeEach(() => {
14 | renderer.render( | )
15 | cell = renderer.getRenderOutput()
16 | })
17 |
18 | it('should render', () => {
19 | expect(cell.type).toEqual('div')
20 | })
21 |
22 | it('should default to 100% width', () => {
23 | expect(cell.props.style.width).toEqual('100%')
24 | })
25 |
26 | it('should have no default padding', () => {
27 | expect(cell.props.style.paddingLeft).toEqual(0)
28 | expect(cell.props.style.paddingRight).toEqual(0)
29 | })
30 |
31 | it('should not be inline by default', () => {
32 | expect(cell.props.inline).toNotExist()
33 | })
34 |
35 | it('should not have a default max', () => {
36 | expect(cell.props.max).toNotExist()
37 | })
38 | })
39 |
40 | describe('browser tests', () => {
41 | if (typeof document === 'undefined') {
42 | return false
43 | }
44 |
45 | beforeEach(() => {
46 | cell = TestUtils.renderIntoDocument(
47 |
48 | Cell
49 | |
50 | )
51 | })
52 |
53 | it('should render', () => {
54 | expect(cell).toExist()
55 | })
56 |
57 | it('should be properly styled', () => {
58 | const style = cell.refs.cell.style
59 | expect(style.boxSizing).toEqual('border-box')
60 | expect(style.position).toEqual('relative')
61 | expect(style.paddingLeft).toEqual('16px')
62 | })
63 | })
64 |
65 | })
66 |
67 |
--------------------------------------------------------------------------------
/test/Grid.spec.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import TestUtils from 'react-addons-test-utils'
5 | import expect from 'expect'
6 | import Grid from '../src/Grid'
7 | import Cell from '../src/Cell'
8 |
9 | const renderer = TestUtils.createRenderer()
10 |
11 | describe('Grid', () => {
12 | let grid
13 |
14 | describe('shallow tests', () => {
15 | beforeEach(() => {
16 | renderer.render()
17 | grid = renderer.getRenderOutput()
18 | })
19 |
20 | it('should render', () => {
21 | expect(grid.type).toEqual('div')
22 | })
23 |
24 | it('should have no default margin', () => {
25 | expect(grid.props.style.marginLeft).toEqual(0)
26 | expect(grid.props.style.marginRight).toEqual(0)
27 | })
28 |
29 | context('when gutter is set', () => {
30 | beforeEach(() => {
31 | renderer.render()
32 | grid = renderer.getRenderOutput()
33 | })
34 |
35 | it('should have negative left and right margins', () => {
36 | expect(grid.props.style.marginLeft).toEqual(-16)
37 | expect(grid.props.style.marginRight).toEqual(-16)
38 | })
39 | })
40 |
41 | context('with Cell children', () => {
42 | let c1, c2
43 | beforeEach(() => {
44 | renderer.render(
45 |
46 | |
47 | |
48 |
49 | )
50 | grid = renderer.getRenderOutput()
51 | c1 = grid.props.children[0]
52 | c2 = grid.props.children[1]
53 | })
54 |
55 | it('should render children Cells', () => {
56 | expect(grid.props.children.length).toEqual(2)
57 | })
58 |
59 | it('should set Cells’ inline prop', () => {
60 | expect(c1.props.inline).toEqual(true)
61 | expect(c2.props.inline).toEqual(true)
62 | })
63 |
64 | it('should set Cells’ widths as ratios of default width', () => {
65 | expect(c1.props.width).toEqual(256 / 768)
66 | expect(c2.props.width).toEqual(512 / 768)
67 | })
68 | })
69 |
70 | context('with gutter and Cell children', () => {
71 | let c1, c2
72 | beforeEach(() => {
73 | renderer.render(
74 |
75 | |
76 | |
77 |
78 | )
79 | grid = renderer.getRenderOutput()
80 | c1 = grid.props.children[0]
81 | c2 = grid.props.children[1]
82 | })
83 |
84 | it('should set negative left and right margins', () => {
85 | expect(grid.props.style.marginLeft).toEqual(-16)
86 | expect(grid.props.style.marginRight).toEqual(-16)
87 | })
88 |
89 | it('should render children Cells', () => {
90 | expect(grid.props.children.length).toEqual(2)
91 | })
92 |
93 | it('should set Cells’ inline prop', () => {
94 | expect(c1.props.inline).toEqual(true)
95 | expect(c2.props.inline).toEqual(true)
96 | })
97 |
98 | it('should set Cells’ widths as ratios of default width', () => {
99 | expect(c1.props.width).toEqual(256 / 768)
100 | expect(c2.props.width).toEqual(512 / 768)
101 | })
102 |
103 | it('should set Cells’ padding', () => {
104 | expect(c1.props.padding).toEqual(16)
105 | expect(c2.props.padding).toEqual(16)
106 | })
107 | })
108 |
109 | context('with gutter and Cell children with padding', () => {
110 | let c1, c2
111 | beforeEach(() => {
112 | renderer.render(
113 |
114 | |
115 | |
116 |
117 | )
118 | grid = renderer.getRenderOutput()
119 | c1 = grid.props.children[0]
120 | c2 = grid.props.children[1]
121 | })
122 |
123 | it('should set negative left and right margins', () => {
124 | expect(grid.props.style.marginLeft).toEqual(-16)
125 | expect(grid.props.style.marginRight).toEqual(-16)
126 | })
127 |
128 | it('should render children Cells', () => {
129 | expect(grid.props.children.length).toEqual(2)
130 | })
131 |
132 | it('should set Cells’ inline prop', () => {
133 | expect(c1.props.inline).toEqual(true)
134 | expect(c2.props.inline).toEqual(true)
135 | })
136 |
137 | it('should set Cells’ widths as ratios of default width', () => {
138 | expect(c1.props.width).toEqual(256 / 768)
139 | expect(c2.props.width).toEqual(512 / 768)
140 | })
141 |
142 | it('should not set first Cell’s padding', () => {
143 | expect(c1.props.padding).toEqual(32)
144 | })
145 |
146 | it('should set second Cell’s padding', () => {
147 | expect(c2.props.padding).toEqual(16)
148 | })
149 | })
150 | })
151 |
152 |
153 | describe('browser tests', () => {
154 | if (typeof document === 'undefined') {
155 | return false
156 | }
157 |
158 | let div = document.createElement('div'),
159 | c1,
160 | c2
161 |
162 | document.body.appendChild(div)
163 |
164 | context('at wider width', () => {
165 | beforeEach(() => {
166 | grid = ReactDOM.render(
167 |
168 |
169 | Cell 192
170 | |
171 |
172 | Cell 576
173 | |
174 | ,
175 | div
176 | )
177 | const cells = TestUtils.scryRenderedComponentsWithType(grid, Cell)
178 | c1 = cells[0]
179 | c2 = cells[1]
180 | })
181 |
182 | afterEach(() => {
183 | ReactDOM.unmountComponentAtNode(div)
184 | })
185 |
186 | it('should render', () => {
187 | expect(grid).toExist()
188 | })
189 |
190 | it('should have negative margins', () => {
191 | const el = grid.refs.root
192 | expect(el.style.marginLeft).toEqual('-32px')
193 | expect(el.style.marginRight).toEqual('-32px')
194 | })
195 |
196 | it('should render children', () => {
197 | expect(c1).toExist()
198 | expect(c2).toExist()
199 | })
200 |
201 | it('should set padding on children', () => {
202 | expect(c1.props.padding).toEqual(32)
203 | expect(c2.props.padding).toEqual(32)
204 | })
205 |
206 | it('should style padding on children', () => {
207 | expect(c1.refs.cell.style.paddingLeft).toEqual('32px')
208 | expect(c1.refs.cell.style.paddingRight).toEqual('32px')
209 | expect(c2.refs.cell.style.paddingLeft).toEqual('32px')
210 | expect(c2.refs.cell.style.paddingRight).toEqual('32px')
211 | })
212 |
213 | it('should set children inline', () => {
214 | expect(c1.props.inline).toEqual(true)
215 | expect(c2.props.inline).toEqual(true)
216 | })
217 |
218 | it('should set correct widths for children', () => {
219 | expect(c1.props.width).toEqual(192/768)
220 | expect(c2.props.width).toEqual(576/768)
221 | })
222 |
223 | it('should style widths on children', () => {
224 | expect(c1.refs.cell.style.width).toEqual('25%')
225 | expect(c2.refs.cell.style.width).toEqual('75%')
226 | })
227 | })
228 |
229 | context('when in a smaller width container', () => {
230 | beforeEach((done) => {
231 | div.style.width = '512px'
232 | grid = ReactDOM.render(
233 |
234 |
235 | Cell 192
236 | |
237 |
238 | Cell 576
239 | |
240 | ,
241 | div
242 | )
243 | window.setTimeout(() => {
244 | const cells = TestUtils.scryRenderedComponentsWithType(grid, Cell)
245 | c1 = cells[0]
246 | c2 = cells[1]
247 | done()
248 | }, 100, this)
249 | })
250 |
251 | afterEach(() => {
252 | ReactDOM.unmountComponentAtNode(div)
253 | })
254 |
255 | it('should set padding on children', () => {
256 | expect(c1.props.padding).toEqual(32)
257 | expect(c2.props.padding).toEqual(32)
258 | })
259 |
260 | it('should not set children inline', () => {
261 | expect(c1.props.inline).toEqual(false)
262 | expect(c2.props.inline).toEqual(false)
263 | })
264 |
265 | it('should style children at full-width', () => {
266 | expect(c1.refs.cell.style.width).toEqual('100%')
267 | expect(c2.refs.cell.style.width).toEqual('100%')
268 | })
269 |
270 | it('should have the Cells stacked', () => {
271 | const y1 = c1.refs.cell.getBoundingClientRect().top
272 | const y2 = c2.refs.cell.getBoundingClientRect().top
273 | expect(y1 < y2).toEqual(true)
274 | })
275 | })
276 | })
277 | })
278 |
279 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 |
2 | import './Grid.spec'
3 | import './Cell.spec'
4 |
5 | // var context = require.context('.', true, /.+\.spec\.jsx?$/)
6 | // context.keys().forEach(context)
7 | // module.exports = context
8 |
9 |
--------------------------------------------------------------------------------
/test/karma.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function (config) {
3 | config.set({
4 | browsers: [
5 | // 'Chrome',
6 | 'Firefox'
7 | ],
8 |
9 | files: [
10 | 'index.js'
11 | ],
12 |
13 | frameworks: [ 'chai', 'mocha' ],
14 |
15 | plugins: [
16 | 'karma-chrome-launcher',
17 | 'karma-firefox-launcher',
18 | 'karma-chai',
19 | 'karma-mocha',
20 | 'karma-mocha-reporter',
21 | 'karma-webpack',
22 | ],
23 |
24 | preprocessors: {
25 | 'index.js': [
26 | 'webpack'
27 | ]
28 | },
29 | reporters: [
30 | 'mocha'
31 | ],
32 | singleRun: true,
33 |
34 | webpack: {
35 | module: {
36 | loaders: [
37 | {
38 | test: /\.jsx?$/,
39 | exclude: /node_modules/,
40 | loader: 'babel'
41 | }
42 | ],
43 | },
44 | resolve: {
45 | extensions: ['', '.js', '.jsx']
46 | }
47 | },
48 |
49 | webpackMiddleware: {
50 | noInfo: true,
51 | }
52 |
53 | })
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | var webpack = require('webpack')
3 |
4 | module.exports = {
5 |
6 | entry: [
7 | './docs/entry.js'
8 | ],
9 |
10 | output: {
11 | filename: 'bundle.js',
12 | publicPath: '/docs/',
13 | path: __dirname + '/docs'
14 | },
15 |
16 | module: {
17 | loaders: [
18 | {
19 | test: /(\.js$|\.jsx?$)/,
20 | exclude: /node_modules/,
21 | loaders: [
22 | 'react-hot',
23 | 'babel'
24 | ]
25 | },
26 | { test: /\.json/, loader: 'json' },
27 | { test: /\.md/, loader: 'html!markdown-loader' },
28 | { test: /\.css$/, loader: 'style!css!cssnext' }
29 | ]
30 | },
31 |
32 | resolve: {
33 | extensions: ['', '.js', '.jsx']
34 | },
35 |
36 | cssnext: {
37 | features: {
38 | customProperties: {
39 | variables: {
40 | 'font-family': '"SF UI Text", "Helvetica Neue", sans-serif',
41 | 'bold-font-weight': 500,
42 | 'heading-font-weight': 500,
43 | }
44 | }
45 | }
46 | }
47 |
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 |
2 | require('babel-register')()
3 |
4 | var _ = require('lodash')
5 | var config = require('./webpack.config.js')
6 | var webpack = require('webpack')
7 |
8 | module.exports = _.extend(config, {
9 |
10 | entry: [
11 | 'webpack-dev-server/client?http://localhost:8080/',
12 | 'webpack/hot/only-dev-server',
13 | './docs/entry.js'
14 | ],
15 |
16 | plugins: [
17 | new webpack.HotModuleReplacementPlugin()
18 | ],
19 |
20 | devServer: {
21 | hot: true
22 | }
23 |
24 | })
25 |
26 |
--------------------------------------------------------------------------------