Search code examples
reactjsstyled-components

How can I dynamically change the style component used in a component through props?


I am trying to refactor some css to use styled components so I can just deploy the cloud component and declare its size styles and what animation styles it uses through props so it looks like this:

<Cloud size={TinyCloud} animation={cloud_1}/>

I have a base style to define a cloud:

const Cloud = styled(css)`
  -webkit-animation: clouds 60s infinite linear;
  -moz-animation: clouds 60s infinite linear;
  -ms-animation: clouds 60s infinite linear;
  -o-animation: clouds 60s infinite linear;
  animation: clouds 60s infinite linear;
  -moz-border-radius: 10px;
  -webkit-border-radius: 10px;
  border-radius: 10px;
  position: relative;
  margin: 20px 20px 10px 2050px;
  width: 54px;
  height: 5px;
  background: black;
  & div {
    -moz-box-shadow: inset -2px -3px 0 0 #f7e7eb;
    -webkit-box-shadow: inset -2px -3px 0 0 #f7e7eb;
    box-shadow: inset -2px -3px 0 0 #f7e7eb;
    position: absolute;
    border-radius: 50%;
    width: 12px;
    height: 12px;
    left: -3px;
    bottom: 0;
    background: #fafbf0;
    z-index: 10;
  }
  & div:first-child + div {
    -moz-transform: scale(1.6, 1.6);
    -ms-transform: scale(1.6, 1.6);
    -webkit-transform: scale(1.6, 1.6);
    transform: scale(1.6, 1.6);
    margin: 0 0 4px 13px;
    z-index: 9;
  }
  & div:first-child + div + div {
    -moz-transform: scale(2.4, 2.4);
    -ms-transform: scale(2.4, 2.4);
    -webkit-transform: scale(2.4, 2.4);
    transform: scale(2.4, 2.4);
    margin: 0 0 9px 32px;
    z-index: 8;
  }
  & div:first-child + div + div + div {
    -moz-transform: scale(1.3, 1.3);
    -ms-transform: scale(1.3, 1.3);
    -webkit-transform: scale(1.3, 1.3);
    transform: scale(1.3, 1.3);
    margin: 0 0 2px 50px;
    z-index: 7;
  }
  @-webkit-keyframes clouds {
    0% {
        left: -50%;
    }
    100% {
        left: 20%;
    }
  }
  @-moz-keyframes clouds {
    0% {
        left: -50%;
    }
    100% {
        left: 20%;
    }
  }
  @-ms-keyframes clouds {
    0% {
        left: -50%;
    }
    100% {
        left: 20%;
    }
  }
  @keyframes clouds {
    0% {
        left: -50%;
    }
    100% {
        left: 20%;
    }
  }
`;

This style is then extended by four more styles as such:

const TinyCloud = styled(Cloud)`
  -moz-transform: scale(0.35);
  -ms-transform: scale(0.35);
  -webkit-transform: scale(0.35);
  transform: scale(0.35);
`;

I also have 12 more styles to extend the now styled base Cloud (Now TinyCloud) as such:

const cloud_1 = styled(css)`
  -webkit-animation-duration: 10s;
  -moz-animation-duration:    10s;
  -ms-animation-duration:     10s;
  -o-animation-duration:      10s;
  animation-duration:         10s;
  margin-left: 40%;
`;

The sizes and animations are in separate files and exported so they can be passed as props.

What I tried was defined like this (I realize that I cannot do this but I include this to show my thinking):

const Cloud = (size, animation) => {
  const CloudFrame = styled(size)`
  ${animation}
  `
  return (
    <CloudFrame>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </CloudFrame>
  )
};

Instead of using classes like this:

<div class="cloud tiny cloud-4">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

Solution

  • With styled-components, the usual per-instance customization uses adaptation based on props.

    Unless your sizes and cloud id customizations are more complex than your samples, you could use this adaptation technique easily:

    const CloudFrame = styled.div`
      animation: clouds 60s infinite linear;
      // etc.
    
      transform: scale(${props => props.scale});
    
      animation-duration: ${props => props.duration};
      margin-left: ${props => props.left};
    `;
    
    // Usage
    function App() {
      return (
        <CloudFrame scale={0.35} duration="10s" left="40%">
          <div></div>
          <div></div>
          <div></div>
          <div></div>
        </CloudFrame>
      )
    }
    

    With this, you do not even need to predefine hard coded sets of sizes and cloud ids: you can define those values arbitrarily when you instantiate the component.