├── .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 |
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 | |
52 |
53 |
54 | 
55 |
56 | |
57 |
58 |
59 |
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 |
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 |
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 |
120 |
121 | What if the spacing between elements isn’t constant? Take this screen from
122 | [dribbble](https://dribbble.com/) for example:
123 |
124 |
125 |
126 | How is that spacing defined?
127 |
128 |
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 | layup
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 |
--------------------------------------------------------------------------------