├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── package.json ├── src │ ├── index.html │ ├── index.js │ └── pages │ │ ├── GridTestPage │ │ ├── GridTestPage.jsx │ │ ├── index.js │ │ └── styles.css │ │ └── index.js └── webpack.config.js ├── package.json ├── scripts └── copy-files.js └── src ├── __tests__ ├── components │ ├── Col.spec.jsx │ ├── Grid.spec.jsx │ ├── Row.spec.jsx │ └── index.spec.js ├── index.spec.js └── shared │ └── utils.spec.js ├── components ├── Col.jsx ├── Grid.jsx ├── Row.jsx └── index.js ├── index.js └── shared └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "env": { 5 | "production": { 6 | "ignore": ["src/__tests__", "src/app"] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.md] 7 | trim_trailing_whitespace = false 8 | 9 | [*.js] 10 | trim_trailing_whitespace = true 11 | 12 | # Unix-style newlines with a newline ending every file 13 | [*] 14 | indent_style = tab 15 | end_of_line = lf 16 | charset = utf-8 17 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | we use the airbnb guidelines as the basis for our rules (see: http://airbnb.io/javascript/) 4 | 5 | current set of overrides: 6 | 7 | - linebreak-style is being disabled because of our mixed environments of unix and windows 8 | - we prefer tabbed indentation since it is configurable in most editors. the following rules allow us to use tabs for indentation: 9 | 10 | - indent -> tab 11 | - no-tabs -> off 12 | - react/jsx-indent -> tab 13 | - react/jsx-indent -> tab 14 | 15 | - the SwitchCase option for indent allows for indentation of cases in switchblocks 16 | 17 | - when using a constant value that was injected from .env, add the constant to the "globals" section in this format: 18 | "__CONSTANT_NAME__": false 19 | */ 20 | { 21 | "extends": "airbnb", 22 | "env": { 23 | "browser": true, 24 | "commonjs": true, 25 | "es6": true, 26 | "node": true, 27 | "jest": true 28 | }, 29 | "globals": { 30 | }, 31 | "parser": "babel-eslint", 32 | "parserOptions": { 33 | "ecmaFeatures": { 34 | "jsx": true 35 | }, 36 | "sourceType": "module" 37 | }, 38 | "rules": { 39 | "linebreak-style": 0, 40 | "indent": ["error", "tab", {"SwitchCase": 1}], 41 | "no-tabs": "off", 42 | "react/jsx-indent": ["error", "tab"], 43 | "react/jsx-indent-props": ["error", "tab"] 44 | }, 45 | "plugins": [ 46 | "react", 47 | "jsx-a11y" 48 | ], 49 | "settings": { 50 | "import/resolver": { 51 | "webpack": { 52 | "config": "./webpack.config.js" 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | .env 4 | .vscode 5 | debug.log 6 | npm-debug.log 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | HEAD 2 | 3 | ## [1.1.0] - 2017-03-24 4 | ### Added 5 | - Support for material-responsive-grid version 1.1.0 6 | - Grid now has marginless boolean property 7 | - Marginless example added to example app 8 | 9 | ### Changed 10 | - Grid's fixed property corresponds with new .fixed-left and .fixed-center modifiers for .grid 11 | - Grid's fixed property now uses an enumeration instead of string for propTypes 12 | - Test for Grid now validates functionality of marginless property 13 | 14 | ### Fixed 15 | - Classnames applied to Grid, Row, or Col will be placed last so that the consumer of this library can override any styles they see fit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 STORIS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Material Responsive Grid # 2 | 3 | A set of React components implementing the [material-responsive-grid](https://github.com/STORIS/material-responsive-grid) CSS framework. 4 | 5 | ## Installation ## 6 | 7 | ``` 8 | npm i react-material-responsive-grid --save 9 | ``` 10 | 11 | ## Components ## 12 | 13 | ### Grid ### 14 | 15 | Container for Row components, intended as a layout 16 | 17 | #### Features #### 18 | 19 | - Adheres to [Material Design responsive UI](https://material.io/guidelines/layout/responsive-ui.html#responsive-ui-breakpoints) standards for screens at or exceeding 1600 px 20 | - Fluid by default, allows the Grid to continue 21 | - Margin by default, preserves the outer padding for outer columns, preventing a row of content from spanning edge to edge 22 | - Optionally fixed width for viewports larger than 1599 px, centered or left aligned 23 | - Optionally marginless, allow row content to span from edge to edge 24 | 25 | #### Properties #### 26 | 27 | Name | Default | Value | Description 28 | ---------- | ------- | ---------------------- | ----------------------------------------------------------------------------- 29 | fixed | | { *left* or *center* } | Grid is fixed width and aligned as specified for viewports larger than 1599px 30 | marginless | *false* | Boolean | Grid has no margin, row content can span edge to edge 31 | tagName | *div* | | The type of tag to use when rendering this element 32 | className | | | The CSS class name of this element 33 | 34 | 35 | ### Row ### 36 | 37 | Container for Col components, implemented with flexbox 38 | 39 | #### Features #### 40 | 41 | - Reverse the flow direction of contained Col components 42 | - Horizontal alignment based on flow direction, *start*, *center*, or *end* 43 | - Vertically align contained Col components, *top*, *middle*, *bottom* 44 | - Evenly distribute unconsumed space *around* or *between* contained Col components 45 | 46 | #### Properties #### 47 | 48 | Name | Default | Value | Description 49 | --------- | ------- | --------------------- | ----------------------------------------------------------------------------- 50 | reverse | *false* | Boolean | Reverse direction of this row 51 | start | | Array of sizes | Justify content to the start for the specified sizes (based on direction) 52 | center | | Array of sizes | Center content within this row for the specified sizes 53 | end | | Array of sizes | Justify content to the end for the specified sizes (based on direction) 54 | top | | Array of sizes | Vertically align content to top for the specified sizes 55 | middle | | Array of sizes | Vertically align content to middle for the specified sizes 56 | bottom | | Array of sizes | Vertically align content to bottom for the specified sizes 57 | around | | Array of sizes | Evenly distribute unused space around columns for the specified sizes 58 | between | | Array of sizes | Evenly distribute unused space between columns for the specified sizes 59 | tagName | *div* | | The type of tag to use when rendering this element 60 | className | | | The CSS class name of this element 61 | 62 | 63 | ### Col ### 64 | 65 | Responsively sized, positioned, and visible component contained by a Row 66 | 67 | #### Features #### 68 | 69 | - Responsive width expressed as columns consumed 70 | - Offset expressed as columns skipped before consuming columns 71 | - Responsively hide for any configuration of screen sizes 72 | - Force to beginning or end of Row 73 | 74 | #### Properties #### 75 | 76 | Name | Default | Value | Description 77 | ---------- | ------- | --------------------- | ---------------------------------------------------------------------------- 78 | xs4 | | Integer, 1-4 | Number of columns to consume on extra-small (4 column) viewports 79 | xs8 | | Integer, 1-8 | Number of columns to consume on extra-small (8 column) viewports 80 | sm8 | | Integer, 1-8 | Number of columns to consume on small (8 column) viewports 81 | sm12 | | Integer, 1-12 | Number of columns to consume on small (12 column) viewports 82 | sm | | Integer, 1-12 | Shorthand for *sm12* 83 | md12 | | Integer, 1-12 | Number of columns to consume on medium (12 column) viewports 84 | md | | Integer, 1-12 | Shorthand for *md12* 85 | lg12 | | Integer, 1-12 | Number of columns to consume on large (12 column) viewports 86 | lg | | Integer, 1-12 | Shorthand for *lg12* 87 | xl12 | | Integer, 1-12 | Number of columns to consume on extra-large (12 column) viewports 88 | xl | | Integer, 1-12 | Shorthand for *xl12* 89 | xs4Offset | | Integer, 0-3 | Number of columns to offset on extra-small (4 column) viewports 90 | xs8Offset | | Integer, 0-7 | Number of columns to offset on extra-small (8 column) viewports 91 | sm8Offset | | Integer, 0-7 | Number of columns to offset on small (8 column) viewports 92 | sm12Offset | | Integer, 0-11 | Number of columns to offset on small (12 column) viewports 93 | smOffset | | Integer, 0-11 | Shorthand for *sm12Offset* 94 | md12Offset | | Integer, 0-11 | Number of columns to offset on medium (12 column) viewports 95 | mdOffset | | Integer, 0-11 | Shorthand for *md12Offset* 96 | lg12Offset | | Integer, 0-11 | Number of columns to offset on large (12 column) viewports 97 | lgOffset | | Integer, 0-11 | Shorthand for *lg12Offset* 98 | xl12Offset | | Integer, 0-11 | Number of columns to offset on extra-large (12 column) viewports 99 | xlOffset | | Integer, 0-11 | Shorthand for *xl12Offset* 100 | first | | Array of sizes | Present this column first for the specified sizes (based on row direction) 101 | last | | Array of sizes | Present this column last for the specified sizes (based on row direction) 102 | hiddenOnly | | Array of sizes | Hide this column for the specified sizes 103 | hiddenDown | | Size | Hide this column for sizes equal to or smaller than the specified size 104 | hiddenUp | | Size | Hide this column for sizes equal to or larger than the specified size 105 | tagName | *div* | | The type of tag to use when rendering this element 106 | className | | | The CSS class name of this element 107 | 108 | ## Example ## 109 | 110 | ```jsx 111 | import React from 'react'; 112 | import { Grid, Row, Col } from 'react-material-responsive-grid'; 113 | 114 | class App extends React.Component { 115 | render() { 116 | return ( 117 | 118 | 119 | 120 |

This column consumes the entire row for extra-small, 121 | small, and medium screens. For large and extra-large 122 | screens, it consumes half of the row.

123 | 124 | 125 |

This column isn't visible for extra-small, small, 126 | and medium screens, but is visible for large and 127 | extra-large screens. It consumes half of the row.

128 | 129 | 130 |

This column is only visible for medium and large 131 | screens and consumes the entire row.

132 | 133 | 134 |

This column is hidden for small and large screens 135 | and consumes the entire row.

136 | 137 |
138 |
139 | ); 140 | } 141 | } 142 | ``` 143 | 144 | ## Inspiration ## 145 | 146 | - [react-flexbox-grid](https://github.com/roylee0704/react-flexbox-grid) 147 | - [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap) 148 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-material-responsive-grid-examples", 3 | "private": true, 4 | "version": "1.2.2", 5 | "description": "A set of examples for react-material-responsive-grid", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "cross-env NODE_ENV=development webpack-dev-server", 9 | "lint": "eslint --ignore-path ../.eslintignore -- ./src/. ", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [ 13 | "boilerplate", 14 | "eslint", 15 | "webpack", 16 | "react", 17 | "redux" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/STORIS/react-material-responsive-grid.git" 22 | }, 23 | "author": "Ken Gregory", 24 | "license": "ISC", 25 | "homepage": "https://github.com/STORIS/react-material-responsive-grid", 26 | "devDependencies": { 27 | "babel-core": "^6.22.1", 28 | "babel-eslint": "^7.1.1", 29 | "babel-loader": "^6.2.10", 30 | "babel-plugin-transform-runtime": "^6.23.0", 31 | "babel-preset-env": "^1.2.0", 32 | "babel-preset-react": "^6.22.0", 33 | "babel-preset-stage-2": "^6.22.0", 34 | "cross-env": "^3.1.4", 35 | "css-loader": "^0.26.1", 36 | "eslint": "^3.14.0", 37 | "eslint-config-airbnb": "^14.0.0", 38 | "eslint-import-resolver-webpack": "^0.8.1", 39 | "eslint-plugin-import": "^2.2.0", 40 | "eslint-plugin-jsx-a11y": "^3.0.2", 41 | "eslint-plugin-react": "^6.9.0", 42 | "extract-text-webpack-plugin": "^2.0.0-rc.3", 43 | "html-webpack-plugin": "^2.26.0", 44 | "postcss-cssnext": "^2.9.0", 45 | "postcss-import": "^9.1.0", 46 | "postcss-loader": "^1.2.2", 47 | "react-hot-loader": "^3.1.3", 48 | "style-loader": "^0.13.1", 49 | "webpack": "^2.2.1", 50 | "webpack-combine-loaders": "^2.0.3", 51 | "webpack-dev-server": "^2.3.0" 52 | }, 53 | "dependencies": { 54 | "babel-runtime": "^6.23.0", 55 | "normalize.css": "^7.0.0", 56 | "react": "^16.1.0", 57 | "react-dom": "^16.1.0", 58 | "webfontloader": "^1.6.27" 59 | }, 60 | "babel": { 61 | "presets": [ 62 | "env", 63 | "react", 64 | "stage-2" 65 | ], 66 | "plugins": ["transform-runtime"], 67 | "env": { 68 | "development": { 69 | "presets": [] 70 | }, 71 | "production": { 72 | "presets": [] 73 | }, 74 | "test": { 75 | "presets": [] 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Material Responsive Grid 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import WebFont from 'webfontloader'; 4 | 5 | /* eslint-disable no-unused-vars */ 6 | import normalize from 'normalize.css'; 7 | 8 | import GridTestPage from './pages/GridTestPage'; 9 | 10 | const app = () => { 11 | if (process.env.NODE_ENV === 'development') { 12 | // attach react dev tools to window 13 | if (typeof window !== 'undefined') { 14 | window.React = React; 15 | } 16 | } 17 | 18 | /* eslint-disable react/jsx-filename-extension */ 19 | render( 20 | , 21 | document.querySelector('#main'), 22 | ); 23 | }; 24 | 25 | // postpone initiation of app until fonts are active (for text components that require measurement) 26 | const webFontConfig = { 27 | google: { 28 | families: ['Roboto'], 29 | }, 30 | classes: false, 31 | timeout: 1000, 32 | active: app, 33 | }; 34 | 35 | WebFont.load(webFontConfig); 36 | -------------------------------------------------------------------------------- /examples/src/pages/GridTestPage/GridTestPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid, Row, Col } from '../../../../src/'; 3 | import styles from './styles.css'; 4 | 5 | const GridTestPage = () => ( 6 | 7 |

Material Responsive Grid (Components)

8 |

This project uses react-material-responsive-grid to define responsive layouts. This set of React components implements the material-responsive-grid CSS framework.

9 | 10 |
11 |

Lowest Specified Viewport Size

12 |

Sizes specified at lower viewport sizes carry forward, unless overridden.

13 | 14 | 15 |
Full
16 | 17 |
18 | 19 | 20 |
Quarter
21 | 22 | 23 |
Three-quarters
24 | 25 |
26 | 27 | 28 |
Half
29 | 30 | 31 |
Half
32 | 33 |
34 | 35 | 36 |
Three-quarters
37 | 38 | 39 |
Quarter
40 | 41 |
42 | 43 | 44 |
Full
45 | 46 |
47 |
{`  
 48 |      
 49 |         Full
 50 |      
 51 |      
 52 |         Quarter
 53 |         Three-quarters
 54 |      
 55 |      
 56 |         Half
 57 |         Half
 58 |      
 59 |      
 60 |         Three-quarters
 61 |         Quarter
 62 |      
 63 |      
 64 |         Full
 65 |      
 66 |   `}
67 |
68 | 69 |
70 |

Responsively Sized Columns

71 |

Three columns that are sized differently for various viewport sizes

72 | 73 | 74 |
1st
75 | 76 | 77 |
2nd
78 | 79 | 80 |
3rd
81 | 82 |
83 |
{`  
 84 |      
 85 |         
 86 |            1st
 87 |         
 88 |         
 89 |            2nd
 90 |         
 91 |         
 92 |            3rd
 93 |         
 94 |      
 95 |   `}
96 |
97 | 98 |
99 |

Offset Columns

100 |

101 | Columns that are positioned by specifying an offset, which skips the 102 | specified number of columns 103 |

104 | 105 | 106 |
1st
107 | 108 |
109 | 110 | 111 |
2nd
112 | 113 |
114 | 115 | 116 |
3rd
117 | 118 |
119 | 120 | 121 |
4th
122 | 123 |
124 |
{`  
125 |      
126 |         1st
127 |      
128 |      
129 |         2nd
130 |      
131 |      
132 |         3rd
133 |      
134 |      
135 |         4th
136 |      
137 |   `}
138 |
139 | 140 |
141 |

Horizontal Alignment

142 |

Align columns within a row horizontally

143 | 144 | 145 |
Start 1
146 | 147 | 148 |
Start 2
149 | 150 |
151 | 152 | 153 |
Center 1
154 | 155 | 156 |
Center 2
157 | 158 |
159 | 160 | 161 |
End 1
162 | 163 | 164 |
End 2
165 | 166 |
167 |
{`  
168 |      
169 |         Start 1
170 |         Start 2
171 |      
172 |      
173 |         Center 1
174 |         Center 2
175 |      
176 |      
177 |         End 1
178 |         End 2
179 |      
180 |   `}
181 |
182 | 183 |
184 |

Vertical Alignment

185 |

Align columns within a row vertically

186 | 187 | 188 |
189 | 190 | 191 |
Top 1
192 | 193 | 194 |
Top 2
195 | 196 | 197 | 198 | 199 |
Middle 1
200 | 201 | 202 |
203 | 204 | 205 |
Middle 2
206 | 207 | 208 | 209 | 210 |
Bottom 1
211 | 212 | 213 |
Bottom 2
214 | 215 | 216 | 217 |
218 | 219 | 220 |
{`  
221 |      
222 |         
223 |            Tall!
224 | 225 | Top 1 226 | Top 2 227 |
228 | 229 | Middle 1 230 | 231 | Tall!
232 | 233 | Middle 2 234 |
235 | 236 | Bottom 1 237 | Bottom 2 238 | 239 | Tall!
240 | 241 |
242 |
`}
243 |
244 | 245 |
246 |

Reorder Columns

247 |

248 | Columns can become the first or last in the row. Column position is based 249 | on the Row's flow direction, which can be reversed. 250 |

251 | 252 | 253 |
1
254 | 255 | 256 |
2
257 | 258 | 259 |
3
260 | 261 | 262 |
4
263 | 264 | 265 |
5
266 | 267 | 268 |
6
269 | 270 |
271 | 272 | 273 |
1
274 | 275 | 276 |
2
277 | 278 | 279 |
3
280 | 281 | 282 |
4
283 | 284 | 285 |
5
286 | 287 | 288 |
6
289 | 290 |
291 | 292 | 293 |
1
294 | 295 | 296 |
2
297 | 298 | 299 |
3
300 | 301 | 302 |
4
303 | 304 | 305 |
5
306 | 307 | 308 |
6
309 | 310 |
311 |
{`  
312 |      
313 |         1
314 |         2
315 |         3
316 |         4
317 |         5
318 |         6
319 |      
320 |      
321 |         1
322 |         2
323 |         3
324 |         4
325 |         5
326 |         6
327 |      
328 |      
329 |         1
330 |         2
331 |         3
332 |         4
333 |         5
334 |         6
335 |      
336 |   `}
337 |
338 | 339 |
340 |

Reversing a Row

341 |

342 | Rows can be reversed. This changes the flow direction, which affects columns 343 | that have been changed to be first or last. 344 |

345 | 346 | 347 |
1
348 | 349 | 350 |
2
351 | 352 | 353 |
3
354 | 355 | 356 |
4
357 | 358 | 359 |
5
360 | 361 | 362 |
6
363 | 364 |
365 | 366 | 367 |
1
368 | 369 | 370 |
2
371 | 372 | 373 |
3
374 | 375 | 376 |
4
377 | 378 | 379 |
5
380 | 381 | 382 |
6
383 | 384 |
385 | 386 | 387 |
1
388 | 389 | 390 |
2
391 | 392 | 393 |
3
394 | 395 | 396 |
4
397 | 398 | 399 |
5
400 | 401 | 402 |
6
403 | 404 |
405 |
{`  
406 |      
407 |         1
408 |         2
409 |         3
410 |         4
411 |         5
412 |         6
413 |      
414 | 	  
415 |         1
416 |         2
417 |         3
418 |         4
419 |         5
420 |         6
421 |      
422 |      
423 |         1
424 |         2
425 |         3
426 |         4
427 |         5
428 |         6
429 |      
430 |   `}
431 |
432 | 433 |
434 |

Hidden

435 |

Columns can be hidden for specific viewport sizes (or a range of viewport sizes)

436 | 437 | 438 |
if xs4, visible
439 | 440 | 441 |
if xs8, visible
442 | 443 | 444 |
if sm8, visible
445 | 446 | 447 |
if sm12, visible
448 | 449 | 450 |
if md12, visible
451 | 452 | 453 |
if lg12, visible
454 | 455 | 456 |
if xl12, visible
457 | 458 |
459 | 460 | 461 |
if xs4, hidden
462 | 463 | 464 |
if xs8, hidden
465 | 466 | 467 |
if sm8, hidden
468 | 469 | 470 |
if sm12, hidden
471 | 472 | 473 |
if md12, hidden
474 | 475 | 476 |
if lg12, hidden
477 | 478 | 479 |
if xl12, hidden
480 | 481 |
482 |
{`  
483 |      
484 |         
485 |            if xs4, visible
486 |         
487 |         
488 |            if xs8, visible
489 |         
490 |         
491 |            if sm8, visible
492 |         
493 |         
494 |            if sm12, visible
495 |         
496 |         
497 |            if md12, visible
498 |         
499 |         
500 |            if lg12, visible
501 |         
502 |         
503 |            if xl12, visible
504 |         
505 |      
506 |      
507 |         
508 |            if xs4, hidden
509 |         
510 |         
511 |            if xs8, hidden
512 |         
513 |         
514 |            if sm8, hidden
515 |         
516 |         
517 |            if sm12, hidden
518 |         
519 |         
520 |            if md12, hidden
521 |         
522 |         
523 |            if lg12, hidden
524 |         
525 |         
526 |            if xl12, hidden
527 |         
528 |      
529 |   `}
530 |
531 | 532 |
533 |

Fixed Width Grids

534 |

At 1600 dp, Material Design allows a grid to either fill their container or remain at fixed width (left aligned or centered).

535 | 536 | 537 | 538 |
Fluid (default)
539 | 540 |
541 |
542 | 543 | 544 | 545 |
Fixed width, aligned left
546 | 547 |
548 |
549 | 550 | 551 | 552 |
Fixed width, aligned center
553 | 554 |
555 |
556 |
{`  
557 |      
558 |         
559 |            Fluid (default)
560 |         
561 |      
562 |   
563 |   
564 |      
565 |         
566 |            Fixed width, aligned left
567 |         
568 |      
569 |   
570 |   
571 |      
572 |         
573 |            Fixed width, aligned center
574 |         
575 |      
576 |   `}
577 |
578 | 579 |
580 |

Marginless Grid

581 |

582 | By default, grids have a margin that preserves the outer padding on edge-adjacent 583 | columns and prevents a row of content from spanning edge to edge. A marginless 584 | grid eliminates this outer padding. 585 |

586 | 587 | 588 | 589 |
590 | Default Grid 591 |
592 | 593 | 594 |
595 | Outer 596 |
597 | 598 | 599 |
600 | Inner 601 |
602 | 603 | 604 |
605 | Inner 606 |
607 | 608 | 609 |
610 | Outer 611 |
612 | 613 |
614 |
615 | 616 | 617 | 618 |
Marginless Grid
619 | 620 | 621 |
622 | Outer 623 |
624 | 625 | 626 |
627 | Inner 628 |
629 | 630 | 631 |
632 | Inner 633 |
634 | 635 | 636 |
637 | Outer 638 |
639 | 640 |
641 |
642 |
{`  
643 |      
644 |         
645 |            Default Grid
646 |         
647 |         
648 |            Outer
649 |         
650 |         
651 |            Inner
652 |         
653 |         
654 |            Inner
655 |         
656 |         
657 |            Outer
658 |         
659 |      
660 |   
661 |   
662 |      
663 |         
664 |            Marginless Grid
665 |         
666 |         
667 |            Outer
668 |         
669 |         
670 |            Inner
671 |         
672 |         
673 |            Inner
674 |         
675 |         
676 |            Outer
677 |         
678 |      
679 |   `}
680 |
681 | 682 |
683 | ); 684 | 685 | export default GridTestPage; 686 | -------------------------------------------------------------------------------- /examples/src/pages/GridTestPage/index.js: -------------------------------------------------------------------------------- 1 | import GridTestPage from './GridTestPage'; 2 | 3 | export default GridTestPage; 4 | -------------------------------------------------------------------------------- /examples/src/pages/GridTestPage/styles.css: -------------------------------------------------------------------------------- 1 | .box { 2 | background-color: rgba(255, 143, 0, 0.7); 3 | border: solid 1px rgba(255, 143, 0, 0.5); 4 | padding: 1.0em; 5 | margin-bottom: 16px; 6 | text-align: center; 7 | } 8 | 9 | .inner { 10 | background-color: rgba(255, 143, 0, 0.7); 11 | border: solid 1px rgba(255, 143, 0, 0.5); 12 | padding: 1.0em; 13 | text-align: center; 14 | } 15 | 16 | .box-container { 17 | padding: 16px 16px 0 16px; 18 | } 19 | 20 | .box-tall { 21 | height: 64px; 22 | } 23 | 24 | .box-other { 25 | background-color: rgba(64, 79, 98, 0.2); 26 | border: solid 1px rgba(64, 79, 98, 0.1); 27 | } 28 | 29 | .code { 30 | padding: 0.8em; 31 | border: solid 1px #ddd; 32 | background-color: #e2e2e2; 33 | line-height: 1.5em; 34 | font-size: 0.9em; 35 | font-family: 'Courier New', monospace; 36 | overflow: auto; 37 | } 38 | -------------------------------------------------------------------------------- /examples/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import GridTestPage from './GridTestPage'; 2 | 3 | export default { 4 | GridTestPage, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | const combineLoaders = require('webpack-combine-loaders'); 2 | const cssnext = require('postcss-cssnext'); 3 | const cssImport = require('postcss-import'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const path = require('path'); 7 | const webpack = require('webpack'); 8 | 9 | const NODE_ENV = process.env.NODE_ENV; 10 | const isDev = NODE_ENV === 'development'; 11 | const isProd = NODE_ENV === 'production'; 12 | 13 | const config = { 14 | entry: [ 15 | path.join(__dirname, 'src', 'index.js'), 16 | ], 17 | output: { 18 | filename: 'app.js', 19 | path: path.join(__dirname, 'dist'), 20 | publicPath: '/', 21 | }, 22 | module: { 23 | loaders: [{ 24 | test: /\.(js|jsx)?$/, 25 | exclude: /node_modules/, 26 | loaders: ['babel-loader'], 27 | }, { 28 | test: /\.css$/, 29 | loader: ExtractTextPlugin.extract({ 30 | fallback: 'style-loader', 31 | use: combineLoaders([{ 32 | loader: 'css-loader', 33 | query: { 34 | modules: true, 35 | localIdentName: '[name]__[local]___[hash:base64:5]', 36 | minimize: isProd, 37 | }, 38 | }, { 39 | loader: 'postcss-loader', 40 | }]), 41 | }), 42 | }], 43 | }, 44 | plugins: [ 45 | new HtmlWebpackPlugin({ 46 | filename: 'index.html', 47 | template: path.join(__dirname, 'src', 'index.html'), 48 | }), 49 | new webpack.LoaderOptionsPlugin({ 50 | options: { 51 | postcss: [ 52 | cssImport(), 53 | cssnext(), 54 | ], 55 | }, 56 | }), 57 | new webpack.optimize.OccurrenceOrderPlugin(), 58 | new webpack.NoEmitOnErrorsPlugin(), 59 | new ExtractTextPlugin('styles.css'), 60 | ], 61 | resolve: { 62 | modules: ['node_modules', './src'], 63 | extensions: ['.js', '.jsx'], 64 | }, 65 | }; 66 | 67 | if (isDev) { 68 | config.entry.unshift( 69 | 'react-hot-loader/patch', 70 | 'webpack/hot/only-dev-server'); 71 | config.devtool = 'source-map'; 72 | config.devServer = { 73 | inline: true, 74 | historyApiFallback: true, 75 | host: '0.0.0.0', 76 | hot: true, 77 | port: 3000, 78 | }; 79 | config.plugins.push(new webpack.HotModuleReplacementPlugin()); 80 | } 81 | 82 | if (isProd) { 83 | config.plugins.push(new webpack.optimize.UglifyJsPlugin()); 84 | } 85 | 86 | config.entry.unshift('babel-polyfill'); // must always come first in entry list 87 | 88 | module.exports = config; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-material-responsive-grid-source", 3 | "private": true, 4 | "version": "1.2.2", 5 | "description": "A set of React components implementing the material-responsive-grid CSS framework.", 6 | "main": "build/index.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/STORIS/react-material-responsive-grid.git" 10 | }, 11 | "scripts": { 12 | "build": "npm run build:components && npm run build:copy-files", 13 | "build:components": "cross-env NODE_ENV=production babel -d ./build ./src", 14 | "build:copy-files": "babel-node ./scripts/copy-files.js", 15 | "clean:build": "rimraf ./build", 16 | "prebuild": "npm run lint && npm run test && npm run clean:build", 17 | "lint": "eslint --ignore-path .eslintignore -- ./src/. ", 18 | "test": "jest" 19 | }, 20 | "keywords": [ 21 | "grid", 22 | "material design", 23 | "responsive", 24 | "react", 25 | "components" 26 | ], 27 | "author": "Ken Gregory", 28 | "license": "MIT", 29 | "homepage": "https://github.com/STORIS/react-material-responsive-grid", 30 | "devDependencies": { 31 | "babel-cli": "^6.24.0", 32 | "babel-core": "^6.22.1", 33 | "babel-eslint": "^8.0.2", 34 | "babel-jest": "^21.2.0", 35 | "babel-plugin-transform-runtime": "^6.23.0", 36 | "babel-preset-env": "^1.2.0", 37 | "babel-preset-react": "^6.22.0", 38 | "babel-preset-stage-2": "^6.22.0", 39 | "cross-env": "^5.1.1", 40 | "eslint": "^4.11.0", 41 | "eslint-config-airbnb": "^16.1.0", 42 | "eslint-plugin-import": "^2.2.0", 43 | "eslint-plugin-jsx-a11y": "^6.0.2", 44 | "eslint-plugin-react": "^7.4.0", 45 | "expect": "^21.2.1", 46 | "fs-extra": "^4.0.2", 47 | "identity-obj-proxy": "^3.0.0", 48 | "jest": "^21.2.1", 49 | "react": "^16.1.1", 50 | "react-dom": "^16.1.1", 51 | "react-hot-loader": "^3.0.0-beta.6", 52 | "react-test-renderer": "^16.1.1", 53 | "rimraf": "^2.5.4" 54 | }, 55 | "dependencies": { 56 | "babel-runtime": "^6.23.0", 57 | "material-responsive-grid": "^1.1.0", 58 | "prop-types": "^15.5.10" 59 | }, 60 | "peerDependencies": { 61 | "react": "^15.3.0 || ^16.0.0", 62 | "react-dom": "^15.3.0 || ^16.0.0" 63 | }, 64 | "jest": { 65 | "moduleNameMapper": { 66 | "\\.(css|less)$": "identity-obj-proxy" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /scripts/copy-files.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import path from 'path'; 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | import fse from 'fs-extra'; 5 | 6 | const files = [ 7 | 'README.md', 8 | 'CHANGELOG.md', 9 | 'LICENSE', 10 | ]; 11 | 12 | const resolveBuildPath = file => ( 13 | path.resolve(__dirname, '../build/', path.basename(file)) 14 | ); 15 | 16 | const copyFile = (file) => { 17 | const buildPath = resolveBuildPath(file); 18 | return new Promise((resolve) => { 19 | fse.copy( 20 | file, 21 | buildPath, 22 | (err) => { 23 | if (err) throw err; 24 | resolve(); 25 | }, 26 | ); 27 | }) 28 | .then(() => console.log(`Copied ${file} to ${buildPath}`)); 29 | }; 30 | 31 | const createPackageFile = () => ( 32 | new Promise((resolve) => { 33 | fse.readFile(path.resolve(__dirname, '../package.json'), 'utf8', (err, data) => { 34 | if (err) throw err; 35 | resolve(data); 36 | }); 37 | }) 38 | .then(data => JSON.parse(data)) 39 | .then((packageData) => { 40 | const { 41 | author, 42 | version, 43 | description, 44 | keywords, 45 | repository, 46 | license, 47 | bugs, 48 | homepage, 49 | peerDependencies, 50 | dependencies, 51 | } = packageData; 52 | 53 | const minimalPackage = { 54 | name: 'react-material-responsive-grid', 55 | author, 56 | version, 57 | description, 58 | main: './index.js', 59 | keywords, 60 | repository, 61 | license, 62 | bugs, 63 | homepage, 64 | peerDependencies, 65 | dependencies, 66 | }; 67 | 68 | return new Promise((resolve) => { 69 | const buildPath = resolveBuildPath('package.json'); 70 | const data = JSON.stringify(minimalPackage, null, 2); 71 | fse.writeFile(buildPath, data, (err) => { 72 | if (err) throw (err); 73 | console.log(`Created package.json in ${buildPath}`); 74 | resolve(); 75 | }); 76 | }); 77 | }) 78 | ); 79 | 80 | Promise.all( 81 | files.map(file => copyFile(file)), 82 | ) 83 | .then(() => createPackageFile()); 84 | -------------------------------------------------------------------------------- /src/__tests__/components/Col.spec.jsx: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import React from 'react'; 3 | import { createRenderer } from 'react-test-renderer/shallow'; 4 | import style from 'material-responsive-grid/material-responsive-grid.css'; 5 | import Col from '../../components/Col'; 6 | 7 | const renderer = createRenderer(); 8 | 9 | /** 10 | * className: string 11 | * xs4: number 12 | * xs8: number 13 | * sm8: number 14 | * sm: number 15 | * md: number 16 | * lg: number 17 | * xl: number 18 | * xs4Offset: number 19 | * xs8Offset: number 20 | * sm8Offset: number 21 | * smOffset: number 22 | * mdOffset: number 23 | * lgOffset: number 24 | * xlOffset: number 25 | * first: arrayOf(string) 26 | * last: arrayOf(string) 27 | * hiddenUp: string 28 | * hiddenDown: string 29 | * hiddenOnly: arrayOf(string) 30 | * tagName: string 31 | */ 32 | describe('Col', () => { 33 | it('Should add classes for screen sizes', () => { 34 | renderer.render( 35 | , 48 | ); 49 | 50 | const { 51 | type, 52 | props: { className }, 53 | } = renderer.getRenderOutput(); 54 | 55 | expect(type).toBe('div'); 56 | expect(className).toContain(style['col-xs4-1']); 57 | expect(className).toContain(style['col-xs8-2']); 58 | expect(className).toContain(style['col-sm8-3']); 59 | expect(className).toContain(style['col-sm12-4']); 60 | expect(className).toContain(style['col-md12-5']); 61 | expect(className).toContain(style['col-lg12-6']); 62 | expect(className).toContain(style['col-xl12-7']); 63 | expect(className).toContain(style['col-sm-8']); 64 | expect(className).toContain(style['col-md-9']); 65 | expect(className).toContain(style['col-lg-10']); 66 | expect(className).toContain(style['col-xl-11']); 67 | }); 68 | 69 | it('Should add classes for screen size specific offset', () => { 70 | renderer.render( 71 | , 84 | ); 85 | 86 | const { 87 | type, 88 | props: { className }, 89 | } = renderer.getRenderOutput(); 90 | 91 | expect(type).toBe('div'); 92 | expect(className).toContain(style['col-xs4-offset-0']); 93 | expect(className).toContain(style['col-xs8-offset-1']); 94 | expect(className).toContain(style['col-sm8-offset-2']); 95 | expect(className).toContain(style['col-sm12-offset-3']); 96 | expect(className).toContain(style['col-md12-offset-4']); 97 | expect(className).toContain(style['col-lg12-offset-5']); 98 | expect(className).toContain(style['col-xl12-offset-6']); 99 | expect(className).toContain(style['col-sm-offset-7']); 100 | expect(className).toContain(style['col-md-offset-8']); 101 | expect(className).toContain(style['col-lg-offset-9']); 102 | expect(className).toContain(style['col-xl-offset-10']); 103 | }); 104 | 105 | it('Should add "first-*" class if "first" property includes valid sizes', () => { 106 | renderer.render(); 107 | 108 | const { 109 | props: { className }, 110 | } = renderer.getRenderOutput(); 111 | 112 | expect(className).toContain(style['first-xs4']); 113 | expect(className).toContain(style['first-md']); 114 | expect(className).toContain(style['first-xl']); 115 | expect(className).toContain(style['first-lg12']); 116 | expect(className).not.toContain('xxl'); 117 | }); 118 | 119 | it('Should add "last-*" class if "last" property includes valid sizes', () => { 120 | renderer.render(); 121 | 122 | const { 123 | props: { className }, 124 | } = renderer.getRenderOutput(); 125 | 126 | expect(className).toContain(style['last-xs4']); 127 | expect(className).toContain(style['last-md']); 128 | expect(className).toContain(style['last-xl']); 129 | expect(className).toContain(style['last-lg12']); 130 | expect(className).not.toContain('xxl'); 131 | }); 132 | 133 | it('Should add "hidden-*-only" if "hidden" includes valid sizes', () => { 134 | renderer.render(); 135 | 136 | const { 137 | props: { className }, 138 | } = renderer.getRenderOutput(); 139 | 140 | expect(className).toContain(style['hidden-xs4-only']); 141 | expect(className).toContain(style['hidden-md-only']); 142 | expect(className).toContain(style['hidden-xl-only']); 143 | expect(className).toContain(style['hidden-lg12-only']); 144 | expect(className).not.toContain('xxl'); 145 | }); 146 | 147 | it('Should add "hidden-*-down" if "hiddenDown" is set to a valid size', () => { 148 | renderer.render(); 149 | 150 | const { 151 | props: { className }, 152 | } = renderer.getRenderOutput(); 153 | 154 | expect(className).toContain(style['hidden-xs4-down']); 155 | }); 156 | 157 | it('Should not add "hidden-*-down" if "hiddenDown" is set to an invalid size', () => { 158 | renderer.render(); 159 | 160 | const { 161 | props: { className }, 162 | } = renderer.getRenderOutput(); 163 | 164 | expect(className).toBe(undefined); 165 | }); 166 | 167 | it('Should add "hidden-*-up" if "hiddenUp" is set to a valid size', () => { 168 | renderer.render(); 169 | 170 | const { 171 | props: { className }, 172 | } = renderer.getRenderOutput(); 173 | 174 | expect(className).toContain(style['hidden-xs4-up']); 175 | }); 176 | 177 | it('Should not add "hidden-*-up" if "hiddenUp" is set to an invalid size', () => { 178 | renderer.render(); 179 | 180 | const { 181 | props: { className }, 182 | } = renderer.getRenderOutput(); 183 | 184 | expect(className).toBe(undefined); 185 | }); 186 | 187 | it('Should retain specified className as last class', () => { 188 | renderer.render(); 189 | 190 | const { className } = renderer.getRenderOutput().props; 191 | 192 | expect(className).toContain('foo'); 193 | expect(className).toContain(style['col-md-3']); 194 | expect(className.split(' ').pop()).toEqual('foo'); 195 | }); 196 | 197 | it('Should apply both class names both forms of a size property are applied', () => { 198 | renderer.render(); 199 | 200 | const { className } = renderer.getRenderOutput().props; 201 | 202 | expect(className).toContain(style['col-md-3']); 203 | expect(className).toContain(style['col-md12-2']); 204 | }); 205 | 206 | it('Should apply both class names when both forms of a size are specified in an array', () => { 207 | renderer.render(); 208 | 209 | const { className } = renderer.getRenderOutput().props; 210 | 211 | expect(className).toContain(style['first-sm']); 212 | expect(className).toContain(style['first-sm12']); 213 | }); 214 | 215 | it('Should support custom tag name', () => { 216 | renderer.render(); 217 | expect(renderer.getRenderOutput().type).toBe('li'); 218 | }); 219 | }); 220 | -------------------------------------------------------------------------------- /src/__tests__/components/Grid.spec.jsx: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import React from 'react'; 3 | import { createRenderer } from 'react-test-renderer/shallow'; 4 | import style from 'material-responsive-grid/material-responsive-grid.css'; 5 | import Grid from '../../components/Grid'; 6 | 7 | /** 8 | * className: string 9 | * fixed: string (left | center) 10 | * tagName: string 11 | */ 12 | const renderer = createRenderer(); 13 | 14 | describe('Grid', () => { 15 | it('Should add "marginless" class if marginless', () => { 16 | renderer.render(); 17 | 18 | const { 19 | type, 20 | props: { className }, 21 | } = renderer.getRenderOutput(); 22 | 23 | expect(type).toBe('div'); 24 | expect(className).toContain(style.grid); 25 | expect(className).toContain(style.marginless); 26 | }); 27 | 28 | it('Should add "fixed-left" class if fixed left', () => { 29 | renderer.render(); 30 | 31 | const { 32 | type, 33 | props: { className }, 34 | } = renderer.getRenderOutput(); 35 | 36 | expect(type).toBe('div'); 37 | expect(className).toContain(style.grid); 38 | expect(className).toContain(style['fixed-left']); 39 | }); 40 | 41 | it('Should add "fixed-center" class if fixed center', () => { 42 | renderer.render(); 43 | 44 | const { 45 | type, 46 | props: { className }, 47 | } = renderer.getRenderOutput(); 48 | 49 | expect(type).toBe('div'); 50 | expect(className).toContain(style.grid); 51 | expect(className).toContain(style['fixed-center']); 52 | }); 53 | 54 | it('Should not add "fixed-left" or "fixed-center" class if fixed is invalid', () => { 55 | renderer.render(); 56 | 57 | const { 58 | type, 59 | props: { className }, 60 | } = renderer.getRenderOutput(); 61 | 62 | expect(type).toBe('div'); 63 | expect(className).toContain(style.grid); 64 | expect(className).not.toContain(style['fixed-left']); 65 | expect(className).not.toContain(style['fixed-center']); 66 | }); 67 | 68 | it('Should not add "fixed-left", "fixed-center", or "marginless" class if unspecified', () => { 69 | renderer.render(); 70 | 71 | const { 72 | type, 73 | props: { className }, 74 | } = renderer.getRenderOutput(); 75 | 76 | expect(type).toBe('div'); 77 | expect(className).toContain(style.grid); 78 | expect(className).not.toContain(style['fixed-left']); 79 | expect(className).not.toContain(style['fixed-center']); 80 | expect(className).not.toContain(style.marginless); 81 | }); 82 | 83 | it('Should retain specified className as last class', () => { 84 | renderer.render(); 85 | 86 | const { className } = renderer.getRenderOutput().props; 87 | 88 | expect(className).toContain('foo'); 89 | expect(className).toContain(style['fixed-left']); 90 | expect(className.split(' ').pop()).toEqual('foo'); 91 | }); 92 | 93 | it('Should support custom tag name', () => { 94 | renderer.render(); 95 | expect(renderer.getRenderOutput().type).toBe('li'); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/__tests__/components/Row.spec.jsx: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import React from 'react'; 3 | import { createRenderer } from 'react-test-renderer/shallow'; 4 | import style from 'material-responsive-grid/material-responsive-grid.css'; 5 | import Row from '../../components/Row'; 6 | 7 | /** 8 | * className: string 9 | * reverse: bool 10 | * start: arrayOf(string) 11 | * center: arrayOf(string) 12 | * end: arrayOf(string) 13 | * top: arrayOf(string) 14 | * middle: arrayOf(string) 15 | * bottom: arrayOf(string) 16 | * around: arrayOf(string) 17 | * between: arrayOf(string) 18 | * tagName: string 19 | */ 20 | const renderer = createRenderer(); 21 | 22 | describe('Row', () => { 23 | it('Should add "reverse" class if "reverse" is set', () => { 24 | renderer.render(); 25 | 26 | const { 27 | type, 28 | props: { className }, 29 | } = renderer.getRenderOutput(); 30 | 31 | expect(type).toBe('div'); 32 | expect(className).toContain(style.reverse); 33 | }); 34 | 35 | it('Should add "start-*" class if "start" property is set', () => { 36 | renderer.render(); 37 | 38 | const { 39 | props: { className }, 40 | } = renderer.getRenderOutput(); 41 | 42 | expect(className).toContain(style['start-xs4']); 43 | expect(className).toContain(style['start-md']); 44 | expect(className).toContain(style['start-xl']); 45 | }); 46 | 47 | it('Should add "center-*" class if "center" property is set', () => { 48 | renderer.render(); 49 | 50 | const { 51 | props: { className }, 52 | } = renderer.getRenderOutput(); 53 | 54 | expect(className).toContain(style['center-xs4']); 55 | expect(className).toContain(style['center-md']); 56 | expect(className).toContain(style['center-xl']); 57 | }); 58 | 59 | it('Should add "end-*" class if "end" property is set', () => { 60 | renderer.render(); 61 | 62 | const { 63 | props: { className }, 64 | } = renderer.getRenderOutput(); 65 | 66 | expect(className).toContain(style['end-xs4']); 67 | expect(className).toContain(style['end-md']); 68 | expect(className).toContain(style['end-xl']); 69 | }); 70 | 71 | it('Should add "top-*" class if "top" property is set', () => { 72 | renderer.render(); 73 | 74 | const { 75 | props: { className }, 76 | } = renderer.getRenderOutput(); 77 | 78 | expect(className).toContain(style['top-xs4']); 79 | expect(className).toContain(style['top-md']); 80 | expect(className).toContain(style['top-xl']); 81 | }); 82 | 83 | it('Should add "middle-*" class if "middle" property is set', () => { 84 | renderer.render(); 85 | 86 | const { 87 | props: { className }, 88 | } = renderer.getRenderOutput(); 89 | 90 | expect(className).toContain(style['middle-xs4']); 91 | expect(className).toContain(style['middle-md']); 92 | expect(className).toContain(style['middle-xl']); 93 | }); 94 | 95 | it('Should add "bottom-*" class if "bottom" property is set', () => { 96 | renderer.render(); 97 | 98 | const { 99 | props: { className }, 100 | } = renderer.getRenderOutput(); 101 | 102 | expect(className).toContain(style['bottom-xs4']); 103 | expect(className).toContain(style['bottom-md']); 104 | expect(className).toContain(style['bottom-xl']); 105 | }); 106 | 107 | it('Should retain specified className as last class', () => { 108 | renderer.render(); 109 | 110 | const { className } = renderer.getRenderOutput().props; 111 | 112 | expect(className).toContain('foo'); 113 | expect(className).toContain(style['end-md']); 114 | expect(className.split(' ').pop()).toEqual('foo'); 115 | }); 116 | 117 | it('Should support custom tag name', () => { 118 | renderer.render(); 119 | expect(renderer.getRenderOutput().type).toBe('li'); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /src/__tests__/components/index.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as Exports from '../../components/index'; 3 | 4 | describe('components/index exports', () => { 5 | it('exports all components', () => { 6 | ['Grid', 'Row', 'Col'].forEach((component) => { 7 | expect(Object.keys(Exports)).toContain(component); 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as Exports from '../index'; 3 | 4 | describe('index exports', () => { 5 | it('exports all components', () => { 6 | ['Grid', 'Row', 'Col'].forEach((component) => { 7 | expect(Object.keys(Exports)).toContain(component); 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/__tests__/shared/utils.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import style from 'material-responsive-grid/material-responsive-grid.css'; 3 | import { getClass, validSizes, isSizeValid, pushSizeClassNames } from '../../shared/utils'; 4 | 5 | describe('getClass', () => { 6 | it('Should translate known styles', () => { 7 | expect(getClass('col-xs4-3')).toEqual(style['col-xs4-3']); 8 | }); 9 | 10 | it('Does not translate unknown styles', () => { 11 | expect(getClass('unknown-style')).toEqual('unknown-style'); 12 | }); 13 | }); 14 | 15 | describe('isSizeValid', () => { 16 | it('Should return true for sizes in the validSizes array', () => { 17 | expect(isSizeValid(validSizes[0])).toBe(true); 18 | }); 19 | 20 | it('Should return false for invalid sizes', () => { 21 | expect(isSizeValid('I believe it is called large')).toBe(false); 22 | }); 23 | }); 24 | 25 | describe('pushSizeClassNames', () => { 26 | it('Should push classNames for valid sizes onto the provided array', () => { 27 | const classNames = []; 28 | pushSizeClassNames(classNames, ['xs4', 'lg'], 'hidden-', '-only'); 29 | expect(classNames).toEqual(['hidden-xs4-only', 'hidden-lg-only']); 30 | }); 31 | it('Should not push classNames for invalid sizes', () => { 32 | const classNames = []; 33 | pushSizeClassNames(classNames, ['xs4', 'not a valid size', 'lg12'], 'hidden-', '-only'); 34 | expect(classNames).toEqual(['hidden-xs4-only', 'hidden-lg12-only']); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/components/Col.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getClass, isSizeValid, pushSizeClassNames } from '../shared/utils'; 4 | 5 | const Col = ({ 6 | className, 7 | xs4, 8 | xs8, 9 | sm8, 10 | sm12, 11 | md12, 12 | lg12, 13 | xl12, 14 | xs4Offset, 15 | xs8Offset, 16 | sm8Offset, 17 | sm12Offset, 18 | md12Offset, 19 | lg12Offset, 20 | xl12Offset, 21 | sm, 22 | md, 23 | lg, 24 | xl, 25 | smOffset, 26 | mdOffset, 27 | lgOffset, 28 | xlOffset, 29 | first, 30 | last, 31 | hidden, 32 | hiddenUp, 33 | hiddenDown, 34 | tagName, 35 | ...other 36 | }) => { 37 | const newProps = { ...other }; 38 | const classNames = []; 39 | 40 | // size specific properties 41 | if (xs4) { 42 | classNames.push(getClass(`col-xs4-${xs4}`)); 43 | } 44 | 45 | if (xs8) { 46 | classNames.push(getClass(`col-xs8-${xs8}`)); 47 | } 48 | 49 | if (sm8) { 50 | classNames.push(getClass(`col-sm8-${sm8}`)); 51 | } 52 | 53 | if (sm12) { 54 | classNames.push(getClass(`col-sm12-${sm12}`)); 55 | } 56 | 57 | if (md12) { 58 | classNames.push(getClass(`col-md12-${md12}`)); 59 | } 60 | 61 | if (lg12) { 62 | classNames.push(getClass(`col-lg12-${lg12}`)); 63 | } 64 | 65 | if (xl12) { 66 | classNames.push(getClass(`col-xl12-${xl12}`)); 67 | } 68 | 69 | if (xs4Offset != null) { 70 | classNames.push(getClass(`col-xs4-offset-${xs4Offset}`)); 71 | } 72 | 73 | if (xs8Offset != null) { 74 | classNames.push(getClass(`col-xs8-offset-${xs8Offset}`)); 75 | } 76 | 77 | if (sm8Offset != null) { 78 | classNames.push(getClass(`col-sm8-offset-${sm8Offset}`)); 79 | } 80 | 81 | if (sm12Offset != null) { 82 | classNames.push(getClass(`col-sm12-offset-${sm12Offset}`)); 83 | } 84 | 85 | if (md12Offset != null) { 86 | classNames.push(getClass(`col-md12-offset-${md12Offset}`)); 87 | } 88 | 89 | if (lg12Offset != null) { 90 | classNames.push(getClass(`col-lg12-offset-${lg12Offset}`)); 91 | } 92 | 93 | if (xl12Offset != null) { 94 | classNames.push(getClass(`col-xl12-offset-${xl12Offset}`)); 95 | } 96 | 97 | // convenience properties (twelve columns implied) 98 | if (sm) { 99 | classNames.push(getClass(`col-sm-${sm}`)); 100 | } 101 | 102 | if (md) { 103 | classNames.push(getClass(`col-md-${md}`)); 104 | } 105 | 106 | if (lg) { 107 | classNames.push(getClass(`col-lg-${lg}`)); 108 | } 109 | 110 | if (xl) { 111 | classNames.push(getClass(`col-xl-${xl}`)); 112 | } 113 | 114 | if (smOffset != null) { 115 | classNames.push(getClass(`col-sm-offset-${smOffset}`)); 116 | } 117 | 118 | if (mdOffset != null) { 119 | classNames.push(getClass(`col-md-offset-${mdOffset}`)); 120 | } 121 | 122 | if (lgOffset != null) { 123 | classNames.push(getClass(`col-lg-offset-${lgOffset}`)); 124 | } 125 | 126 | if (xlOffset != null) { 127 | classNames.push(getClass(`col-xl-offset-${xlOffset}`)); 128 | } 129 | 130 | if (hiddenUp && isSizeValid(hiddenUp)) { 131 | classNames.push(getClass(`hidden-${hiddenUp}-up`)); 132 | } 133 | 134 | if (hiddenDown && isSizeValid(hiddenDown)) { 135 | classNames.push(getClass(`hidden-${hiddenDown}-down`)); 136 | } 137 | 138 | // properties implemented as an array of sizes 139 | pushSizeClassNames(classNames, first, 'first-'); 140 | pushSizeClassNames(classNames, last, 'last-'); 141 | pushSizeClassNames(classNames, hidden, 'hidden-', '-only'); 142 | 143 | // specified class is added last 144 | if (className) { 145 | classNames.push(className); 146 | } 147 | 148 | if (classNames.length) { 149 | newProps.className = classNames.filter(cssName => cssName).join(' '); 150 | } 151 | 152 | return ( 153 | React.createElement(tagName || 'div', newProps) 154 | ); 155 | }; 156 | 157 | Col.propTypes = { 158 | className: PropTypes.string, 159 | xs4: PropTypes.number, 160 | xs8: PropTypes.number, 161 | sm8: PropTypes.number, 162 | sm12: PropTypes.number, 163 | md12: PropTypes.number, 164 | lg12: PropTypes.number, 165 | xl12: PropTypes.number, 166 | xs4Offset: PropTypes.number, 167 | xs8Offset: PropTypes.number, 168 | sm8Offset: PropTypes.number, 169 | sm12Offset: PropTypes.number, 170 | md12Offset: PropTypes.number, 171 | lg12Offset: PropTypes.number, 172 | xl12Offset: PropTypes.number, 173 | sm: PropTypes.number, 174 | md: PropTypes.number, 175 | lg: PropTypes.number, 176 | xl: PropTypes.number, 177 | smOffset: PropTypes.number, 178 | mdOffset: PropTypes.number, 179 | lgOffset: PropTypes.number, 180 | xlOffset: PropTypes.number, 181 | first: PropTypes.arrayOf(PropTypes.string), 182 | last: PropTypes.arrayOf(PropTypes.string), 183 | hidden: PropTypes.arrayOf(PropTypes.string), 184 | hiddenUp: PropTypes.string, 185 | hiddenDown: PropTypes.string, 186 | tagName: PropTypes.string, 187 | }; 188 | 189 | Col.defaultProps = { 190 | className: null, 191 | xs4: null, 192 | xs8: null, 193 | sm8: null, 194 | sm12: null, 195 | md12: null, 196 | lg12: null, 197 | xl12: null, 198 | xs4Offset: null, 199 | xs8Offset: null, 200 | sm8Offset: null, 201 | sm12Offset: null, 202 | md12Offset: null, 203 | lg12Offset: null, 204 | xl12Offset: null, 205 | sm: null, 206 | md: null, 207 | lg: null, 208 | xl: null, 209 | smOffset: null, 210 | mdOffset: null, 211 | lgOffset: null, 212 | xlOffset: null, 213 | first: [], 214 | last: [], 215 | hidden: [], 216 | hiddenUp: null, 217 | hiddenDown: null, 218 | tagName: null, 219 | }; 220 | 221 | export default Col; 222 | -------------------------------------------------------------------------------- /src/components/Grid.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getClass } from '../shared/utils'; 4 | 5 | const Grid = ({ 6 | className, 7 | fixed, 8 | marginless, 9 | tagName, 10 | ...other 11 | }) => { 12 | const classNames = [getClass('grid')]; 13 | 14 | if (fixed === 'left') { 15 | classNames.push(getClass('fixed-left')); 16 | } else if (fixed === 'center') { 17 | classNames.push(getClass('fixed-center')); 18 | } 19 | 20 | if (marginless) { 21 | classNames.push(getClass('marginless')); 22 | } 23 | 24 | // specified class is added last 25 | if (className) { 26 | classNames.push(className); 27 | } 28 | 29 | return ( 30 | React.createElement(tagName, { 31 | className: classNames.filter(cssName => cssName).join(' '), 32 | ...other, 33 | }) 34 | ); 35 | }; 36 | 37 | Grid.propTypes = { 38 | className: PropTypes.string, 39 | fixed: PropTypes.oneOf(['left', 'center']), 40 | marginless: PropTypes.bool, 41 | tagName: PropTypes.string, 42 | }; 43 | 44 | Grid.defaultProps = { 45 | className: null, 46 | fixed: null, 47 | marginless: false, 48 | tagName: 'div', 49 | }; 50 | 51 | export default Grid; 52 | -------------------------------------------------------------------------------- /src/components/Row.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getClass, pushSizeClassNames } from '../shared/utils'; 4 | 5 | const Row = ({ 6 | className, 7 | reverse, 8 | start, 9 | center, 10 | end, 11 | top, 12 | middle, 13 | bottom, 14 | around, 15 | between, 16 | tagName, 17 | ...other 18 | }) => { 19 | const classNames = [getClass('row')]; 20 | 21 | if (reverse) { 22 | classNames.push(getClass('reverse')); 23 | } 24 | 25 | // properties implemented as an array of sizes 26 | pushSizeClassNames(classNames, start, 'start-'); 27 | pushSizeClassNames(classNames, center, 'center-'); 28 | pushSizeClassNames(classNames, end, 'end-'); 29 | pushSizeClassNames(classNames, top, 'top-'); 30 | pushSizeClassNames(classNames, middle, 'middle-'); 31 | pushSizeClassNames(classNames, bottom, 'bottom-'); 32 | pushSizeClassNames(classNames, around, 'around-'); 33 | pushSizeClassNames(classNames, between, 'between-'); 34 | 35 | // specified class is added last 36 | if (className) { 37 | classNames.push(className); 38 | } 39 | 40 | return ( 41 | React.createElement(tagName || 'div', { 42 | className: classNames.filter(cssName => cssName).join(' '), 43 | ...other, 44 | }) 45 | ); 46 | }; 47 | 48 | Row.propTypes = { 49 | className: PropTypes.string, 50 | reverse: PropTypes.bool, 51 | start: PropTypes.arrayOf(PropTypes.string), 52 | center: PropTypes.arrayOf(PropTypes.string), 53 | end: PropTypes.arrayOf(PropTypes.string), 54 | top: PropTypes.arrayOf(PropTypes.string), 55 | middle: PropTypes.arrayOf(PropTypes.string), 56 | bottom: PropTypes.arrayOf(PropTypes.string), 57 | around: PropTypes.arrayOf(PropTypes.string), 58 | between: PropTypes.arrayOf(PropTypes.string), 59 | tagName: PropTypes.string, 60 | }; 61 | 62 | Row.defaultProps = { 63 | className: null, 64 | reverse: false, 65 | start: [], 66 | center: [], 67 | end: [], 68 | top: [], 69 | middle: [], 70 | bottom: [], 71 | around: [], 72 | between: [], 73 | tagName: null, 74 | }; 75 | 76 | export default Row; 77 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Grid } from './Grid'; 2 | export { default as Row } from './Row'; 3 | export { default as Col } from './Col'; 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { Grid, Row, Col } from './components'; 2 | -------------------------------------------------------------------------------- /src/shared/utils.js: -------------------------------------------------------------------------------- 1 | import styles from 'material-responsive-grid/material-responsive-grid.css'; 2 | 3 | export const getClass = className => ( 4 | (styles && styles[className]) ? styles[className] : className 5 | ); 6 | 7 | export const validSizes = [ 8 | 'xs4', 9 | 'xs8', 10 | 'sm8', 11 | 'sm12', 12 | 'md12', 13 | 'lg12', 14 | 'xl12', 15 | 'sm', 16 | 'md', 17 | 'lg', 18 | 'xl', 19 | ]; 20 | 21 | export const isSizeValid = size => ( 22 | validSizes.indexOf(size) >= 0 23 | ); 24 | 25 | export const pushSizeClassNames = (array, sizes, preClassName = '', postClassName = '') => { 26 | sizes.forEach((size) => { 27 | if (isSizeValid(size)) { 28 | array.push(getClass(`${preClassName}${size}${postClassName}`)); 29 | } 30 | }); 31 | }; 32 | 33 | export default { 34 | getClass, 35 | validSizes, 36 | isSizeValid, 37 | pushSizeClassNames, 38 | }; 39 | --------------------------------------------------------------------------------