为样式化组件创建响应式道具

Creating Responsive Props for Styled Components

我正在尝试为样式化组件创建响应式道具,如下所示。首先,我们有一个组件(比如说一个按钮):

<Button primary large>Click Me</Button>

此按钮将获得主背景色和大尺寸(由主题文件确定)。

我现在想创建此按钮的响应版本。这就是我希望它的工作方式:

<Button 
  primary 
  large 
  mobile={{size: 'small', style: 'secondary'}}
  tablet={size: 'small'}} 
  widescreen={{style: 'accent'}}
>
  Click Me
</Button>

我现在有相同的按钮,但样式和大小因屏幕尺寸而异。

现在,我已经开始工作了——但是它涉及很多重复代码。这是它的外观示例:

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

我正在寻找一种简化此代码的方法。基本上,我只想一次编写 CSS 样式,然后根据这样的查询对象生成不同的道具和媒体查询:

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
  }
}

我想我应该能够创建一个循环遍历 mediaQueries 对象并为每次迭代插入适当的 css 的函数。但是,我似乎不知道该怎么做。

关于如何做到这一点有什么想法吗?

此外,提前感谢您提供的任何帮助。

也许您正在寻找这样的东西:

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
    }, {});

然后你只需像这样导入和使用:

import media from './media'
const button = styled.button`
   ${({large , small})=> media.mobile`
      color: red;
      font-size: ${large ? '2em' : '1em'};
   `}
`

这里有一些进一步的阅读,包括与主题一起使用:

Media queries in styled-components

使用道具:

使用与上面相同的媒体查询对象:

创建辅助函数以将样式对象格式化为 css 字符串:

const formatCss = (styleObject) => {
    return JSON.stringify(styleObject)
        .replace(/[{}"']/g,'')
        .replace(/,/g,';') 
        + ';'
}

创建另一个辅助函数来映射样式并通过映射其键并使用括号表示法动态添加查询来生成查询:

const mapQueries = (myQueries) =>{
    return Object.keys(myQueries).map(key=> media[key]`
        ${formatCss(myQueries[key])}
    `)
}

在您的样式组件中:

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

最后像这样向您的组件添加一个 myQueries 属性(为了简单起见,请注意使用 css-formatted 键而不是 javascriptFormatted 键):

<Button myQueries={{
    mobile:{ color:'red' },
    tablet:{ color:'blue', "background-color":'green'},
    desktop:{ height:'10rem' , width:'100%'}
}}>Button</Button>

要遍历所有媒体查询,您可以创建类似于以下的函数:

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;

在组件中的用法:

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