Search code examples
reactjsstyled-componentsstyled-system

styled-system | Resolve theme style function within component


I'm trying to figure out how to resolve a styled-system function within a component by default. The idea is to have a container element which uses the theme out of the box to supply the main responsive breaks.

Right now what I am doing below is supplying the styled-system width function with an object like this:

//theme.js
const CONTAINERS = {
  xs: "100%",
  sm: "55rem",
  md: "74rem",
  lg: "100rem",
  xl: "131rem",
};
export default {
 containers: CONTAINERS,
//other items
}
//Container.js
export default (props) => {
  const { containers, grid } = useContext(ThemeContext);
  return (
    <Container px={grid.margin} width={containers} {...props}>
      {props.children}
    </Container>
  );
};

const Container = styled("div")(
  css`
    margin-right: auto;
    margin-left: auto;
    padding-right: 1rem;
    padding-left: 1rem;
  `,
  compose
);

This DOES work, however, it's not as clean as I would like it. I would love to be able to simply have

const Container = styled("div")(
  css`
    margin-right: auto;
    margin-left: auto;
    padding-right: 1rem;
    padding-left: 1rem;
    ${//maybe resolve the responsive widths here}
  `,
  compose //<- a group of styled-system functions supplied in one object
  //or resolve responsive widths here
);

export default Container

This would be so much cleaner as I could then combine and export layout components into one file without having to do the const Container... + const StyledContainer... formalities.

I'm toying with the idea of writing a function which loops the containers object and returns the widths wrapped in media queries but I'm wondering if styled-system does this out of the box?


Solution

  • I can't find anything in the docs to make something like this work out of the box so I've implemented it manually.

    //theme.js
    const BREAKPOINTS = {
      xs: "0em",
      sm: "37em",
      md: "48em",
      lg: "64.625em",
      xl: "84em",
    };
    
    const CONTAINERS = {
      xs: "100%",
      sm: "55rem",
      md: "74rem",
      lg: "100rem",
      xl: "131rem",
    };
    

    The script below takes the objects above and puts the breakpoints values into a wrapping @media then inserts the values of the containers as width: inside of that. Then returns the entire lot as css.

    const Container = styled("div")(
      css`
        margin-right: auto;
        margin-left: auto;
        padding-right: 1rem;
        padding-left: 1rem;
    
        &::after,
        &::before {
          content: "";
          display: block;
          min-height: 2rem;
          height: 4vw;
          max-height: 6rem;
        }
      `,
      compose,
      (props) => {
        const breakpoints = props.theme.breakpoints;
        const containers = props.theme.containers;
    
        const makeMedia = (media) => {
          return (...args) => css`
            @media ${media} {
              ${css(...args)}
            }
          `;
        };
    
        const getMedia = Object.keys(breakpoints).reduce((media, breakpoint) => {
          const breakpointWidth = breakpoints[breakpoint];
          media[breakpoint] = makeMedia(
            [breakpoint !== 0 && `(min-width: ${breakpointWidth})`]
              .filter(Boolean)
              .join(" and ")
          );
          return media;
        }, {});
    
        return css`
          ${Object.keys(breakpoints).map(
            (key) => containers[key] && getMedia[key]`width: ${containers[key]}`
          )};
        `;
      }
    );