emotion-js / emotion

👩‍🎤 CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.38k stars 1.11k forks source link

Styled components css-helper #2198

Open jukkahuuskonen opened 3 years ago

jukkahuuskonen commented 3 years ago

The problem I'm considering moving from Styled Components to Emotion (mostly due to Material Ui switching to emotion), but I've been using Styled Components css-helper (css, not same as css in emotion) quite a lot. It returns a variable with css-style code that can be used directly when using styled- or css-function and even given as a prop to a styled component and directly inserted into styled``-tag. For example overriding some Material-ui components. Here is an example for Material Ui Button extended with extra colors (prop nsVariant).

import styled from 'styled-components';
import PropTypes from 'prop-types';
import MuiButton from '@material-ui/core/Button';
import { VARIANT_NAMES } from 'shared/styledTheme/themeConstants';
import { buttonColorVariant, buttonHover } from './cssFunctions/cssFunctions';

const filter = ['area', 'nsVariant', 'overrideColor', 'width'];

const Button = styled(MuiButton).withConfig({
  shouldForwardProp: prop => !filter.includes(prop),
})`
  ${({ area }) => (area === 'none' ? 'display: none;' : '')}
  ${buttonColorVariant}
  margin: 1px;
  padding: 2px 7px;
  width: ${({ width = 'auto' }) => width};
  & span {
    font-size: ${({ fontSize = 'smaller' }) => fontSize};
  }
  ${buttonHover}
`;

Button.propTypes = {
  nsVariant: PropTypes.oneOf(VARIANT_NAMES),
  overrideColor: PropTypes.string,
  width: PropTypes.string,
  fontSize: PropTypes.string,
};

export default Button;

And here is that cssFunctions-file with buttonHover and buttonColorVariant styles:

import { css } from 'styled-components';
import hexToRgba from 'hex-to-rgba';

export const backgroundColorObject = ({ theme: { palette }, nsVariant }) => {
  if (nsVariant && nsVariant !== 'initial') {
    return palette[nsVariant];
  }
  return {
    main: '',
    light: '',
    dark: '',
  };
};

export const buttonHover = ({ nsVariant, variant: muiVariant }) => {
  if (nsVariant && nsVariant === 'initial') {
    return '';
  }
  let colorsCss = '';
  switch (muiVariant) {
    case 'contained':
      colorsCss = css`
        background-color: ${props => backgroundColorObject(props).dark};
      `;
      break;
    case 'outlined':
      colorsCss = css`
        border-color: ${props => backgroundColorObject(props).dark};
        background-color: ${props => hexToRgba(backgroundColorObject(props).main, 0.08)};
        color: '${props => backgroundColorObject(props).main}';
      `;
      break;
    default:
      colorsCss = css`
        background-color: ${props => hexToRgba(backgroundColorObject(props).main, 0.08)};
        color: ${props => backgroundColorObject(props).main};
      `;
      break;
  }
  const hoverCss = css`
    &:hover {
      ${colorsCss}
    }
  `;
  return hoverCss;
};

export const buttonColorVariant = ({
  theme: { palette },
  nsVariant,
  variant: muiVariant,
  overrideColor,
}) => {
  if (nsVariant && nsVariant !== 'initial') {
    let buttonCss = '';
    let colorCss = css`
      color: ${palette[nsVariant].main};
    `;
    if (overrideColor) {
      colorCss = css`
        color: ${overrideColor};
      `;
    }
    switch (muiVariant) {
      case 'contained':
        if (!overrideColor) {
          colorCss = css`
            color: ${palette[nsVariant].contrastText};
          `;
        }
        buttonCss = css`
          background-color: ${palette[nsVariant].main};
          ${colorCss}
        `;
        break;
      case 'outlined':
        buttonCss = css`
          border-color: ${palette[nsVariant].main};
          ${colorCss}
        `;
        break;
      default:
        buttonCss = css`
          ${colorCss}
        `;
        break;
    }
    return buttonCss;
  }
  return '';
};

Emotion seems to be missing Styled components style css-helper function. Emotion css-function doesn't work in this case. And neither does using the css-function from styled components in emotion styled-function.

Proposed solution

I would like to have the similiar function as Styled components has also in Emotion.

Additional context I think the problem here lies on emotion stringifing those dynamic props and functions in them getting stringified unlike in styled components. Not sure though. Is there any other way to achieve things in my code in Emotion.

jukkahuuskonen commented 3 years ago

Looks like I finally got it working after all. ~I think this can be closed. I don't yet know what was problem, but I'll try to add here a post about what I did to get it working so if others face the same problem they may find help in this.~

The problem for me was (I think), that emotion's css-function solves the functions inside it (${...}-format in backticks) and styled components version does not. css-function in styled components just creates a variable that can be directly inserted into other css-function or styled-function backtick notions. Only the styled-function actually solves the ${...}-variables (and passes props to them).

Here is an example of a working buttonHover-function in emotion:

export const buttonHover = ({ theme, nsVariant, variant: muiVariant }) => {
  const isInitial = nsVariant && nsVariant === 'initial';
  if (isInitial) {
    return '';
  }
  let colorsCss = '';
  switch (muiVariant) {
    case 'contained':
      colorsCss = css`
        ${!isInitial ? `background-color: ${theme.palette[nsVariant].dark};` : ''};
      `;
      break;
    case 'outlined':
      colorsCss = css`
        border-color: ${!isInitial ? theme.palette[nsVariant].dark : ''};
        ${!isInitial ? `background-color: ${hexToRgba(theme.palette[nsVariant].main, 0.08)};` : ''};
        ${!isInitial ? `color: ${theme.palette[nsVariant].main};` : ''};
      `;
      break;
    default:
      colorsCss = css`
        background-color: ${hexToRgba(!isInitial ? theme.palette[nsVariant].main : '', 0.08)};
        color: ${!isInitial ? theme.palette[nsVariant].main : ''};
      `;
      break;
  }
  const hoverCss = css`
    &:hover {
      ${colorsCss}
    }
  `;
  return hoverCss;
};
Andarist commented 3 years ago

A thing like this:

      colorsCss = css`
        border-color: ${props => backgroundColorObject(props).dark};
        background-color: ${props => hexToRgba(backgroundColorObject(props).main, 0.08)};
        color: '${props => backgroundColorObject(props).main}';
      `;

doesn't make much sense. You already have access to props in your function so you can use them directly instead of putting a lazy function there.

jukkahuuskonen commented 3 years ago

A thing like this:

      colorsCss = css`
        border-color: ${props => backgroundColorObject(props).dark};
        background-color: ${props => hexToRgba(backgroundColorObject(props).main, 0.08)};
        color: '${props => backgroundColorObject(props).main}';
      `;

doesn't make much sense. You already have access to props in your function so you can use them directly instead of putting a lazy function there.

In styled components it does make sense. css-function in styled components doesn't solve the backtick notation, but just returns it in a format that can be inserted as a variable inside styled/css-function backtick notation. Those functions get solved in styled-function, not in css-function. They receive the props from styled.

The purpose here was that the backgroundColorObject()-function can be used independently inside the styled-function or inside other css-functions and I was just reusing that part of the code here.

I can see this would be a useful pattern to get into emotion too, and might help others to migrate to emotion.

ps. updated my previous comment with a working code.