Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emotion CSS-in-JS - how to add conditional CSS based on component props?

I'd like to have a component, styled with Emotion, that takes props that ultimately control the styling. For example, consider a GridCol component that has various props that change the padding and width (the width can be changed across different viewport widths).

I'd like to use an API like this:

<GridCol gutter size="2>

// or alternatively, like this:

<GridCol gutter size={{
  m: 2,
  l: 4
}}>

There are three things happening here:

  • the gutter is a boolean prop that adds some horizontal padding to the column
  • the size prop can be a string or an object. If it is a string, we just add a few lines of CSS and we're good, however, if it is an object, we need to insert some media-queries based on a breakpoints object that is set elsewhere.

Emotion's docs are not clear how to handle styling of this nature, at least I have not seen it, so I was hoping that a common solution could be found.

For the gutter prop, it is trivial:

const GridCol = props => styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props.gutter ? `0 10px` : '0'};
`

For the size prop, it becomes more complicated, I'd like the resultant CSS to look something like this:

const GridCol = props => styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props.gutter ? `0 10px` : '0'};

  /* styles here if  `size` is a string */
  width: 10%;

  /* styles here if  `size` is an object */
  @media screen and (min-width: 500px) {
      width: 20%;
  }


  @media screen and (min-width: 800px) {
      width: 30%;
  }


  @media screen and (min-width: 1100px) {
      width: 40%;
  }
`

The width values will be determined by the prop's key, which corresponds to a value in a breakpoints object, this part is not trivial, but I don't know how to dynamically generate the css needed.

I'm sure there's more info that I could add, I have made some attempts but none of them are working at the moment. My feeling is that I should create a stateless functional component that generates the css for each condition, then joins the CSS at the end..

like image 262
Zander Avatar asked Nov 27 '17 13:11

Zander


3 Answers

If you are looking for a way to implement conditional styles with Emotion's css prop, you can do something like this:

import { css } from '@emotion/core';

const styles = ({ isSelected }) => css`
  border: solid 1px black;
  border-radius: 10px;
  padding: 16px;
  cursor: pointer;
  ${isSelected === true &&
  `
    background-color: #413F42;
    color: white;
  `}
`;

const ConditionalComponent = () => {
  const [isSelected, setIsSelected] = useState(false);
  return (
    <div css={styles({ isSelected })} onClick={() => setIsSelected(!isSelected)}>
      Click here to change styles.
    </div>
  );
};

More details on this here: https://seanconnolly.dev/emotion-conditionals

like image 137
Sean Avatar answered Nov 18 '22 21:11

Sean


This is a great question. First, avoid this pattern.

const GridCol = props => styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props.gutter ? `0 10px` : '0'};
`

In this example, a new styled component is created on every render which is terrible for performance.

Any expression, or interpolation, can be a function. This function will receive 2 arguments: props and context

const GridCol = styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props => props.gutter ? `0 10px` : '0'};
`

As for the size prop in your example, I would use the following pattern.

import { css } from 'emotion'

const sizePartial = (props) => typeof props.size === 'string' ?
  css`width: 10%;` :
  css`
   @media screen and (min-width: 500px) {
      width: 20%;
   }


   @media screen and (min-width: 800px) {
      width: 30%;
   }


   @media screen and (min-width: 1100px) {
      width: 40%;
   }
 `

You can then use the partial just like any other function that occurs in an expression.

const GridCol = styled('div')`
  display: block;
  box-sizing: border-box;
  flex: 1 0 0;
  min-width: 0;
  padding: ${props => props.gutter ? `0 10px` : '0'};
  ${sizePartial};
`

This is an extremely powerful pattern that can be used to compose reusable dynamic styles across your project.

If you are interested in other libraries that leverage this pattern check out https://github.com/emotion-js/facepaint and https://github.com/jxnblk/styled-system

like image 41
tkh44 Avatar answered Nov 18 '22 22:11

tkh44


Emotion has a helper called cx that provides functionality similar to the popular classnames library. You can use this to write conditionals more easily:

import { cx, css } from '@emotion/css'

const cls1 = css`
  font-size: 20px;
  background: green;
`

const foo = true
const bar = false

const SomeComponentWithProp = ({ foo }) => (
  <div
    className={cx(
      { [cls1]: foo === 'bar' },
    )}
  />
);

(Adapted from the linked docs)

like image 2
Jack Guy Avatar answered Nov 18 '22 23:11

Jack Guy