50 | ```
51 |
52 | Grid exposes the property `options` allowing you to define custom grid settings.
53 |
54 | `options` shape:
55 |
56 | ```js
57 | {
58 | columns: number // default = 12 - Columns size for the bigger media.
59 | gutter: number // default = 16 - Gutter size in pixel.
60 | margin: number // default = 16 - Margin size in pixel.
61 | deaf: bool // default = false - Ignore MediaQueryList updates.
62 | list: [ // default = [...] - List of target media.
63 | {
64 | name: string // required - Media name.
65 | query: string // required - Media query to test.
66 | gutter: number // default = options -> gutter - Media gutter size in pixel.
67 | margin: number // default = options -> margin - Media margin size in pixel.
68 | }
69 | ]
70 | }
71 | ```
72 |
73 | If `options` is not provided, or invalid, it will be fixed to apply values inspired by [Google Material Design Lite](http://www.getmdl.io/) grid layout:
74 |
75 | ```js
76 | // options -> list
77 | [
78 | {
79 | name: 'phone',
80 | gutter: 16,
81 | margin: 16,
82 | columns: 4,
83 | query: '(max-width: 479px)'
84 | },
85 | {
86 | name: 'tablet',
87 | gutter: 16,
88 | margin: 16,
89 | columns: 8,
90 | query: '(min-width: 480px) and (max-width: 839px)'
91 | },
92 | {
93 | name: 'desktop',
94 | gutter: 16,
95 | margin: 16,
96 | columns: 12,
97 | query: '(min-width: 840px)'
98 | }
99 | ]
100 | ```
101 |
102 | If no media match the queries, Grid will define the first `options -> list -> value` as default current media in order to match the "popular" mobile first approch.
103 |
104 | ### <Row />
105 |
106 | Exposes the property `is` (string) to update the following default style object:
107 |
108 | ```js
109 | {
110 | display: 'flex',
111 | flexFlow: 'row wrap',
112 | alignItems: 'stretch'
113 | }
114 | ```
115 | `is` specify the `justify-content` style property as:
116 | - `start`
117 | - `center`
118 | - `end`
119 | - `around`
120 | - `between`
121 |
122 | ```js
123 |
124 |
125 | Content
126 | |
127 |
128 |
129 | // not phone
130 |
131 |
132 | Content
133 | |
134 |
135 |
136 | // phone
137 |
138 |
139 | Content
140 | |
141 |
142 |
143 | ```
144 |
145 | ### <Cell />
146 |
147 | Exposes the property `is` (string) to update the following default style object:
148 |
149 | ```js
150 | {
151 | boxSizing: 'border-box'
152 | }
153 | ```
154 | `is` specify cell size and `align-self` style property as:
155 | - `
`
156 | - `-`
157 | - `-offset-`
158 | - `top`
159 | - `middle`
160 | - `bottom`
161 | - `stretch`
162 |
163 | ```js
164 |
165 |
166 | Content
167 | |
168 |
169 |
170 | // desktop
171 |
172 |
175 |
176 |
177 | // tablet
178 |
179 |
182 |
183 |
184 | // phone
185 |
186 |
189 |
190 | ```
191 |
192 | For both `
` and ` | `, `is` property ask for an "already defined" values, the last one is used:
193 |
194 | ```js
195 |
196 | Content
197 | |
198 |
199 | // will be defined as
200 |
201 |
202 | Content
203 | |
204 | ```
205 |
206 | ## Examples
207 |
208 | The [gh-pages](http://broucz.github.io/react-inline-grid/) page of this repository use some patterns as examples, but feel free to play and test your layouts using the `examples` folder.
209 |
210 | Run the gh-pages example:
211 |
212 | ```
213 | git clone https://github.com/broucz/react-inline-grid.git
214 |
215 | cd react-inline-grid
216 | npm install
217 |
218 | cd examples/react-transform-boilerplate
219 | npm install
220 |
221 | npm start
222 | open http://localhost:3000/
223 | ```
224 |
225 | ## Thanks
226 |
227 | * [Redux](https://github.com/rackt/redux) I learned a lot from package evolution, author [@gaearon](https://github.com/gaearon), contributors, and related discussions.
228 | * [React](https://facebook.github.io/react) for the fun.
229 | * [React Redux](https://github.com/rackt/react-redux) to make it easier.
230 |
--------------------------------------------------------------------------------
/examples/gh-pages/README.md:
--------------------------------------------------------------------------------
1 | react-inline-grid-gh-pages
2 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Alignments.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Row, Cell } from 'react-inline-grid';
3 | import Code from 'react-embed-code';
4 | import { COLOR } from '../constants';
5 | import Box from './Box';
6 |
7 | const { gray, primary } = COLOR;
8 |
9 | const strHorizontal = `Place at the 'start', 'center', or 'end' of a .
10 | `;
11 |
12 | const strVertical = `Place at the 'top', 'middle', or 'bottom' of a .
13 | `;
14 |
15 | const str1 = `
16 |
17 |
18 | |
19 |
20 |
21 |
22 |
23 |
24 | |
25 |
26 |
27 |
28 |
29 |
30 | |
31 |
32 | `;
33 |
34 | const str2 = `
35 |
36 |
37 | |
38 |
39 |
40 | |
41 |
42 |
43 |
44 |
45 |
46 | |
47 |
48 |
49 | |
50 |
51 |
52 |
53 |
54 |
55 | |
56 |
57 |
58 | |
59 |
60 | `;
61 |
62 | class Alignments extends Component {
63 | render() {
64 | return (
65 |
66 |
67 |
68 | Alignments
69 | Horizontal
70 |
71 | |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | |
80 |
81 |
82 | |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | |
91 |
92 |
93 | |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | |
102 |
103 |
104 | |
105 |
106 |
107 |
108 |
109 |
110 | Vertical
111 |
112 | |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | |
122 |
123 |
124 | |
125 |
126 |
127 | |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | |
136 |
137 |
138 | |
139 |
140 |
141 | |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | |
150 |
151 |
152 | |
153 |
154 |
155 | |
156 |
157 |
158 |
159 |
160 |
161 | |
162 |
163 |
164 | );
165 | }
166 | }
167 |
168 | export default Alignments;
169 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Box.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | const STYLE_PROPS = {
4 | boxDefault: {
5 | color: 'white',
6 | paddingLeft: '8px',
7 | paddingTop: '4px'
8 | },
9 | box: { height: '30px' },
10 | big: { height: '90px' },
11 | huge: { height: '200px' }
12 | };
13 |
14 | const { boxDefault } = STYLE_PROPS;
15 |
16 | class Box extends Component {
17 | render() {
18 | const { color, size } = this.props;
19 | const boxStyle =
20 | STYLE_PROPS[size]
21 | || STYLE_PROPS.box;
22 | const background = { background: color };
23 | return (
24 |
25 | );
26 | }
27 | }
28 |
29 | Box.propTypes = {
30 | color: PropTypes.string,
31 | size: PropTypes.string
32 | };
33 |
34 | export default Box;
35 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Distribution.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Row, Cell } from 'react-inline-grid';
3 | import Code from 'react-embed-code';
4 | import { COLOR } from '../constants';
5 | import Box from './Box';
6 |
7 | const { gray, primary } = COLOR;
8 |
9 | const strAround = ` are positioned with space before && between && and after.
10 | `;
11 |
12 | const strBetween = ` are positioned with space between.
13 | `;
14 |
15 | const str1 = `
16 | |
17 | |
18 | |
19 |
20 | `;
21 |
22 | const str2 = `
23 | |
24 | |
25 | |
26 |
27 | `;
28 |
29 | class Distribution extends Component {
30 | render() {
31 | return (
32 |
33 |
34 |
35 | Distribution
36 | Around
37 |
38 | |
39 |
40 |
41 |
42 |
43 |
44 | |
45 | |
46 | |
47 |
48 |
49 | |
50 |
51 |
52 |
53 |
54 |
55 | Between
56 |
57 | |
58 |
59 |
60 |
61 |
62 |
63 |
64 | |
65 | |
66 | |
67 |
68 |
69 | |
70 |
71 |
72 |
73 |
74 | |
75 |
76 |
77 | );
78 | }
79 | }
80 |
81 | export default Distribution;
82 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class Header extends Component {
4 | render() {
5 | return (
6 |
7 | react-inline-grid
8 | A predictable gird layout based on flexbox for React applications using inline styles.
9 | View on GitHub
10 |
11 | );
12 | }
13 | }
14 |
15 | export default Header;
16 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Intro.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Row, Cell } from 'react-inline-grid';
3 | import { COLOR } from '../constants';
4 | import Box from './Box';
5 |
6 | const { gray, primary } = COLOR;
7 |
8 | class Intro extends Component {
9 | render() {
10 | return (
11 |
12 |
13 | |
14 | |
15 | |
16 | |
17 |
18 | |
19 | |
20 | |
21 |
22 | |
23 | |
24 | |
25 |
26 |
27 | );
28 | }
29 | }
30 |
31 | export default Intro;
32 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Offset.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Row, Cell } from 'react-inline-grid';
3 | import Code from 'react-embed-code';
4 | import { COLOR } from '../constants';
5 | import Box from './Box';
6 |
7 | const { primary } = COLOR;
8 |
9 | const strOffset = `Apply 'margin-left' to
10 | `;
11 |
12 | const str = `
13 |
14 |
15 | |
16 |
17 |
18 |
19 |
20 | |
21 |
22 |
23 |
24 |
25 | |
26 |
27 | `;
28 |
29 | class Offset extends Component {
30 | render() {
31 | return (
32 |
33 |
34 |
35 | Offset
36 |
37 | |
38 |
39 |
40 | |
41 | |
42 | |
43 |
44 |
45 |
46 |
47 | |
48 |
49 |
50 | );
51 | }
52 | }
53 |
54 | export default Offset;
55 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Root.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Grid, Row, Cell } from 'react-inline-grid';
3 | import Header from './Header';
4 | import Intro from './Intro';
5 | import Alignments from './Alignments';
6 | import Offset from './Offset';
7 | import Distribution from './Distribution';
8 | import Spacing from './Spacing';
9 |
10 | class Root extends Component {
11 | render() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | export default Root;
34 |
--------------------------------------------------------------------------------
/examples/gh-pages/components/Spacing.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Row, Cell } from 'react-inline-grid';
3 | import Code from 'react-embed-code';
4 | import { COLOR } from '../constants';
5 | import Box from './Box';
6 |
7 | const { gray, primary } = COLOR;
8 |
9 | const strSpace = `Remove padding and maring.
10 | `;
11 |
12 | const str = `
13 |
14 |
15 | |
16 |
17 | `;
18 |
19 | class Spacing extends Component {
20 | render() {
21 | return (
22 |
23 |
24 |
25 | No Spacing
26 |
27 | |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | |
37 |
38 |
39 | |
40 |
41 |
42 |
43 |
44 | |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | export default Spacing;
52 |
--------------------------------------------------------------------------------
/examples/gh-pages/constants.js:
--------------------------------------------------------------------------------
1 | export const COLOR = {
2 | gray: '#bdbdbd',
3 | primary: '#4285F4'
4 | };
5 |
--------------------------------------------------------------------------------
/examples/gh-pages/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | react-inline-grid
8 |
9 |
10 |
11 |
12 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/gh-pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Root from './components/Root';
4 |
5 | import 'normalize.css';
6 | import './main.scss';
7 |
8 | ReactDOM.render(, document.getElementById('mount'));
9 |
--------------------------------------------------------------------------------
/examples/gh-pages/main.scss:
--------------------------------------------------------------------------------
1 | $large-breakpoint: 64em;
2 | $medium-breakpoint: 42em;
3 |
4 | @mixin large {
5 | @media screen and (min-width: #{$large-breakpoint}) {
6 | @content;
7 | }
8 | }
9 |
10 | @mixin medium {
11 | @media screen and (min-width: #{$medium-breakpoint}) and (max-width: #{$large-breakpoint}) {
12 | @content;
13 | }
14 | }
15 |
16 | @mixin small {
17 | @media screen and (max-width: #{$medium-breakpoint}) {
18 | @content;
19 | }
20 | }
21 |
22 | * {
23 | box-sizing: border-box;
24 | }
25 |
26 | body {
27 | padding: 0;
28 | margin: 0;
29 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
30 | font-size: 16px;
31 | line-height: 1.5;
32 | color: #606c71;
33 | }
34 |
35 | a {
36 | color: #4285F4;
37 | text-decoration: none;
38 |
39 | &:hover {
40 | text-decoration: underline;
41 | }
42 | }
43 |
44 | .btn {
45 | display: inline-block;
46 | margin-bottom: 1em;
47 | color: rgba(255, 255, 255, 0.7);
48 | background-color: rgba(255, 255, 255, 0.08);
49 | border-color: rgba(255, 255, 255, 0.2);
50 | border-style: solid;
51 | border-width: 1px;
52 | border-radius: 0.3em;
53 | transition: color 0.2s, background-color 0.2s, border-color 0.2s;
54 |
55 | &:hover {
56 | color: rgba(255, 255, 255, 0.8);
57 | text-decoration: none;
58 | background-color: rgba(255, 255, 255, 0.2);
59 | border-color: rgba(255, 255, 255, 0.3);
60 | }
61 |
62 | + .btn {
63 | margin-left: 1em;
64 | }
65 |
66 | @include large {
67 | padding: 0.75em 1em;
68 | }
69 |
70 | @include medium {
71 | padding: 0.6em 0.9em;
72 | font-size: 0.9em;
73 | }
74 |
75 | @include small {
76 | display: block;
77 | width: 100%;
78 | padding: 0.75em;
79 | font-size: 0.9em;
80 |
81 | + .btn {
82 | margin-top: 1em;
83 | margin-left: 0;
84 | }
85 | }
86 | }
87 |
88 | .pageHeader {
89 | color: #fff;
90 | text-align: center;
91 | background: -webkit-linear-gradient(120deg, #4285F4 10%, #4A75BD 90%);
92 | background: -moz-linear-gradient(120deg, #4285F4 10%, #4A75BD 90%);
93 | background: -ms-linear-gradient(120deg, #4285F4 10%, #4A75BD 90%);
94 | background: -o-linear-gradient(120deg, #4285F4 10%, #4A75BD 90%);
95 | background: linear-gradient(120deg, #4285F4 10%, #4A75BD 90%);
96 |
97 |
98 | @include large {
99 | padding: 5em 6em;
100 | }
101 |
102 | @include medium {
103 | padding: 3em 4em;
104 | }
105 |
106 | @include small {
107 | padding: 2em 1em;
108 | }
109 | }
110 |
111 | .pageHeaderTitle {
112 | margin-top: 0;
113 | margin-bottom: 0.1em;
114 | font-family: Menlo, Monaco, Courier, monospace;
115 |
116 | @include large {
117 | font-size: 3.25em;
118 | }
119 |
120 | @include medium {
121 | font-size: 2.25em;
122 | }
123 |
124 | @include small {
125 | font-size: 1.75em;
126 | }
127 | }
128 |
129 | .pageHeaderTagline {
130 | margin-bottom: 2em;
131 | font-weight: normal;
132 | opacity: 0.8;
133 |
134 | @include large {
135 | font-size: 1.25em;
136 | }
137 |
138 | @include medium {
139 | font-size: 1.15em;
140 | }
141 |
142 | @include small {
143 | font-size: 1em;
144 | }
145 |
146 | a {
147 | color: #fff;
148 | text-decoration: underline;
149 | }
150 | }
151 |
152 | .pageContent {
153 | font-size: 1.1em;
154 | }
155 |
--------------------------------------------------------------------------------
/examples/gh-pages/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inline-grid-gh-pages",
3 | "version": "0.0.0",
4 | "description": "Github website for React Inline Grid",
5 | "scripts": {
6 | "start": "node webpack.server.js"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/broucz/react-inline-grid.git"
11 | },
12 | "author": "Pierre Brouca (https://github.com/broucz)",
13 | "license": "MIT",
14 | "bugs": {
15 | "url": "https://github.com/broucz/react-inline-grid/issues"
16 | },
17 | "homepage": "https://github.com/broucz/react-inline-grid",
18 | "dependencies": {
19 | "normalize.css": "^3.0.3",
20 | "react": "^0.14.0 || ^0.14.0-beta3 || ^0.14.0-rc1",
21 | "react-dom": "^0.14.0 || ^0.14.0-beta3 || ^0.14.0-rc1",
22 | "react-embed-code": "^0.1.0"
23 | },
24 | "devDependencies": {
25 | "babel": "^5.8.21",
26 | "babel-core": "^5.8.22",
27 | "babel-loader": "^5.3.2",
28 | "css-loader": "^0.16.0",
29 | "node-sass": "^3.3.2",
30 | "react-hot-loader": "^1.2.8",
31 | "sass-loader": "^2.0.1",
32 | "style-loader": "^0.12.3",
33 | "webpack": "^1.11.0",
34 | "webpack-dev-server": "^1.10.1"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/gh-pages/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var webpack = require('webpack');
5 |
6 | module.exports = {
7 | entry: [path.join(__dirname)],
8 | output: {
9 | path: path.join(__dirname, 'dist'),
10 | filename: 'bundle.js'
11 | },
12 | module: {
13 | loaders: [
14 | {
15 | test: /\.js$/,
16 | loaders: ['babel-loader'],
17 | exclude: /node_modules/
18 | },
19 | {
20 | test: /\.css$/,
21 | loaders: [ 'style-loader', 'css-loader' ]
22 | },
23 | {
24 | test: /\.scss$/,
25 | loader: 'style!css!sass'
26 | }
27 | ]
28 | },
29 | resolve: {
30 | alias: {
31 | 'react-inline-grid': path.join(__dirname, '..', '..', 'src'),
32 | 'react': path.resolve(__dirname, 'node_modules', 'react')
33 | }
34 | },
35 | plugins: [
36 | new webpack.optimize.OccurenceOrderPlugin()
37 | ]
38 | };
39 |
--------------------------------------------------------------------------------
/examples/gh-pages/webpack.config.development.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var path = require('path');
5 | var webpack = require('webpack');
6 | var baseConfig = require('./webpack.config.base');
7 |
8 | module.exports = _.merge({}, baseConfig, {
9 | entry: _.union(baseConfig.entry, [
10 | 'webpack-dev-server/client?http://localhost:3000',
11 | 'webpack/hot/only-dev-server'
12 | ]),
13 | plugins: _.union(baseConfig.plugins, [
14 | new webpack.DefinePlugin({
15 | '__DEV__': 'false',
16 | 'process.env.NODE_ENV': '"production"'
17 | }),
18 | new webpack.HotModuleReplacementPlugin(),
19 | new webpack.NoErrorsPlugin()
20 | ])
21 | });
22 |
--------------------------------------------------------------------------------
/examples/gh-pages/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var webpack = require('webpack');
5 | var baseConfig = require('./webpack.config.base');
6 |
7 | module.exports = _.merge({}, baseConfig, {
8 | plugins: _.union(baseConfig.plugins, [
9 | new webpack.DefinePlugin({
10 | '__DEV__': 'false',
11 | 'process.env.NODE_ENV': '"production"'
12 | }),
13 | new webpack.optimize.UglifyJsPlugin({
14 | compressor: {
15 | pure_getters: true,
16 | unsafe: true,
17 | unsafe_comps: true,
18 | screw_ie8: true,
19 | warnings: false
20 | }
21 | })
22 | ])
23 | });
24 |
--------------------------------------------------------------------------------
/examples/gh-pages/webpack.server.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('./webpack.config.development');
4 |
5 | new WebpackDevServer(webpack(config), {
6 | publicPath: config.output.publicPath,
7 | hot: true,
8 | historyApiFallback: true,
9 | stats: {
10 | colors: true
11 | }
12 | }).listen(3000, 'localhost', function (err) {
13 | if (err) {
14 | console.log(err);
15 | }
16 |
17 | console.log('Listening at localhost:3000');
18 | });
19 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "plugins": [
4 | "react-transform"
5 | ],
6 | "extra": {
7 | "react-transform": [{
8 | "target": "react-transform-webpack-hmr",
9 | "imports": ["react"],
10 | "locals": ["module"]
11 | }, {
12 | "target": "react-transform-catch-errors",
13 | "imports": ["react", "redbox-react"]
14 | }]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/README.md:
--------------------------------------------------------------------------------
1 | react-inline-grid-transform-boilerplate
2 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | DEV | react-inline-grid
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inline-grid-transform-boilerplate",
3 | "version": "0.0.0",
4 | "description": "Workspace for react-inline-gridA using https://github.com/gaearon/react-transform-boilerplate.",
5 | "scripts": {
6 | "start": "node server.js"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/broucz/react-inline-grid.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/broucz/react-inline-grid/issues"
14 | },
15 | "homepage": "https://github.com/broucz/react-inline-grid",
16 | "devDependencies": {
17 | "babel-core": "^5.4.7",
18 | "babel-eslint": "^3.1.9",
19 | "babel-loader": "^5.1.2",
20 | "babel-plugin-react-transform": "^1.0.1",
21 | "eslint": "^1.3.1",
22 | "eslint-plugin-react": "^2.3.0",
23 | "express": "^4.13.3",
24 | "react-transform-catch-errors": "^0.1.1",
25 | "react-transform-webpack-hmr": "^0.1.2",
26 | "redbox-react": "^1.0.1",
27 | "webpack": "^1.9.6",
28 | "webpack-dev-middleware": "^1.2.0",
29 | "webpack-hot-middleware": "^2.0.0"
30 | },
31 | "dependencies": {
32 | "react": "^0.14.0 || ^0.14.0-beta3 || ^0.14.0-rc1",
33 | "react-dom": "^0.14.0 || ^0.14.0-beta3 || ^0.14.0-rc1",
34 | "react-redux": "^2.1.1",
35 | "redux": "^2.0.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var express = require('express');
3 | var webpack = require('webpack');
4 | var config = require('./webpack.config');
5 |
6 | var app = express();
7 | var compiler = webpack(config);
8 |
9 | app.use(require('webpack-dev-middleware')(compiler, {
10 | noInfo: true,
11 | publicPath: config.output.publicPath
12 | }));
13 |
14 | app.use(require('webpack-hot-middleware')(compiler));
15 |
16 | app.get('*', function(req, res) {
17 | res.sendFile(path.join(__dirname, 'index.html'));
18 | });
19 |
20 | app.listen(3000, 'localhost', function (err) {
21 | if (err) {
22 | console.log(err);
23 | return;
24 | }
25 |
26 | console.log('Listening at http://localhost:3000');
27 | });
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/src/App.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-multi-comp */
2 |
3 | import React, { Component } from 'react';
4 | import { Grid } from 'react-inline-grid';
5 | import ToolBar from './ToolBar';
6 | import Content from './Content';
7 |
8 | const options = {
9 | gutter: 16,
10 | margin: 16,
11 | list: [
12 | {
13 | name: 'phone',
14 | query: '(max-width: 479px)'
15 | },
16 | {
17 | name: 'tablet',
18 | query: '(min-width: 480px) and (max-width: 839px)'
19 | },
20 | {
21 | name: 'desktop',
22 | query: '(min-width: 840px)'
23 | }
24 | ]
25 | };
26 |
27 | class WorkSpace extends Component {
28 | render() {
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | export class App extends Component {
41 | render() {
42 | return (
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/src/Box.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | const container = {
4 | box: { height: '30px' },
5 | big: { height: '90px' },
6 | huge: { height: '200px' }
7 | };
8 |
9 | export default class Box extends Component {
10 | static propTypes = {
11 | color: PropTypes.string.isRequired,
12 | size: PropTypes.string
13 | };
14 |
15 | render() {
16 | const { color, size } = this.props;
17 | const height = container[size] || container.box;
18 | const background = { background: color };
19 | return (
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/src/Content.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Row, Cell } from 'react-inline-grid';
3 | import Box from './Box';
4 |
5 | const gray = '#bdbdbd';
6 | const primary = '#4A75BD';
7 |
8 | export default class Content extends Component {
9 | render() {
10 | return (
11 |
12 | |
13 | |
14 | |
15 | |
16 |
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/src/ToolBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { Row, Cell } from 'react-inline-grid';
5 | import { updateMediaName } from './actionCreators';
6 |
7 | const mapStateToProps = state => ({ ...state });
8 | const mapDispatchToProps = dispatch => {
9 | return bindActionCreators({ updateMediaName }, dispatch);
10 | };
11 |
12 | const container = {
13 | color: '#fff',
14 | fontFamily: 'Helvetica Neue,Helvetica,Arial,sans-serif',
15 | fontSize: '.9rem',
16 | background: '#4A75BD'
17 | };
18 |
19 | const btn = {
20 | width: '100%',
21 | boxSizing: 'border-box',
22 | padding: '.75em 1em',
23 | textAlign: 'center',
24 | color: 'rgba(255, 255, 255, 0.7)',
25 | backgroundColor: 'rgba(255, 255, 255, 0.08)',
26 | border: '1px solid rgba(255, 255, 255, 0.2)',
27 | borderRadius: '0.3em'
28 | };
29 |
30 | class ToolBar extends Component {
31 | static propTypes = {
32 | updateMediaName: PropTypes.func.isRequired
33 | };
34 |
35 | render() {
36 | return (
37 |
38 |
39 |
40 | phone
41 | |
42 |
43 | tablet
44 | |
45 |
46 | desktop
47 | |
48 |
49 |
50 | );
51 | }
52 |
53 | handleUpdateMediaName(name) {
54 | return this.props.updateMediaName(name);
55 | }
56 | }
57 |
58 | export default connect(
59 | mapStateToProps,
60 | mapDispatchToProps
61 | )(ToolBar);
62 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/src/actionCreators.js:
--------------------------------------------------------------------------------
1 | export function updateMediaName(payload) {
2 | return {
3 | type: 'media/name/UPDATE',
4 | payload
5 | };
6 | }
7 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { App } from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/examples/react-transform-boilerplate/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: 'eval',
6 | entry: [
7 | 'webpack-hot-middleware/client',
8 | './src/index'
9 | ],
10 | output: {
11 | path: path.join(__dirname, 'dist'),
12 | filename: 'bundle.js',
13 | publicPath: '/static/'
14 | },
15 | plugins: [
16 | new webpack.HotModuleReplacementPlugin(),
17 | new webpack.NoErrorsPlugin()
18 | ],
19 | resolve: {
20 | alias: {
21 | 'react-inline-grid': path.join(__dirname, '..', '..', 'src'),
22 | 'react': path.resolve('node_modules', 'react')
23 | },
24 | extensions: ['', '.js']
25 | },
26 | module: {
27 | loaders: [{
28 | test: /\.js$/,
29 | loaders: ['babel-loader'],
30 | include: [
31 | path.join(__dirname, 'src'),
32 | path.join(__dirname, '..', '..', 'src')
33 | ]
34 | }]
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inline-grid",
3 | "description": "A predictable gird layout based on flexbox for React.",
4 | "main": "./lib/index.js",
5 | "version": "0.5.3",
6 | "scripts": {
7 | "clean": "rimraf lib dist coverage",
8 | "lint": "eslint src test examples",
9 | "build:lib": "babel src --out-dir lib",
10 | "build:umd": "webpack src/index.js dist/react-inline-grid.js --config webpack.config.development.js",
11 | "build:umd:min": "webpack src/index.js dist/react-inline-grid.min.js --config webpack.config.production.js",
12 | "build": "npm run build:lib && npm run build:umd && npm run build:umd:min",
13 | "prepublish": "npm run clean && npm run build",
14 | "gh:clean": "rimraf examples/gh-pages/dist",
15 | "gh:build": "webpack -p --config examples/gh-pages/webpack.config.production.js && cp examples/gh-pages/index.html examples/gh-pages/dist/",
16 | "gh:publish": "npm run gh:clean && npm run gh:build && cd examples/gh-pages/dist && git init && git commit --allow-empty -m 'update gh-pages' && git checkout -b gh-pages && git add . && git commit -am 'update gh-pages' && git push git@github.com:broucz/react-inline-grid gh-pages --force",
17 | "test": "mocha --compilers js:babel/register --recursive",
18 | "test:watch": "npm test -- --watch",
19 | "test:cov": "babel-node $(npm bin)/isparta cover $(npm bin)/_mocha -- --recursive"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/broucz/react-inline-grid.git"
24 | },
25 | "keywords": [
26 | "react",
27 | "reactjs",
28 | "grid",
29 | "inline",
30 | "style",
31 | "flux",
32 | "redux",
33 | "predictable",
34 | "react-component"
35 | ],
36 | "author": "Pierre Brouca (https://github.com/broucz)",
37 | "license": "MIT",
38 | "bugs": {
39 | "url": "https://github.com/broucz/react-inline-grid/issues"
40 | },
41 | "homepage": "https://github.com/broucz/react-inline-grid",
42 | "dependencies": {
43 | "lodash": "^3.10.1",
44 | "react-redux": "^2.1.1",
45 | "redux": "^2.0.0"
46 | },
47 | "peerDependencies": {
48 | "react": "^0.14.0 || ^0.14.0-beta3 || ^0.14.0-rc1"
49 | },
50 | "devDependencies": {
51 | "babel": "^5.8.21",
52 | "babel-core": "^5.8.22",
53 | "babel-eslint": "^4.0.10",
54 | "babel-loader": "^5.3.2",
55 | "eslint": "^1.2.0",
56 | "eslint-config-airbnb": "0.0.7",
57 | "eslint-plugin-react": "^3.2.3",
58 | "expect": "^1.9.0",
59 | "isparta": "^3.0.3",
60 | "mocha": "^2.2.5",
61 | "react-hot-loader": "^1.2.8",
62 | "rimraf": "^2.4.2",
63 | "webpack": "^1.11.0",
64 | "webpack-dev-server": "^1.10.1"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/Grid.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes, Children } from 'react';
2 | import { Provider } from 'react-redux';
3 | import matchMedia from '../matchMedia';
4 | import store from '../store';
5 | import { updateMediaName } from '../reducers/media';
6 | import { MEDIA_MODEL_HELPER } from '../constants';
7 | import invariant from '../utils/invariant';
8 |
9 | const optionsShape = PropTypes.shape({
10 | columns: PropTypes.number,
11 | gutter: PropTypes.number,
12 | margin: PropTypes.number,
13 | deaf: PropTypes.bool,
14 | list: PropTypes.arrayOf(
15 | PropTypes.shape({
16 | name: PropTypes.string.isRequired,
17 | query: PropTypes.string.isRequired,
18 | gutter: PropTypes.number,
19 | margin: PropTypes.number
20 | })
21 | )
22 | });
23 |
24 | export function ensureValue(options, base, key, value) {
25 | if (process.env.NODE_ENV !== 'production') {
26 | invariant(
27 | key,
28 | ` -> ensureValue -> key must be defined.`
29 | );
30 |
31 | invariant(
32 | base,
33 | ` -> ensureValue -> base must be defined.`
34 | );
35 |
36 | invariant(
37 | (typeof base[key] !== 'undefined'),
38 | ` -> ensureValue -> base -> key must be defined.`
39 | );
40 | }
41 |
42 | if (value >= 0) return value;
43 | const result = (options && options[key] >= 0)
44 | ? options[key]
45 | : base[key];
46 | return result;
47 | }
48 |
49 | export function ensureListProperties(options, base, list) {
50 | return list.map(n => {
51 | const { name, query, gutter, margin } = n;
52 | return {
53 | name,
54 | query,
55 | gutter: ensureValue(options, base, 'gutter', gutter),
56 | margin: ensureValue(options, base, 'margin', margin)
57 | };
58 | });
59 | }
60 |
61 | export function build(options = {}, base = {}) {
62 | const {
63 | columns,
64 | deaf = false,
65 | list = base.list
66 | } = options;
67 |
68 | const size = list.length;
69 |
70 | invariant(
71 | !!size,
72 | ' -> options -> list can not be empty'
73 | );
74 |
75 | if (columns) {
76 | invariant(
77 | !(columns % size) > 0,
78 | ' -> options -> columns must be a multiple of ' +
79 | ' -> options -> list -> length'
80 | );
81 | }
82 |
83 | return {
84 | columns: columns || size * 4,
85 | deaf,
86 | list: ensureListProperties(options, base, list)
87 | };
88 | }
89 |
90 | export function setMedia(name) {
91 | return { name };
92 | }
93 |
94 | export function setReference(options) {
95 | return { options };
96 | }
97 |
98 | export default class Grid extends Component {
99 | static propTypes = {
100 | options: optionsShape,
101 | children: PropTypes.element.isRequired
102 | };
103 |
104 | constructor(props, context) {
105 | super(props, context);
106 |
107 | // Initialize a new Model:
108 | // If -> options is missing, it return a default Model.
109 | // if -> options is provided, it return a valid Model.
110 | const model = build(props.options, MEDIA_MODEL_HELPER);
111 |
112 | this.match = matchMedia(model.list);
113 | this.shouldSubscribe = model.deaf !== true;
114 |
115 | // Initialize Redux `store`.
116 | const media = setMedia(this.match.getCurrentName);
117 | const reference = setReference(model);
118 | this.store = store({ media, reference });
119 | }
120 |
121 | componentDidMount() {
122 | this.trySubscribe();
123 | }
124 |
125 | componentWillUnmount() {
126 | this.tryUnsubscribe();
127 | }
128 |
129 | render() {
130 | return (
131 |
132 | {Children.only(this.props.children)}
133 |
134 | );
135 | }
136 |
137 | trySubscribe() {
138 | if (this.shouldSubscribe && !this.unsubscribe) {
139 | this.unsubscribe =
140 | this.match.subscribe(this.handleChange.bind(this));
141 | }
142 | }
143 |
144 | tryUnsubscribe() {
145 | if (this.unsubscribe) {
146 | this.unsubscribe();
147 | this.unsubscribe = null;
148 | }
149 | }
150 |
151 | handleChange(payload) {
152 | if (!this.unsubscribe) {
153 | return;
154 | }
155 | this.store.dispatch(updateMediaName(payload));
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/components/createComponent.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes, Children, cloneElement } from 'react';
2 | import { connect } from 'react-redux';
3 | import pick from '../utils/pick';
4 |
5 | const mapStateToProps = state => ({ ...state });
6 |
7 | const mergeProps =
8 | (stateProps, dispatchProps, { is, ...clean }) => ({
9 | ...clean,
10 | grid: {
11 | ...stateProps,
12 | is
13 | }
14 | });
15 |
16 | const gridShape =
17 | PropTypes.shape({
18 | media: PropTypes.object.isRequired,
19 | reference: PropTypes.object.isRequired,
20 | is: PropTypes.string
21 | }).isRequired;
22 |
23 | const elem = (tag) => {
24 | return class Elem extends Component {
25 | static propTypes = {
26 | grid: gridShape
27 | };
28 |
29 | shouldComponentUpdate(nextProps) {
30 | if (process.env.NODE_ENV !== 'production') {
31 | return true;
32 | }
33 |
34 | if (process.env.NODE_ENV === 'production') {
35 | return (nextProps.grid.media.name !== this.props.grid.media.name)
36 | || (nextProps.grid.is !== this.props.grid.is);
37 | }
38 | }
39 |
40 | render() {
41 | const { grid, children, ...clean } = this.props;
42 | return (
43 |
44 | {Children.map(children, child => {
45 | return cloneElement(child, {...clean});
46 | })}
47 |
48 | );
49 | }
50 | };
51 | };
52 |
53 | export default function createComponent(tag) {
54 | return connect(
55 | mapStateToProps,
56 | null,
57 | mergeProps)(elem(tag));
58 | }
59 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const UPDATE_MEDIA_NAME = 'media/name/UPDATE';
2 | export const ROW_ID = 'row';
3 | export const CELL_ID = 'cell';
4 | export const MEDIA_MODEL_HELPER = {
5 | gutter: 16,
6 | margin: 16,
7 | list: [
8 | {
9 | name: 'phone',
10 | query: '(max-width: 479px)'
11 | },
12 | {
13 | name: 'tablet',
14 | query: '(min-width: 480px) and (max-width: 839px)'
15 | },
16 | {
17 | name: 'desktop',
18 | query: '(min-width: 840px)'
19 | }
20 | ]
21 | };
22 | export const WHITE_LIST = {
23 | [ROW_ID]: [
24 | 'row',
25 | 'start',
26 | 'center',
27 | 'end',
28 | 'around',
29 | 'between',
30 | 'nospace'
31 | ],
32 | [CELL_ID]: [
33 | 'cell',
34 | '1',
35 | '2',
36 | '3',
37 | '4',
38 | '5',
39 | '6',
40 | '7',
41 | '8',
42 | '9',
43 | '10',
44 | '11',
45 | '12',
46 | 'top',
47 | 'middle',
48 | 'bottom',
49 | 'stretch',
50 | 'between',
51 | 'offset',
52 | 'nospace'
53 | ]
54 | };
55 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { ROW_ID, CELL_ID } from './constants';
2 | import createComponent from './components/createComponent';
3 |
4 | export { default as Grid } from './components/Grid';
5 |
6 | const Row = createComponent(ROW_ID);
7 | const Cell = createComponent(CELL_ID);
8 |
9 | export {
10 | Row,
11 | Cell
12 | };
13 |
--------------------------------------------------------------------------------
/src/matchMedia.js:
--------------------------------------------------------------------------------
1 | function setModel(options) {
2 | return options.map(n => {
3 | const { name, query } = n;
4 | return {
5 | name,
6 | query
7 | };
8 | });
9 | }
10 |
11 | function setState(model, handleChange) {
12 | return model.map(({ name, query }) => {
13 | const match = window.matchMedia(query);
14 | match.add = () => match.addListener(handleChange);
15 | match.add();
16 | match.remove = () => match.removeListener(handleChange);
17 |
18 | return {
19 | name,
20 | match
21 | };
22 | });
23 | }
24 |
25 | class MatchMedia {
26 | constructor(list) {
27 | this.listeners = [];
28 | this.state = [];
29 | this.model = setModel(list.slice());
30 |
31 | return this.updateState();
32 | }
33 |
34 | handleChange() {
35 | this.state.slice().forEach(({ match }) => match.remove());
36 |
37 | return this.updateState();
38 | }
39 |
40 | updateState() {
41 | this.state =
42 | setState(this.model, this.handleChange.bind(this));
43 |
44 | return this.dispatchUpdate();
45 | }
46 |
47 | getCurrentName() {
48 | const current =
49 | this.state.filter(({ match }) => match.matches);
50 |
51 | const { name } =
52 | (current.length > 0)
53 | ? current[0]
54 | : this.model[0];
55 |
56 | return name;
57 | }
58 |
59 | dispatchUpdate() {
60 | const current = this.getCurrentName();
61 | return this.listeners
62 | .slice()
63 | .forEach(listener => listener(current));
64 | }
65 |
66 | subscribe(listener) {
67 | this.listeners.push(listener);
68 |
69 | return function unsubscribe() {
70 | if (this.listeners != null) {
71 | const index = this.listeners.indexOf(listener);
72 | this.listeners = this.listeners.slice(index, 1);
73 | }
74 | };
75 | }
76 | }
77 |
78 | export default function matchMedia(list) {
79 | const mM = new MatchMedia(list);
80 |
81 | return {
82 | subscribe: mM.subscribe.bind(mM),
83 | getCurrentName: mM.getCurrentName()
84 | };
85 | }
86 |
--------------------------------------------------------------------------------
/src/reducers/media.js:
--------------------------------------------------------------------------------
1 | import { UPDATE_MEDIA_NAME } from '../constants';
2 |
3 | export function hydrateMedia({ name }) {
4 | return {
5 | name
6 | };
7 | }
8 |
9 | export function updateMediaName(payload) {
10 | return {
11 | type: UPDATE_MEDIA_NAME,
12 | payload
13 | };
14 | }
15 |
16 | export default function media(state = {}, action = {}) {
17 | switch (action.type) {
18 | case UPDATE_MEDIA_NAME:
19 | return {
20 | ...state,
21 | name: action.payload
22 | };
23 | default:
24 | return state;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/reducers/reference.js:
--------------------------------------------------------------------------------
1 | import isUAFixNeeded from '../utils/isUAFixNeeded';
2 | import fixUserAgent from '../utils/fixUserAgent';
3 | import calcPropWithGutter from '../utils/calcPropWithGutter';
4 | import { ROW_ID, CELL_ID } from '../constants';
5 |
6 | const ROW_ROOT = {
7 | display: 'flex',
8 | flexFlow: 'row wrap',
9 | alignItems: 'stretch'
10 | };
11 |
12 | export const buildRow = (id, FIXED_ROW, gutter, margin) => {
13 | return {
14 | [id]: {
15 | ...FIXED_ROW,
16 | padding: `${margin - (gutter / 2)}px`
17 | }
18 | };
19 | };
20 |
21 | export const buildRowTypeProperties = (justifyContent) => {
22 | return {
23 | start: { [justifyContent]: 'flex-start' },
24 | center: { [justifyContent]: 'center' },
25 | end: { [justifyContent]: 'flex-end' },
26 | around: { [justifyContent]: 'space-around' },
27 | between: { [justifyContent]: 'space-between' }
28 | };
29 | };
30 |
31 | export const buildCell = (id, gutter) => {
32 | return {
33 | [id]: {
34 | boxSizing: 'border-box',
35 | margin: `${gutter / 2}px`,
36 | width: `calc(100% - ${gutter}px)`
37 | }
38 | };
39 | };
40 |
41 | export const buildCellTypeProperties = (alignSelf) => {
42 | return {
43 | top: { [alignSelf]: 'flex-start' },
44 | middle: { [alignSelf]: 'center' },
45 | bottom: { [alignSelf]: 'flex-end' },
46 | stretch: { [alignSelf]: 'stretch' }
47 | };
48 | };
49 |
50 | export const buildSharedProperties = () => {
51 | return {
52 | nospace: { padding: 0, margin: 0 }
53 | };
54 | };
55 |
56 | export function hydrateReference({ options }) {
57 | const { columns, list } = options;
58 | const size = list.length;
59 |
60 | return list.reduce((acc, current, i) => {
61 | const { name, gutter, margin } = current;
62 |
63 | const {
64 | justifyContent,
65 | alignSelf,
66 | FIXED_ROW
67 | } = fixUserAgent(ROW_ROOT, isUAFixNeeded(navigator.userAgent));
68 |
69 | // 4
70 | // 8
71 | // 12
72 | const localColumns = (columns / size) * (i + 1);
73 |
74 | // Define partial sizes for columnNumber < totalColumns.
75 | const partialWidth =
76 | calcPropWithGutter(
77 | [1, localColumns, gutter],
78 | 'width'
79 | );
80 |
81 | // Define sizes = 100% for everything else.
82 | const fullWidth =
83 | calcPropWithGutter(
84 | [localColumns, columns + 1, gutter],
85 | 'width',
86 | true
87 | );
88 |
89 | // Define offset sizes.
90 | const offset =
91 | calcPropWithGutter(
92 | [0, localColumns, gutter / 2],
93 | 'marginLeft'
94 | );
95 |
96 | const row = buildRow(ROW_ID, FIXED_ROW, gutter, margin);
97 | const rowTypeProperties = buildRowTypeProperties(justifyContent);
98 |
99 | const cell = buildCell(CELL_ID, gutter);
100 | const cellTypeProperties = buildCellTypeProperties(alignSelf);
101 |
102 | const sharedProperties = buildSharedProperties();
103 |
104 | return {
105 | ...acc,
106 | [name]: {
107 | ...row,
108 | ...rowTypeProperties,
109 | ...cell,
110 | ...cellTypeProperties,
111 | ...partialWidth,
112 | ...fullWidth,
113 | ...sharedProperties,
114 | offset: { ...offset }
115 | }
116 | };
117 | }, {});
118 | }
119 |
120 | export default function reference(state = {}) {
121 | return state;
122 | }
123 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers } from 'redux';
2 | import media, { hydrateMedia } from './reducers/media';
3 | import reference, { hydrateReference } from './reducers/reference';
4 |
5 | export default function store(initialState) {
6 | return createStore(
7 | combineReducers({ media, reference }),
8 | {
9 | media: hydrateMedia(initialState.media),
10 | reference: hydrateReference(initialState.reference)
11 | }
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/cache.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/src/utils/calcPropWithGutter.js:
--------------------------------------------------------------------------------
1 | import range from 'lodash/utility/range';
2 |
3 | export default function calcPropWithGutter([start, end, gutter], prop, isFull) {
4 | return range(start, end).reduce((acc, n) => {
5 | const width = (isFull) ? 100 : (n / end) * 100;
6 | acc[n] = {
7 | [prop]: `calc(${width}% - ${gutter}px)`
8 | };
9 | return acc;
10 | }, {});
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/capitalize.js:
--------------------------------------------------------------------------------
1 | export default function capitalize(string) {
2 | return string.charAt(0).toUpperCase() + string.slice(1);
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/fixUserAgent.js:
--------------------------------------------------------------------------------
1 | export default function fixUserAgent(rowRoot, needFix) {
2 | const justifyContent =
3 | needFix
4 | ? 'WebkitJustifyContent'
5 | : 'justifyContent';
6 |
7 | const alignSelf =
8 | needFix
9 | ? 'WebkitAlignSelf'
10 | : 'alignSelf';
11 |
12 | const FIXED_ROW =
13 | needFix
14 | ? {
15 | display: '-webkit-flex',
16 | WebkitFlexFlow: 'row wrap',
17 | WebkitAlignItems: 'stretch'
18 | }
19 | : rowRoot;
20 |
21 | return {
22 | justifyContent,
23 | alignSelf,
24 | FIXED_ROW
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/utils/invariant.js:
--------------------------------------------------------------------------------
1 | export default function invariant(condition, error) {
2 | if (!condition) throw new Error(error);
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/isUAFixNeeded.js:
--------------------------------------------------------------------------------
1 | export default function isUAFixNeeded(userAgent) {
2 | return userAgent.indexOf('Chrome') < 0
3 | && userAgent.indexOf('Safari') > -1;
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/memoize.js:
--------------------------------------------------------------------------------
1 | import cache from './cache';
2 |
3 | export default function memoize(callback) {
4 | return function getInMemory(key) {
5 | if (!cache.hasOwnProperty(key)) {
6 | cache[key] = callback.call(this, key);
7 | }
8 | return cache[key];
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/pick.js:
--------------------------------------------------------------------------------
1 | import compact from 'lodash/array/compact';
2 | import getIn from 'lodash/object/get';
3 | import memoize from './memoize';
4 | import invariant from './invariant';
5 | import capitalize from './capitalize';
6 | import { WHITE_LIST } from '../constants';
7 |
8 | export const parser = (initial, input) => {
9 | if (input && input.trim()) {
10 | return [initial, ...input.trim().split(/\s+/)];
11 | }
12 | return [initial];
13 | };
14 |
15 | export const listReducer = (name, list = []) => {
16 | return compact(list.map(n => {
17 | const [ entry, ...value ] = n.split('-');
18 |
19 | switch (value.length) {
20 | case 0:
21 | return entry;
22 | case 1:
23 | if (entry === 'offset') {
24 | return [entry, ...value];
25 | }
26 | if (entry !== name) return false;
27 | return value[0];
28 | case 2:
29 | if (entry !== name) return false;
30 | if (value[0] === 'offset') {
31 | return value;
32 | }
33 | return false;
34 | default:
35 | return false;
36 | }
37 | }));
38 | };
39 |
40 | export const generatePayload = ({ name }, list) => {
41 | return {
42 | name,
43 | list: listReducer(name, list)
44 | };
45 | };
46 |
47 | export const reducePayload = ({ name, list }, reference) => {
48 | return list.reduce((acc, current) => {
49 | const style = getIn(reference, [name, ...current]);
50 | return {
51 | ...acc,
52 | ...style
53 | };
54 | }, {});
55 | };
56 |
57 | export const getInReference = (tag, { media, reference, is }) => {
58 | const list = parser(tag, is);
59 | const payload = generatePayload(media, list);
60 |
61 | if (process.env.NODE_ENV !== 'production') {
62 | payload.list.forEach(n => {
63 | const value = (Array.isArray(n)) ? n[0] : n;
64 | invariant(
65 | WHITE_LIST[tag].indexOf(value) > -1,
66 | `Property '${value}' is not allowed for <${capitalize(tag)}> component.`
67 | );
68 | });
69 | }
70 |
71 | return reducePayload(payload, reference);
72 | };
73 |
74 | export const memoizeProcess =
75 | (...arg) => memoize(() => getInReference(...arg));
76 |
77 | export const generateKey =
78 | (tag, { media: { name }, is }) => `${tag}${name}${is}`;
79 |
80 | export default function pick(...arg) {
81 | const key = generateKey(...arg);
82 |
83 | return memoizeProcess(...arg)(key);
84 | }
85 |
--------------------------------------------------------------------------------
/test/components/Grid.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import {
3 | ensureValue,
4 | ensureListProperties,
5 | build,
6 | setMedia,
7 | setReference
8 | } from '../../src/components/Grid';
9 | import { MEDIA_MODEL_HELPER } from '../../src/constants';
10 |
11 | function buildModelFrom(options) {
12 | const { list } = options;
13 | return {
14 | columns: list.length * 4,
15 | deaf: false,
16 | list: ensureListProperties(undefined, options, list)
17 | };
18 | }
19 |
20 | describe('Components', () => {
21 | describe('Grid helpers', () => {
22 | describe('ensureValue(options, base, key, value)', () => {
23 | it('should return ensureValue -> value if valid and provided', () => {
24 | const v = ensureValue(undefined, { a: 12 }, 'a', 0);
25 | const expected = 0;
26 | expect(v).toEqual(expected);
27 | });
28 |
29 | it('should return ensureValue -> options -> key if valid and provided and if ensureValue -> value is invalid or missing', () => {
30 | const v = ensureValue({ a: 0 }, { a: 12 }, 'a', undefined);
31 | const expected = 0;
32 | expect(v).toEqual(expected);
33 | });
34 |
35 | it('should return ensureValue -> base -> key if ensureValue -> value and ensureValue -> options -> key are invalid or missing', () => {
36 | const v = ensureValue(undefined, { a: 12 }, 'a', undefined);
37 | const expected = 12;
38 | expect(v).toEqual(expected);
39 | });
40 |
41 | it('should throw if ensureValue -> key is missing', () => {
42 | expect(() => {
43 | ensureValue(undefined, undefined, undefined);
44 | }).toThrow(
45 | `Property 'key' of ensureValue() must be defined.`
46 | );
47 | });
48 |
49 | it('should throw if ensureValue -> base is missing', () => {
50 | expect(() => {
51 | ensureValue(undefined, undefined);
52 | }).toThrow(
53 | `Property 'base' of ensureValue() must be defined.`
54 | );
55 | });
56 |
57 | it('should throw if ensureValue -> key is not defined in ensureValue -> base -> key', () => {
58 | const key = 'a';
59 | expect(() => {
60 | ensureValue(undefined, { [key]: undefined }, key);
61 | }).toThrow(
62 | `Property '${key}' of ensureValue() -> base must be defined.`
63 | );
64 | });
65 | });
66 |
67 | describe('ensureListProperties(options, base, list)', () => {
68 | it('should return a new Model -> list from ensureListProperties -> list', () => {
69 | const list = [ { name: 'a', query: 'b' } ];
70 | const v = ensureListProperties(undefined, MEDIA_MODEL_HELPER, list);
71 | const expected = [{
72 | name: 'a',
73 | query: 'b',
74 | gutter: MEDIA_MODEL_HELPER.gutter,
75 | margin: MEDIA_MODEL_HELPER.margin
76 | }];
77 | expect(v).toEqual(expected);
78 | });
79 | });
80 |
81 | describe('build(options, base)', () => {
82 | it('should handle missing build -> options properties', () => {
83 | const v = build({}, MEDIA_MODEL_HELPER);
84 | const expected = buildModelFrom(MEDIA_MODEL_HELPER);
85 | expect(v).toEqual(expected);
86 | });
87 |
88 | it('should throw if build -> options -> list is defined and empty', () => {
89 | expect(() => {
90 | build({ list: [] }, MEDIA_MODEL_HELPER);
91 | }).toThrow(
92 | `Property 'list' of -> 'options' can not be empty`
93 | );
94 | });
95 |
96 | it('should throw if build -> options -> columns is defined and wrong', () => {
97 | expect(() => {
98 | build({ columns: '11', list: ['a', 'b', 'c'] }, MEDIA_MODEL_HELPER);
99 | }).toThrow(
100 | `Property 'columns' of -> 'options' must be a multiple of ` +
101 | ` -> 'options' -> 'list' -> length.`
102 | );
103 | });
104 | });
105 |
106 | describe('setMedia(name)', () => {
107 | it('should return a valid object', () => {
108 | const v = setMedia('a');
109 | const expected = { name: 'a' };
110 | expect(v).toEqual(expected);
111 | });
112 | });
113 |
114 | describe('setReference(options)', () => {
115 | it('should return a valid object', () => {
116 | const v = setReference('a');
117 | const expected = { options: 'a' };
118 | expect(v).toEqual(expected);
119 | });
120 | });
121 | });
122 | });
123 |
--------------------------------------------------------------------------------
/test/reducers/media.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import media, { updateMediaName, hydrateMedia } from '../../src/reducers/media';
3 | import { UPDATE_MEDIA_NAME } from '../../src/constants';
4 |
5 | describe('Reducers', () => {
6 | describe('media helpers', () => {
7 | it('should hydrateMedia', () => {
8 | const v = { name: 'a' };
9 | const expected = { name: 'a' };
10 | expect(hydrateMedia(v)).toEqual(expected);
11 | });
12 |
13 | it('should generate an action to update media name', () => {
14 | const v = 'a';
15 | const expected = {
16 | type: UPDATE_MEDIA_NAME,
17 | payload: v
18 | };
19 | expect(updateMediaName(v)).toEqual(expected);
20 | });
21 | });
22 |
23 | describe('media', () => {
24 | it('should return the initial state', () => {
25 | expect(
26 | media(undefined, undefined)
27 | ).toEqual({});
28 | });
29 |
30 | it('should handle UPDATE_MEDIA_NAME', () => {
31 | expect(
32 | media({ name: 'a' }, {
33 | type: UPDATE_MEDIA_NAME,
34 | payload: 'b'
35 | })
36 | ).toEqual({ name: 'b' });
37 | });
38 |
39 | it('should handle wrong action.type', () => {
40 | expect(
41 | media({ name: 'a' }, {
42 | type: 'fail',
43 | payload: 'b'
44 | })
45 | ).toEqual({ name: 'a' });
46 | });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/test/reducers/reference.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import reference, {
3 | buildRow,
4 | buildRowTypeProperties,
5 | buildCell,
6 | buildCellTypeProperties,
7 | sharedProperties
8 | } from '../../src/reducers/reference';
9 |
10 | describe('Reducers', () => {
11 | describe('reference helpers', () => {
12 | describe('buildRow(ROW_ID, FIXED_ROW, gutter, margin)', () => {
13 | it('should build a new row properties object', () => {
14 | const v = ['a', { b: 'B' }, 8, 16];
15 | const expected = {
16 | a: {
17 | b: 'B',
18 | padding: `${16 - (8 / 2)}px`
19 | }
20 | };
21 | expect(buildRow(...v)).toEqual(expected);
22 | });
23 | });
24 |
25 | describe('buildRowTypeProperties(justifyContent)', () => {
26 | it('should build new row type properties objects', () => {
27 | const v = ['a'];
28 | const expected = {
29 | start: { a: 'flex-start' },
30 | center: { a: 'center' },
31 | end: { a: 'flex-end' },
32 | around: { a: 'space-around' },
33 | between: { a: 'space-between' }
34 | };
35 | expect(buildRowTypeProperties(...v)).toEqual(expected);
36 | });
37 | });
38 |
39 | describe('buildCell(id, gutter)', () => {
40 | it('should build a new cell properties object', () => {
41 | const v = ['a', 8];
42 | const expected = {
43 | a: {
44 | boxSizing: 'border-box',
45 | margin: `${8 / 2}px`,
46 | width: `calc(100% - ${8}px)`
47 | }
48 | };
49 | expect(buildCell(...v)).toEqual(expected);
50 | });
51 | });
52 |
53 | describe('buildCellTypeProperties(alignSelf)', () => {
54 | it('should build new cell type properties objects', () => {
55 | const v = ['a'];
56 | const expected = {
57 | top: { a: 'flex-start' },
58 | middle: { a: 'center' },
59 | bottom: { a: 'flex-end' },
60 | stretch: { a: 'stretch' }
61 | };
62 | expect(buildCellTypeProperties(...v)).toEqual(expected);
63 | });
64 | });
65 |
66 | describe('sharedProperties()', () => {
67 | it('should build a new shared properties object', () => {
68 | const expected = {
69 | nospace: { padding: 0, margin: 0 }
70 | };
71 | expect(sharedProperties()).toEqual(expected);
72 | });
73 | });
74 | });
75 |
76 | describe('reference', () => {
77 | it('should return the initial state', () => {
78 | expect(
79 | reference(undefined, undefined)
80 | ).toEqual({});
81 | });
82 |
83 | it('should handle wrong action.type', () => {
84 | expect(
85 | reference({ name: 'a' }, {
86 | type: 'fail',
87 | payload: 'b'
88 | })
89 | ).toEqual({ name: 'a' });
90 | });
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/test/utils/cache.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import cache from '../../src/utils/cache';
3 |
4 | describe('Utils', () => {
5 | describe('cache', () => {
6 | it(`should be an empty object`, () => {
7 | expect(cache).toEqual({});
8 | });
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/test/utils/calcPropWithGutter.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import calcPropWithGutter from '../../src/utils/calcPropWithGutter';
3 |
4 | describe('Utils', () => {
5 | describe('calcPropWithGutter([start, end, gutter], prop, isFull)', () => {
6 | it(`should generate partial style property if calcPropWithGutter -> isFull is missing or not true`, () => {
7 | const v = calcPropWithGutter([1, 2, 20], 'a');
8 | expect(v).toEqual({ '1': { a: 'calc(50% - 20px)' } });
9 | });
10 |
11 | it(`should generate full style property if calcPropWithGutter -> isFull is true`, () => {
12 | const v = calcPropWithGutter([1, 2, 20], 'a', true);
13 | expect(v).toEqual({ '1': { a: 'calc(100% - 20px)' } });
14 | });
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/test/utils/capitalize.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import capitalize from '../../src/utils/capitalize';
3 |
4 | describe('Utils', () => {
5 | describe('capitalize(string)', () => {
6 | it(`should capitalize capitalize -> string`, () => {
7 | const v = capitalize('abc def');
8 | expect(v).toEqual('Abc def');
9 | });
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/test/utils/fixUserAgent.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import fixUserAgent from '../../src/utils/fixUserAgent';
3 |
4 | const base = {
5 | alignSelf: 'alignSelf',
6 | justifyContent: 'justifyContent',
7 | row: {
8 | display: 'flex',
9 | flexFlow: 'row wrap',
10 | alignItems: 'stretch'
11 | }
12 | };
13 |
14 | const resultNotFixed = {
15 | alignSelf: 'alignSelf',
16 | justifyContent: 'justifyContent',
17 | FIXED_ROW: {
18 | display: 'flex',
19 | flexFlow: 'row wrap',
20 | alignItems: 'stretch'
21 | }
22 | };
23 |
24 | const resultFixed = {
25 | alignSelf: 'WebkitAlignSelf',
26 | justifyContent: 'WebkitJustifyContent',
27 | FIXED_ROW: {
28 | display: '-webkit-flex',
29 | WebkitFlexFlow: 'row wrap',
30 | WebkitAlignItems: 'stretch'
31 | }
32 | };
33 |
34 | describe('Utils', () => {
35 | describe('fixUserAgent(rowRoot, needFix)', () => {
36 | it(`should not fix if fixUserAgent -> needFix is missing or not true`, () => {
37 | const v = fixUserAgent(base.row, false);
38 | expect(v).toEqual(resultNotFixed);
39 | });
40 |
41 | it(`should fix if fixUserAgent -> needFix is true`, () => {
42 | const v = fixUserAgent(base.row, true);
43 | expect(v).toEqual(resultFixed);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/utils/invariant.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import invariant from '../../src/utils/invariant';
3 |
4 | describe('Utils', () => {
5 | describe('invariant(condition, error)', () => {
6 | it(`should throw invariant -> error if !invariant -> condition`, () => {
7 | expect(() => {
8 | invariant(false, 'error text');
9 | }).toThrow('error text');
10 | });
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/test/utils/isUAFixNeeded.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import isUAFixNeeded from '../../src/utils/isUAFixNeeded';
3 |
4 | describe('Utils', () => {
5 | describe('isUAFixNeeded(userAgent)', () => {
6 | it(`not needed`, () => {
7 | const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36';
8 | const v = isUAFixNeeded(UA);
9 | expect(v).toBe(false);
10 | });
11 |
12 | it(`needed`, () => {
13 | const UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4';
14 | const v = isUAFixNeeded(UA);
15 | expect(v).toBe(true);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/test/utils/pick.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import {
3 | parser,
4 | listReducer,
5 | generatePayload,
6 | reducePayload,
7 | generateKey
8 | } from '../../src/utils/pick';
9 |
10 | describe('Utils', () => {
11 | describe('pick helpers', () => {
12 | describe('parser(initial, input)', () => {
13 | it('should return an array of parser -> initial if parser -> input is missing or invalid', () => {
14 | const v = parser('a');
15 | expect(v).toEqual(['a']);
16 |
17 | const v2 = parser('a', ' ');
18 | expect(v2).toEqual(['a']);
19 | });
20 |
21 | it('should handle parser -> input patterns', () => {
22 | const v1 = parser('a', 'prop-1');
23 | expect(v1).toEqual(['a', 'prop-1']);
24 |
25 | const v2 = parser('a', 'prop-1 prop-2');
26 | expect(v2).toEqual(['a', 'prop-1', 'prop-2']);
27 |
28 | const v3 = parser('a', ' prop-1 prop-2 ');
29 | expect(v3).toEqual(['a', 'prop-1', 'prop-2']);
30 | });
31 | });
32 |
33 | describe('listReducer(name, list = [])', () => {
34 | it('should handle empty listReducer -> list', () => {
35 | const v = listReducer();
36 | const expected = [];
37 | expect(v).toEqual(expected);
38 | });
39 |
40 | it('should handle global listReducer -> list -> value', () => {
41 | const v = listReducer(undefined, ['a', 'b', 'c']);
42 | const expected = ['a', 'b', 'c'];
43 | expect(v).toEqual(expected);
44 | });
45 |
46 | it('should handle global named listReducer -> list -> value', () => {
47 | const v = listReducer(undefined, ['a', 'b', 'offset-c']);
48 | const expected = ['a', 'b', ['offset', 'c']];
49 | expect(v).toEqual(expected);
50 | });
51 |
52 | it('should ignore non matching listReducer -> list -> value', () => {
53 | const v = listReducer('phone', ['a', 'b', 'fail-c']);
54 | const expected = ['a', 'b'];
55 | expect(v).toEqual(expected);
56 | });
57 |
58 | it('should keep matching listReducer -> list -> value', () => {
59 | const v = listReducer('phone', ['a', 'b', 'phone-c']);
60 | const expected = ['a', 'b', 'c'];
61 | expect(v).toEqual(expected);
62 | });
63 |
64 | it('should ignore non matching named listReducer -> list -> value', () => {
65 | const v = listReducer('phone', ['a', 'b', 'fail-offset-c']);
66 | const expected = ['a', 'b'];
67 | expect(v).toEqual(expected);
68 | });
69 |
70 | it('should keep matching named listReducer -> list -> value', () => {
71 | const v = listReducer('phone', ['a', 'b', 'phone-offset-c']);
72 | const expected = ['a', 'b', ['offset', 'c']];
73 | expect(v).toEqual(expected);
74 | });
75 |
76 | it('should ignore matching invalid named listReducer -> list -> value', () => {
77 | const v = listReducer('phone', ['a', 'b', 'phone-fail-c']);
78 | const expected = ['a', 'b'];
79 | expect(v).toEqual(expected);
80 | });
81 |
82 | it('should ignore other invalid listReducer -> list -> value', () => {
83 | const v = listReducer('phone', ['a', 'b', 'phone-offset-c-fail']);
84 | const expected = ['a', 'b'];
85 | expect(v).toEqual(expected);
86 | });
87 | });
88 |
89 | describe('generatePayload({ name }, list)', () => {
90 | it('should keep generatePayload -> name and parse generatePayload -> list', () => {
91 | const v = generatePayload({ name: 'a' });
92 | const expected = {
93 | name: 'a',
94 | list: []
95 | };
96 | expect(v).toEqual(expected);
97 | });
98 | });
99 |
100 | describe('reducePayload({ name, list }, reference)', () => {
101 | it('should keep generatePayload -> name and parse generatePayload -> list', () => {
102 | const reference = {
103 | phone: {
104 | a: { propA: 'A' },
105 | b: { propB: 'B' },
106 | c: { inner: { propC: 'C' } }
107 | }
108 | };
109 | const payload = {
110 | name: 'phone',
111 | list: ['a', 'b', ['c', 'inner']]
112 | };
113 | const v = reducePayload(payload, reference);
114 | const expected = {
115 | propA: 'A',
116 | propB: 'B',
117 | propC: 'C'
118 | };
119 | expect(v).toEqual(expected);
120 | });
121 | });
122 |
123 | describe('generateKey(tag, { media: { name }, is })', () => {
124 | it('should generate a key', () => {
125 | const v = generateKey('a', { media: { name: 'b' }, is: 'c' });
126 | const expected = 'abc';
127 | expect(v).toEqual(expected);
128 | });
129 | });
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var path = require('path');
5 | var webpack = require('webpack');
6 |
7 | var reactExternal = {
8 | root: 'React',
9 | commonjs2: 'react',
10 | commonjs: 'react',
11 | amd: 'react'
12 | };
13 |
14 | var reduxExternal = {
15 | root: 'Redux',
16 | commonjs2: 'redux',
17 | commonjs: 'redux',
18 | amd: 'redux'
19 | };
20 |
21 | module.exports = {
22 | externals: {
23 | 'react': reactExternal,
24 | 'redux': reduxExternal
25 | },
26 | module: {
27 | loaders: [
28 | {
29 | test: /\.js$/,
30 | loaders: ['babel-loader'],
31 | include: [
32 | path.resolve(__dirname, 'src'),
33 | path.resolve(__dirname, 'node_modules', 'lodash')
34 | ]
35 | }
36 | ]
37 | },
38 | node: {
39 | process: false
40 | },
41 | output: {
42 | library: 'ReactInlineGrid',
43 | libraryTarget: 'umd'
44 | },
45 | plugins: [
46 | new webpack.optimize.OccurenceOrderPlugin()
47 | ]
48 | };
49 |
--------------------------------------------------------------------------------
/webpack.config.development.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var webpack = require('webpack');
5 | var baseConfig = require('./webpack.config.base');
6 |
7 | module.exports = _.merge({}, baseConfig, {
8 | plugins: _.union(baseConfig.plugins, [
9 | new webpack.DefinePlugin({
10 | '__DEV__': 'true',
11 | 'process.env.NODE_ENV': '"development"'
12 | })
13 | ])
14 | });
15 |
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var webpack = require('webpack');
5 | var baseConfig = require('./webpack.config.base');
6 |
7 | module.exports = _.merge({}, baseConfig, {
8 | plugins: _.union(baseConfig.plugins, [
9 | new webpack.DefinePlugin({
10 | '__DEV__': 'false',
11 | 'process.env.NODE_ENV': '"production"'
12 | }),
13 | new webpack.optimize.UglifyJsPlugin({
14 | compressor: {
15 | pure_getters: true,
16 | unsafe: true,
17 | unsafe_comps: true,
18 | screw_ie8: true,
19 | warnings: false
20 | }
21 | })
22 | ])
23 | });
24 |
--------------------------------------------------------------------------------
| | | | | |