Reputation: 7007
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.
Upvotes: 1
Views: 7867
Reputation: 1413
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>
Upvotes: 5
Reputation: 19224
For iterating through all your media queries, you can create a function similar to:
import { css } from "styled-components";
const sizes = {
desktop: 992,
tablet: 768,
phone: 576
};
// Iterate through the sizes and create a media template
const media = Object.keys(sizes).map(screenLabel => {
return {
query: (...args) => css`
@media (max-width: ${sizes[screenLabel] / 16}em) {
${css(...args)}
}
`,
screenLabel
};
});
export default media;
For usage in the component:
import media from "./media";
// The labels for this has to be same as the ones in sizes object
const colors = {
phone: "red",
tablet: "yellow",
desktop: "green"
};
const Heading = styled.h2`
color: blue;
${media.map(
({ query, screenLabel }) => query`
color: ${colors[screenLabel]};
`
)}
`;
Upvotes: 0