I am trying to create responsive props for styled components as follows. To start with, we have a component (let's say a button):
<Button primary large>Click Me</Button>
This button will get a background-color of primary and a large size (as determined by a theme file).
I now want to create a responsive version of this button. This is how I would like that to work:
<Button
primary
large
mobile={{size: 'small', style: 'secondary'}}
tablet={size: 'small'}}
widescreen={{style: 'accent'}}
>
Click Me
</Button>
I now have my same button, but with the styles and sizes varied for different screen sizes.
Now, I have gotten this to work -- but it involves a lot of duplicate code. This is an example of what it looks like:
const Button = styled('button')(
({
mobile,
tablet,
tabletOnly,
desktop,
widescreen
}) => css`
${mobile &&
css`
@media screen and (max-width: ${theme.breakpoints.mobile.max}) {
background-color: ${colors[mobile.style] || mobile.style};
border: ${colors[mobile.style] || mobile.style};
border-radius: ${radii[mobile.radius] || mobile.radius};
color: ${mobile.style && rc(colors[mobile.style] || mobile.style)};
}
`}
${tablet &&
css`
@media screen and (min-width: ${theme.breakpoints.tablet.min}), print {
background-color: ${colors[tablet.style] || tablet.style};
border: ${colors[tablet.style] || tablet.style};
border-radius: ${radii[tablet.radius] || tablet.radius};
color: ${tablet.style && rc(colors[tablet.style] || tablet.style)};
}
`}
${tabletOnly &&
css`
@media screen and (min-width: ${theme.breakpoints.mobile.min}) and (max-width: ${theme.breakpoints.tablet.max}) {
background-color: ${colors[tabletOnly.style] || tabletOnly.style};
border: ${colors[tabletOnly.style] || tabletOnly.style};
border-radius: ${radii[tabletOnly.radius] || tabletOnly.radius};
color: ${tabletOnly.style &&
rc(colors[tabletOnly.style] || tabletOnly.style)};
}
`}
`
What I am looking for is a way to simplify this code. Basically, I want to only write the CSS styles ONCE and then generate the different props and media queries based off of a query object that something like this:
const mediaQueries = {
mobile: {
min: '0px',
max: '768px'
},
tablet: {
print: true,
min: '769px',
max: '1023px'
},
desktop: {
min: '1024px',
max: '1215px'
},
widescreen: {
min: '1216px',
max: '1407px'
},
fullhd: {
min: '1408px',
max: null
}
}
I imagine I should be able to create a function that loops through through the mediaQueries
object and inserts the appropriate css for each iteration. However, I can't seem to figure out how to do this.
Any ideas on how to do this?
Also, thanks in advance for any help you can offer.
Maybe something like this is what you are looking for:
import { css } from "styled-components";
//mobile first approach min-width
const screenSizes = {
fullhd: 1408,
widescreen: 1215,
desktop: 1023,
tablet: 768,
mobile: 0
}
const media = Object
.keys(screenSizes)
.reduce((acc, label) => {
acc[label] = (...args) => css`
@media (min-width: ${screenSizes[label] / 16}rem) {
${css(...args)}
}
`
return acc
}, {});
Then you just import and use like so:
import media from './media'
const button = styled.button`
${({large , small})=> media.mobile`
color: red;
font-size: ${large ? '2em' : '1em'};
`}
`
Here's some further reading including using with theming:
Media queries in styled-components
Utilizing Props:
Using the same media query object from above:
Create a helper function to format the styles object to a css string:
const formatCss = (styleObject) => {
return JSON.stringify(styleObject)
.replace(/[{}"']/g,'')
.replace(/,/g,';')
+ ';'
}
Create another helper function to map over the styles and generate queries by mapping over its keys and using bracket notation dynamically add queries:
const mapQueries = (myQueries) =>{
return Object.keys(myQueries).map(key=> media[key]`
${formatCss(myQueries[key])}
`)
}
In your styled-component:
export const Button = styled.button`
${({myQueries}) => !myQueries ? '' : mapQueries(myQueries)}
`
Finally add a myQueries prop to your component like so (notice the use of css-formatted
keys instead of javascriptFormatted
keys for simplicity):
<Button myQueries={{
mobile:{ color:'red' },
tablet:{ color:'blue', "background-color":'green'},
desktop:{ height:'10rem' , width:'100%'}
}}>Button</Button>