I'd like to have a component, styled with Emotion, that takes props that ultimately control the styling. For example, consider a GridCol
component that has various props that change the padding and width (the width can be changed across different viewport widths).
I'd like to use an API like this:
<GridCol gutter size="2>
// or alternatively, like this:
<GridCol gutter size={{
m: 2,
l: 4
}}>
There are three things happening here:
gutter
is a boolean prop that adds some horizontal padding to the columnsize
prop can be a string or an object. If it is a string, we just add a few lines of CSS and we're good, however, if it is an object, we need to insert some media-queries based on a breakpoints object that is set elsewhere.Emotion's docs are not clear how to handle styling of this nature, at least I have not seen it, so I was hoping that a common solution could be found.
For the gutter
prop, it is trivial:
const GridCol = props => styled('div')`
display: block;
box-sizing: border-box;
flex: 1 0 0;
min-width: 0;
padding: ${props.gutter ? `0 10px` : '0'};
`
For the size
prop, it becomes more complicated, I'd like the resultant CSS to look something like this:
const GridCol = props => styled('div')`
display: block;
box-sizing: border-box;
flex: 1 0 0;
min-width: 0;
padding: ${props.gutter ? `0 10px` : '0'};
/* styles here if `size` is a string */
width: 10%;
/* styles here if `size` is an object */
@media screen and (min-width: 500px) {
width: 20%;
}
@media screen and (min-width: 800px) {
width: 30%;
}
@media screen and (min-width: 1100px) {
width: 40%;
}
`
The width
values will be determined by the prop's key, which corresponds to a value in a breakpoints
object, this part is not trivial, but I don't know how to dynamically generate the css needed.
I'm sure there's more info that I could add, I have made some attempts but none of them are working at the moment. My feeling is that I should create a stateless functional component that generates the css for each condition, then joins the CSS at the end..
This is a great question. First, avoid this pattern.
const GridCol = props => styled('div')`
display: block;
box-sizing: border-box;
flex: 1 0 0;
min-width: 0;
padding: ${props.gutter ? `0 10px` : '0'};
`
In this example, a new styled component is created on every render which is terrible for performance.
Any expression, or interpolation, can be a function. This function will receive 2 arguments: props
and context
const GridCol = styled('div')`
display: block;
box-sizing: border-box;
flex: 1 0 0;
min-width: 0;
padding: ${props => props.gutter ? `0 10px` : '0'};
`
As for the size
prop in your example, I would use the following pattern.
import { css } from 'emotion'
const sizePartial = (props) => typeof props.size === 'string' ?
css`width: 10%;` :
css`
@media screen and (min-width: 500px) {
width: 20%;
}
@media screen and (min-width: 800px) {
width: 30%;
}
@media screen and (min-width: 1100px) {
width: 40%;
}
`
You can then use the partial just like any other function that occurs in an expression.
const GridCol = styled('div')`
display: block;
box-sizing: border-box;
flex: 1 0 0;
min-width: 0;
padding: ${props => props.gutter ? `0 10px` : '0'};
${sizePartial};
`
This is an extremely powerful pattern that can be used to compose reusable dynamic styles across your project.
If you are interested in other libraries that leverage this pattern check out https://github.com/emotion-js/facepaint and https://github.com/jxnblk/styled-system