├── .babelrc ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── docs-src ├── app.js └── index.html ├── docs ├── df68e89c42c8eb284e2e65cd60a44cad.js └── index.html ├── emotion.js ├── glamor.js ├── inline.js ├── package.json ├── src ├── createLayout.js ├── emotion.js ├── glamor.js ├── index.js ├── inline.js └── styled.js ├── static ├── FoodLayout.png ├── PageActions-grid.png ├── PageActions-margin.png ├── PageActions.png ├── SignUp-margins.gif └── SignUp.png ├── styled.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "cjs": { 4 | "presets": [ 5 | [ 6 | "env", 7 | { 8 | "targets": { 9 | "node": "6" 10 | }, 11 | "modules": "commonjs", 12 | "forceAllTransforms": true 13 | } 14 | ], 15 | "react" 16 | ] 17 | }, 18 | "esm": { 19 | "presets": [ 20 | [ 21 | "env", 22 | { 23 | "targets": { 24 | "node": "6" 25 | }, 26 | "modules": false, 27 | "forceAllTransforms": true 28 | } 29 | ], 30 | "react" 31 | ] 32 | } 33 | }, 34 | 35 | "plugins": [ 36 | "transform-object-rest-spread", 37 | ["styled-components", { "ssr": true }] 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # Parcel cache 61 | .cache 62 | 63 | # Build output 64 | dist 65 | dist-* 66 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | dist 3 | dist-* 4 | docs 5 | package.json 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | proseWrap: 'always', 5 | overrides: [ 6 | { 7 | files: ['.*rc'], 8 | options: { 9 | parser: 'json' 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brian Beck 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 | # layup 2 | 3 | A tiny React helper for CSS layout using 4 | [named areas and lines](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Layout_using_Named_Grid_Lines) 5 | from CSS Grid. 6 | 7 | **Adaptable to any React styling solution.** Includes adapters for: 8 | 9 | * [styled-components][] 10 | * [Emotion][] 11 | * [glamor][] 12 | * inline styles – no additional library necessary! 13 | 14 | ## Install 15 | 16 | ```console 17 | $ yarn add layup 18 | ``` 19 | 20 | ```console 21 | $ npm install --save layup 22 | ``` 23 | 24 | ## Looks like… 25 | 26 | (Using inline styles – but see below for more examples.) 27 | 28 | 29 | 30 | 31 | 52 | 57 | 58 | 59 |
32 | 33 | ```jsx 34 | import Layout from 'layup/inline' 35 | 36 | const foodLayout = { 37 | gridTemplateAreas: ` 38 | 'pizza . . ' 39 | '. tacos . ' 40 | '. . ramen' 41 | ` 42 | } 43 | 44 | , 46 | tacos: , 47 | ramen: 48 | }} /> 49 | ``` 50 | 51 | 53 | 54 | ![FoodLayout](static/FoodLayout.png) 55 | 56 |
60 | 61 | ## Motivation 62 | 63 | * Many React layout components have you define your sizing, alignment, and 64 | spacing via props. This makes responsive design harder. (It’s why 65 | [Styled System](https://github.com/jxnblk/styled-system) has to support array 66 | values for every prop, for example.) Defining as much layout in CSS as 67 | possible lets you move things around via standard CSS features like `@media` 68 | queries – the layout isn’t “stuck” in JavaScript. 69 | * You shouldn’t have to add individual styling properties (like `mb` or 70 | `marginBottom`) to every component you write just because there’s different 71 | whitespace around them sometimes. 72 | * Using the `margin` property for layout is often arbitrary and doesn’t make 73 | sense for UI layout. Only in rare circumstances should UI elements own the 74 | whitespace separating them. 75 | 76 | ### What’s wrong with margin? 77 | 78 | **Strong opinion time!** Using the `margin` property never made sense in the web 79 | app/component world. It’s a relic from the very first version of CSS, when we 80 | were laying out paragraphs of text in 1997. CSS Grid antiquates this practice. 81 | People just haven’t caught on yet. 82 | 83 | Examples? OK. 84 | 85 | Suppose each of these “page actions” on GitHub were a component: 86 | 87 | GitHub page actions 88 | 89 | How do you think the spacing between them is defined? Maybe they’re each inside 90 | a container with padding. Maybe they each have a left margin of 10px. Maybe they 91 | each have a right margin of 10px, except for the `:last-child` which has 0. (In 92 | this case, the latter is exactly what GitHub has done.) 93 | 94 | GitHub page actions with margin 95 | 96 | Which method is “correct”? Well, none in particular. When you’ve been writing 97 | CSS for a long time, you just develop an intuition for this. It could depend on 98 | what other page elements are nearby. But mostly, it’s arbitrary. 99 | 100 | But why do the individual components need to “own” the whitespace between them? 101 | What if they’re used in other contexts that call for different spacing? Are we 102 | just supposed to override their margins every time? Wouldn’t it be nice if the 103 | component’s styles stopped at its actual borders, making it easier to use in 104 | other places? 105 | 106 | CSS Grid offers a new solution to this problem. In this case, the container 107 | element can define the spacing between its children. It’s an inversion of the 108 | usual method of controlling whitespace, and one that (in my opinion) makes more 109 | sense. The children don’t need to concern themselves with spacing. 110 | 111 | ```css 112 | .PageActions { 113 | display: grid; 114 | grid-auto-flow: column; 115 | grid-gap: 10px; 116 | } 117 | ``` 118 | 119 | GitHub page actions with grid 120 | 121 | What if the spacing between elements isn’t constant? Take this screen from 122 | [dribbble](https://dribbble.com/) for example: 123 | 124 | dribbble sign up 125 | 126 | How is that spacing defined? 127 | 128 | dribbble sign up with margins 129 | 130 | Instead of each of those elements worrying about the overall layout, we could 131 | leave them margin-less and have the container lay them out with CSS Grid like 132 | so: 133 | 134 | ```css 135 | .SignUp { 136 | display: grid; 137 | grid-template-rows: 138 | [twitter] auto 139 | 10px 140 | [facebook] auto 141 | 10px 142 | [google] auto 143 | 15px 144 | [orSeparator] auto 145 | 15px 146 | [email] auto 147 | 30px 148 | [signIn]; 149 | } 150 | ``` 151 | 152 | The container element is then free to arrange these rows differently (even 153 | showing them in a different order) at different breakpoints. All the child 154 | elements need to do is define their `grid-row` name. This library is a helper 155 | for doing exactly that! 156 | 157 | ## Usage 158 | 159 | The `Layout` component accepts `areas`, `columns`, and `rows` props. You should 160 | only pass one of these. The value can be an object directly mapping area/line 161 | names to child components: 162 | 163 | ```jsx 164 | Leave a comment, 167 | body: , 168 | footer: 169 | }} 170 | /> 171 | ``` 172 | 173 | …or an array that will be used to pair area/line names with the `children` 174 | passed to `Layout`: 175 | 176 | ```jsx 177 | 178 | Leave a comment 179 | 180 | 181 | 182 | ``` 183 | 184 | How you define the actual layout of the areas/columns/rows depends on your 185 | styling solution. Examples follow. 186 | 187 | ### …with [styled-components][] 188 | 189 | `Layout` and its children receive their styling from `className`. Define the 190 | grid layout using `styled()`: 191 | 192 | ```jsx 193 | import styled from 'styled-components' 194 | import Layout from 'layup/styled' 195 | 196 | const FoodLayout = styled(Layout)` 197 | grid-template-areas: 198 | 'pizza . . ' 199 | '. tacos . ' 200 | '. . ramen'; 201 | ` 202 | 203 | , 205 | tacos: , 206 | ramen: 207 | }} /> 208 | ``` 209 | 210 | ### …with [Emotion][] 211 | 212 | `Layout` and its children receive their styling from `className`. Either use 213 | `styled()` (see the styled-components example above) or define the grid layout 214 | using `css`: 215 | 216 | ```jsx 217 | import { css } from 'emotion' 218 | import Layout from 'layup/emotion' 219 | 220 | const foodLayout = css` 221 | grid-template-areas: 222 | 'pizza . . ' 223 | '. tacos . ' 224 | '. . ramen'; 225 | ` 226 | 227 | , 229 | tacos: , 230 | ramen: 231 | }} /> 232 | ``` 233 | 234 | ### …with [glamor][] 235 | 236 | `Layout` and its children receive their styling from `className`. Define the 237 | grid layout using `css`: 238 | 239 | ```jsx 240 | import { css } from 'glamor' 241 | import Layout from 'layup/glamor' 242 | 243 | const foodLayout = css({ 244 | gridTemplateAreas: ` 245 | 'pizza . . ' 246 | '. tacos . ' 247 | '. . ramen' 248 | ` 249 | }) 250 | 251 | , 253 | tacos: , 254 | ramen: 255 | }} /> 256 | ``` 257 | 258 | ### …with inline styles 259 | 260 | Layout receives its styling from `style`. Children must also support the `style` 261 | prop: 262 | 263 | ```jsx 264 | import Layout from 'layup/inline' 265 | 266 | const foodLayout = { 267 | gridTemplateAreas: ` 268 | 'pizza . . ' 269 | '. tacos . ' 270 | '. . ramen' 271 | ` 272 | } 273 | 274 | , 276 | tacos: , 277 | ramen: 278 | }} /> 279 | ``` 280 | 281 | ### Adapting to any styling solution 282 | 283 | All you need to do is define two components: Grid and Cell. Pass them to the 284 | `createLayout` function to get your Layout component. 285 | 286 | **Grid** should: 287 | 288 | * Render an element with `display: grid` styling pre-applied. 289 | * Render its `children`, which will be an array of Cell elements. 290 | * Apply any further styling received from Layout. This is how users will 291 | customize the grid layout CSS. 292 | * You can do this via a prop like `className` or `style`, for example – it 293 | depends on your styling solution. 294 | 295 | **Cell** should: 296 | 297 | * Return a clone of its `child` prop using `React.cloneElement`. `child` will be 298 | one of the components passed to Layout. 299 | * Note that because it returns a clone, from the perspective of CSS, the 300 | children passed to Layout _are_ the grid cells (they aren’t _wrapped by_ 301 | grid cells). 302 | * Apply `grid-area`, `grid-column`, or `grid-row` styling to the cloned child 303 | based on the received `area`, `column`, or `row` prop, respectively. 304 | * You can apply these styles via a prop like `className` or `style`, for 305 | example – it depends on your styling solution. The only requirement is that 306 | the child components must support that prop. 307 | 308 | See the [styled-components adapter](src/styled.js) for an example. 309 | 310 | [styled system]: https://github.com/jxnblk/styled-system 311 | [styled-components]: https://www.styled-components.com/ 312 | [emotion]: https://emotion.sh/ 313 | [glamor]: https://github.com/threepointone/glamor 314 | -------------------------------------------------------------------------------- /docs-src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import styled, { injectGlobal } from 'styled-components' 4 | import Layout from '../src/styled' 5 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live' 6 | 7 | injectGlobal` 8 | @import url('https://fonts.googleapis.com/css?family=Lato:400,700'); 9 | 10 | html { 11 | font-size: 16px; 12 | 13 | @media (max-width: 768px) { 14 | font-size: 14px; 15 | } 16 | } 17 | 18 | body { 19 | margin: 12px; 20 | padding: 0; 21 | border: 6px solid rgb(218, 97, 85); 22 | font-family: Lato, Helvetica, sans-serif; 23 | font-size: 1rem; 24 | line-height: 1.5; 25 | color: rgb(62, 61, 56); 26 | background: rgb(228, 215, 192); 27 | 28 | @media (max-width: 768px) { 29 | max-width: 100vw; 30 | margin: 0; 31 | } 32 | } 33 | 34 | a:link, 35 | a:hover, 36 | a:active, 37 | a:visited { 38 | color: rgb(4, 121, 125); 39 | text-decoration: none; 40 | border-bottom: 1px solid rgba(0, 116, 120, 0.7); 41 | } 42 | 43 | code, 44 | pre { 45 | font-family: Menlo, Inconsolata, Monaco, Consolas, 'Source Code Pro', 'DejaVu Sans Mono', monospace; 46 | } 47 | 48 | h1, 49 | h2, 50 | h3, 51 | h4, 52 | h5, 53 | h6, 54 | ul, 55 | ol, 56 | li { 57 | margin: 0; 58 | padding: 0; 59 | } 60 | ` 61 | 62 | const DocsContainer = styled(Layout)` 63 | max-width: 75rem; 64 | margin: 0 auto; 65 | padding: 2rem 1rem; 66 | grid-template-rows: 67 | [title] auto 68 | 1rem 69 | [subtitle] auto 70 | 2rem 71 | [features] auto 72 | 3rem 73 | [demo] auto; 74 | 75 | @media (max-width: 768px) { 76 | padding: 2rem 0; 77 | } 78 | ` 79 | 80 | const Title = styled.h1` 81 | text-align: center; 82 | color: rgb(29, 29, 29); 83 | 84 | &:before { 85 | content: '🏀'; 86 | margin-right: 0.6rem; 87 | vertical-align: -0.1rem; 88 | } 89 | 90 | a:link, 91 | a:hover, 92 | a:active, 93 | a:visited { 94 | color: inherit; 95 | border: inherit; 96 | } 97 | ` 98 | 99 | const Subtitle = styled.h2` 100 | font-weight: normal; 101 | text-align: center; 102 | ` 103 | 104 | const FeatureList = styled.ul` 105 | display: grid; 106 | grid-auto-flow: row; 107 | grid-gap: 0.5rem; 108 | list-style: none; 109 | max-width: 50rem; 110 | margin: 0 auto; 111 | padding: 1.5rem 2rem; 112 | border-radius: 10px; 113 | font-size: ${18 / 16}rem; 114 | background: rgba(255, 255, 255, 0.9); 115 | 116 | @media (max-width: 768px) { 117 | max-width: none; 118 | margin: 0; 119 | border-radius: 0; 120 | } 121 | ` 122 | 123 | const Feature = styled.li` 124 | position: relative; 125 | padding-left: 2rem; 126 | 127 | &:before { 128 | display: inline-block; 129 | position: absolute; 130 | left: 0; 131 | min-width: 1.25em; 132 | text-align: center; 133 | content: '${props => props.icon || '✔︎'}'; 134 | color: rgb(14, 135, 84); 135 | } 136 | ` 137 | 138 | const StyledComponents = styled.a.attrs({ 139 | href: 'https://www.styled-components.com/', 140 | children: 'styled-components' 141 | })`` 142 | 143 | const Glamor = styled.a.attrs({ 144 | href: 'https://github.com/threepointone/glamor', 145 | children: 'glamor' 146 | })`` 147 | 148 | const Emotion = styled.a.attrs({ 149 | href: 'https://emotion.sh/', 150 | children: 'Emotion' 151 | })`` 152 | 153 | const DemoLayout = styled(Layout)` 154 | grid-template-columns: repeat(1, 1fr); 155 | grid-gap: 2rem; 156 | 157 | @media (min-width: 900px) { 158 | grid-template-columns: repeat(2, 1fr); 159 | } 160 | ` 161 | 162 | const Food = styled.div` 163 | padding: 10px; 164 | font-size: 32px; 165 | text-align: center; 166 | ` 167 | 168 | const Pizza = styled(Food).attrs({ 169 | children: '🍕' 170 | })` 171 | background: #ffd1a8; 172 | ` 173 | 174 | const Tacos = styled(Food).attrs({ 175 | children: '🌮' 176 | })` 177 | background: #ffe2a8; 178 | ` 179 | 180 | const Ramen = styled(Food).attrs({ 181 | children: '🍜' 182 | })` 183 | background: #ffefd1; 184 | ` 185 | 186 | const areasCode = `const FoodLayout = styled(Layout)\` 187 | grid-template-areas: 188 | 'pizza . . ' 189 | '. tacos . ' 190 | '. . ramen'; 191 | background: white; 192 | \` 193 | 194 | render( 195 | , 197 | tacos: , 198 | ramen: 199 | }} /> 200 | ) 201 | ` 202 | 203 | const rowsCode = `const FoodLayout = styled(Layout)\` 204 | grid-template-rows: 205 | 0.5rem 206 | [pizza] auto 207 | 1rem 208 | [tacos] auto 209 | 2.5rem 210 | [ramen] auto 211 | 4rem; 212 | background: white; 213 | \` 214 | 215 | render( 216 | , 218 | tacos: , 219 | ramen: 220 | }} /> 221 | ) 222 | ` 223 | 224 | const scope = { 225 | Layout, 226 | Pizza, 227 | Tacos, 228 | Ramen, 229 | styled 230 | } 231 | 232 | class App extends React.Component { 233 | render() { 234 | return ( 235 | 236 | 237 | <a href="https://github.com/exogen/layup">layup</a> 238 | 239 | CSS Grid Layout made easy 240 | 241 | Adaptable to any React styling solution 242 | 243 | Built-in adapters for , , , 244 | and inline styles 245 | 246 | 247 | Uses{' '} 248 | 249 | named lines and areas 250 | {' '} 251 | from CSS Grid Layout 252 | 253 | 254 | Say goodbye to{' '} 255 | 256 | arbitrary margin overrides 257 | 258 | 259 | 260 | See the README for 261 | documentation. 262 | 263 | 264 | The demos below are editable, try them out! 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | ) 281 | } 282 | } 283 | 284 | ReactDOM.render(, document.getElementById('app')) 285 | -------------------------------------------------------------------------------- /docs-src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | layup – CSS Grid Layout made easy. 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | layup – CSS Grid Layout made easy.
-------------------------------------------------------------------------------- /emotion.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist-cjs/emotion') 2 | -------------------------------------------------------------------------------- /glamor.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist-cjs/glamor') 2 | -------------------------------------------------------------------------------- /inline.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist-cjs/inline') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layup", 3 | "version": "0.2.2", 4 | "description": "CSS Grid Layout made easy.", 5 | "keywords": [ 6 | "react", 7 | "css", 8 | "grid", 9 | "layout" 10 | ], 11 | "author": "Brian Beck ", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/exogen/layup" 15 | }, 16 | "license": "MIT", 17 | "engines": { 18 | "node": ">=6.0.0", 19 | "npm": ">=5.3.0" 20 | }, 21 | "files": [ 22 | "dist-cjs", 23 | "dist-esm", 24 | "*.js" 25 | ], 26 | "main": "dist-cjs/index.js", 27 | "module": "dist-esm/index.js", 28 | "scripts": { 29 | "build:dist": "npm run clean:dist && npm run build:dist-cjs && npm run build:dist-esm", 30 | "build:dist-cjs": "cross-env BABEL_ENV=cjs babel src --out-dir dist-cjs", 31 | "build:dist-esm": "cross-env BABEL_ENV=esm babel src --out-dir dist-esm", 32 | "build:docs": "npm run clean:docs && cross-env BABEL_ENV=esm parcel build docs-src/index.html --out-dir docs --public-url /layup", 33 | "build": "npm run build:dist && npm run build:docs", 34 | "clean:dist": "rimraf dist-*", 35 | "clean:docs": "rimraf docs", 36 | "format": "prettier --write \"**/*.{js,json,md}\" \"**/.*rc\"", 37 | "precommit": "lint-staged", 38 | "prepare": "npm run build:dist", 39 | "start": "cross-env BABEL_ENV=esm parcel docs-src/index.html" 40 | }, 41 | "lint-staged": { 42 | "*.{js,json,md}": [ 43 | "prettier --write", 44 | "git add" 45 | ], 46 | "**/.*rc": [ 47 | "prettier --write", 48 | "git add" 49 | ], 50 | "docs-src/**": [ 51 | "npm run build:docs", 52 | "git add docs" 53 | ] 54 | }, 55 | "peerDependencies": { 56 | "react": "^16.0.0", 57 | "react-dom": "^16.0.0" 58 | }, 59 | "dependencies": {}, 60 | "devDependencies": { 61 | "babel-cli": "^6.26.0", 62 | "babel-plugin-styled-components": "^1.5.0", 63 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 64 | "babel-preset-env": "^1.6.1", 65 | "babel-preset-react": "^6.24.1", 66 | "cross-env": "^5.1.3", 67 | "glamor": "^2.20.40", 68 | "husky": "^0.14.3", 69 | "lint-staged": "^7.0.0", 70 | "parcel-bundler": "^1.6.2", 71 | "prettier": "^1.10.2", 72 | "react": "^16.2.0", 73 | "react-dom": "^16.2.0", 74 | "react-live": "^1.9.2", 75 | "rimraf": "^2.6.2", 76 | "styled-components": "^3.1.6" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/createLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | /** 4 | * A function that will return a Layout component given Grid and Cell components 5 | * that have been adapted to work with any styling library. 6 | * 7 | * @param {Function} Grid Container component to pass `children` to. The 8 | * component should apply `display: grid` to itself somehow. 9 | * @param {Function} Cell Component to pass each child to (via a `child` prop). 10 | * The component should use the `area`, `column`, or `row` prop to set the 11 | * `grid-area`, `grid-column`, or `grid-row` CSS property. 12 | */ 13 | export default function createLayout(Grid, Cell) { 14 | function Layout({ areas, columns, rows, children, ...props }) { 15 | children = mapPropsToChildren(Cell, { areas, columns, rows, children }) 16 | return React.createElement(Grid, props, children) 17 | } 18 | Layout.propTypes = { 19 | areas: PropTypes.oneOfType([ 20 | PropTypes.objectOf(PropTypes.element), 21 | PropTypes.arrayOf(PropTypes.string) 22 | ]), 23 | columns: PropTypes.oneOfType([ 24 | PropTypes.objectOf(PropTypes.element), 25 | PropTypes.arrayOf(PropTypes.string) 26 | ]), 27 | rows: PropTypes.oneOfType([ 28 | PropTypes.objectOf(PropTypes.element), 29 | PropTypes.arrayOf(PropTypes.string) 30 | ]) 31 | } 32 | return Layout 33 | } 34 | 35 | function mapPropsToChildren(Cell, { areas, columns, rows, children }) { 36 | let names 37 | let nameToProps 38 | // Determine which prop (out of `area`, `column`, or `row`) to pass to `Cell` 39 | // depending on which of `areas`, `columns`, or `rows` was passed to `Layout`. 40 | if (areas) { 41 | names = areas 42 | nameToProps = name => ({ area: name }) 43 | } else if (columns) { 44 | names = columns 45 | nameToProps = name => ({ column: name }) 46 | } else if (rows) { 47 | names = rows 48 | nameToProps = name => ({ row: name }) 49 | } else { 50 | // None of the above supplied? Don't wrap `children` in Cells. 51 | return children 52 | } 53 | const renderCell = (child, name) => { 54 | // Only wrap React elements, because Cell calls `React.cloneElement`. 55 | return React.isValidElement(child) ? ( 56 | 57 | ) : ( 58 | child 59 | ) 60 | } 61 | return Array.isArray(names) 62 | ? React.Children.map(children, (child, i) => renderCell(child, names[i])) 63 | : Object.keys(names).map(name => renderCell(names[name], name)) 64 | } 65 | -------------------------------------------------------------------------------- /src/emotion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid and Cell adapters using Emotion. 3 | * https://emotion.sh/ 4 | * 5 | * Components that make up the grid must support a `className` prop. 6 | * 7 | * (In theory this file could be exactly the same as the one for glamor, but I 8 | * used tagged template literals instead of objects.) 9 | */ 10 | import React from 'react' 11 | import { css } from 'emotion' 12 | import createLayout from './createLayout' 13 | 14 | const grid = css` 15 | display: grid; 16 | ` 17 | 18 | function Grid({ className, children, ...props }) { 19 | className = className ? `${grid} ${className}` : grid 20 | return ( 21 |
22 | {children} 23 |
24 | ) 25 | } 26 | 27 | function Cell({ area, column, row, child }) { 28 | let className 29 | if (area) { 30 | className = css` 31 | grid-area: ${area}; 32 | ` 33 | } else if (column) { 34 | className = css` 35 | grid-column: ${column}; 36 | ` 37 | } else if (row) { 38 | className = css` 39 | grid-row: ${row}; 40 | ` 41 | } 42 | if (child.props.className) { 43 | className += ` ${child.props.className}` 44 | } 45 | return React.cloneElement(child, { className }) 46 | } 47 | 48 | export default createLayout(Grid, Cell) 49 | -------------------------------------------------------------------------------- /src/glamor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid and Cell adapters using glamor. 3 | * https://github.com/threepointone/glamor 4 | * 5 | * Components that make up the grid must support a `className` prop. 6 | */ 7 | import React from 'react' 8 | import { css } from 'glamor' 9 | import createLayout from './createLayout' 10 | 11 | const grid = css({ display: 'grid ' }) 12 | 13 | function Grid({ className, children, ...props }) { 14 | className = className ? `${grid} ${className}` : grid 15 | return ( 16 |
17 | {children} 18 |
19 | ) 20 | } 21 | 22 | function Cell({ area, column, row, child }) { 23 | let className 24 | if (area) { 25 | className = css({ gridArea: area }) 26 | } else if (column) { 27 | className = css({ gridColumn: column }) 28 | } else if (row) { 29 | className = css({ gridRow: row }) 30 | } 31 | if (child.props.className) { 32 | className += ` ${child.props.className}` 33 | } 34 | return React.cloneElement(child, { className }) 35 | } 36 | 37 | export default createLayout(Grid, Cell) 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as createLayout } from './createLayout' 2 | -------------------------------------------------------------------------------- /src/inline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid and Cell adapters using inline styles. 3 | * 4 | * Components that make up the grid must support a `style` prop. 5 | */ 6 | import React from 'react' 7 | import createLayout from './createLayout' 8 | 9 | export function Grid({ style, children, ...props }) { 10 | style = { display: 'grid', ...style } 11 | return ( 12 |
13 | {children} 14 |
15 | ) 16 | } 17 | 18 | export function Cell({ area, column, row, child }) { 19 | const style = { ...child.props.style } 20 | if (area) { 21 | style.gridArea = area 22 | } else if (column) { 23 | style.gridColumn = column 24 | } else if (row) { 25 | style.gridRow = row 26 | } 27 | return React.cloneElement(child, { style }) 28 | } 29 | 30 | export default createLayout(Grid, Cell) 31 | -------------------------------------------------------------------------------- /src/styled.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid and Cell adapters using styled-components. 3 | * https://www.styled-components.com/ 4 | * 5 | * Components that make up the grid must support a `className` prop. 6 | */ 7 | import React from 'react' 8 | import styled from 'styled-components' 9 | import createLayout from './createLayout' 10 | 11 | export const Grid = styled.div` 12 | display: grid; 13 | ` 14 | 15 | export const Cell = styled(({ className, child }) => { 16 | if (child.props.className) { 17 | className += ` ${child.props.className}` 18 | } 19 | return React.cloneElement(child, { className }) 20 | })` 21 | ${props => (props.area ? `grid-area: ${props.area};` : '')} 22 | ${props => (props.column ? `grid-column: ${props.column};` : '')} 23 | ${props => (props.row ? `grid-row: ${props.row};` : '')} 24 | ` 25 | 26 | export default createLayout(Grid, Cell) 27 | -------------------------------------------------------------------------------- /static/FoodLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exogen/layup/797fef83cb256d9e072bcc21699518471da24531/static/FoodLayout.png -------------------------------------------------------------------------------- /static/PageActions-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exogen/layup/797fef83cb256d9e072bcc21699518471da24531/static/PageActions-grid.png -------------------------------------------------------------------------------- /static/PageActions-margin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exogen/layup/797fef83cb256d9e072bcc21699518471da24531/static/PageActions-margin.png -------------------------------------------------------------------------------- /static/PageActions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exogen/layup/797fef83cb256d9e072bcc21699518471da24531/static/PageActions.png -------------------------------------------------------------------------------- /static/SignUp-margins.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exogen/layup/797fef83cb256d9e072bcc21699518471da24531/static/SignUp-margins.gif -------------------------------------------------------------------------------- /static/SignUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exogen/layup/797fef83cb256d9e072bcc21699518471da24531/static/SignUp.png -------------------------------------------------------------------------------- /styled.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist-cjs/styled') 2 | --------------------------------------------------------------------------------