├── .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 |
289 |
290 |
291 |
292 |
293 |
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 |
383 |
384 |
385 |
386 |
387 |
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 |
--------------------------------------------------------------------------------