admin管理员组

文章数量:1391995

I am trying to create responsive props for styled ponents as follows. To start with, we have a ponent (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.

I am trying to create responsive props for styled ponents as follows. To start with, we have a ponent (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.

Share Improve this question asked Oct 27, 2019 at 16:14 MosheMoshe 7,05121 gold badges77 silver badges138 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

Maybe something like this is what you are looking for:

import { css } from "styled-ponents";

//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-ponents

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-ponent:

export const Button = styled.button`
    ${({myQueries}) => !myQueries ? '' : mapQueries(myQueries)}
`

Finally add a myQueries prop to your ponent 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>

For iterating through all your media queries, you can create a function similar to:

import { css } from "styled-ponents";

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 ponent:

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]};
  `
  )}
`;

本文标签: javascriptCreating Responsive Props for Styled ComponentsStack Overflow