Skip to content

Instantly share code, notes, and snippets.

@jgoux
Last active March 28, 2017 10:10
Show Gist options
  • Save jgoux/fc4a8f193a20ec2af450a57682ccd2e1 to your computer and use it in GitHub Desktop.
Save jgoux/fc4a8f193a20ec2af450a57682ccd2e1 to your computer and use it in GitHub Desktop.
CSS Grid Layout + React, version 2! (https://www.webpackbin.com/bins/-KgGJxladDSzvMZkxAT8)
import React from 'react'
import styled, { injectGlobal, css } from 'styled-components'
import Grid from './Grid'
injectGlobal`
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body, #app {
height: 100vh;
font-family: Arial, sans-serif;
}
`
const Header = (props) => <div {...props}>Header</div>
Header.displayName = 'Header'
const Aside = (props) => <div {...props}>Aside</div>
Aside.displayName = 'Aside'
const Main = (props) => <div {...props}>Main</div>
Main.displayName = 'Main'
const Footer = (props) => <div {...props}>Footer</div>
Footer.displayName = 'Footer'
const styles = css`
height: 100vh;
& > * {
font-size: 2em;
background-color: mediumturquoise;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
`
// use area on children
const AppArea = styled(({ className }) => (
<Grid
{...{ className }}
gap="10px"
template={`
150px 1fr
header header 50px
aside main 1fr
footer footer 100px
`}
>
<div area="header">Header</div>
<div area="aside">Aside</div>
<div area="main">Main</div>
<div area="footer">Footer</div>
</Grid>
))`${styles}`
// use displayName from children component
const AppDisplayName = styled(({ className }) => (
<Grid
{...{ className }}
gap="10px"
template={`
150px 1fr
Header Header 50px
Aside Main 1fr
Footer Footer 100px
`}
>
<Header/>
<Aside/>
<Main/>
<Footer/>
</Grid>
))`${styles}`
// use both !
const AppBoth = styled(({ className }) => (
<Grid
{...{ className }}
gap="10px"
template={`
150px 1fr
Header Header 50px
Aside main 1fr
Footer Footer 50px
`}
>
<Header/>
<Aside/>
<main area="main">Main</main>
<Footer/>
</Grid>
))`${styles}`
// export default AppArea
// export default AppDisplayName
export default AppBoth
import R from 'ramda'
import React from 'react'
import styled, { css } from 'styled-components'
// Parse a template shorthand
//
// 150px 1fr
// header header 50px
// aside main 1fr
// footer footer 100px
//
// becomes
//
// {
// columns: '150px 1fr',
// rows: '50px 1fr 100px',
// areas:
// "header header"
// "aside main"
// "footer footer"
// }
const parseTemplate = R.pipe(
R.trim,
R.split('\n'),
R.addIndex(R.reduce)((acc, val, i) => (
i === 0
? { ...acc, columns: val }
: R.pipe(
R.trim,
R.lastIndexOf(' '),
R.splitAt(R.__, R.trim(val)),
([area, row]) => R.evolve({
rows: R.concat(R.__, ` ${row}`),
areas: R.concat(R.__, `"${area}"`)
}, acc)
)(val)
), { columns: '', rows: '', areas: '' })
)
// Apply each CSS transformation if the prop exists
const computeCSS = spec => R.pipe(
R.pick(R.keys(spec)),
R.evolve(R.map(R.ifElse(R.isNil, R.always('')), spec)),
R.values, R.join('')
)
// Child CSS transformations spec
const childToCSS = {
area: area => `grid-area: ${area};`
}
// Grid CSS transformations spec
const columnsToCSS = columns => `grid-template-columns: ${columns};`
const rowsToCSS = rows => `grid-template-rows: ${rows};`
const areasToCSS = areas => `grid-template-areas: ${areas};`
const gridToCSS = {
gap: gap => `grid-gap: ${gap};`,
columns: columnsToCSS,
rows: rowsToCSS,
areas: areasToCSS,
template: R.pipe(
parseTemplate,
R.evolve({ columns: columnsToCSS, rows: rowsToCSS, areas: areasToCSS }),
R.values,
R.join('')
),
children: R.pipe(
R.addIndex(R.map)((child, i) => {
// fallback to displayName if area prop isn't provided
const area = R.defaultTo(child.type.displayName)
const props = { ...child.props, area: area(child.props.area) }
return `
& > *:nth-child(${i+1}) {
${computeCSS(childToCSS)(props)}
}
`
}),
R.join('')
)
}
const styles = css`
display: grid;
${computeCSS(gridToCSS)}
`
const cleanUpChild = R.over(R.lensProp('props'), R.omit(R.keys(childToCSS)))
const Grid = styled(({ children, className }) => {
return (
<div {...{ className }}>
{React.Children.map(children, cleanUpChild)}
</div>
)
})`${styles}`
export default Grid
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>
</html>
import React from 'react'
import { render } from 'react-dom'
import App from './App'
render(<App />, document.querySelector('#app'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment