styled-componentsで条件付きmixinをする方法

# はじめに

styled-componentsで条件付きレンダリングをする場合、以下のように、項目ごとに場合分けをする例をよく見ます
基本はこれで問題ないのですが、場合分けの種類が増えて来ると、管理が面倒になります。
この場合、場合分けの条件ごとにclassのようにスタイリングをまとめたいと思うのが自然だと思います。

そこで、複数の項目をclassの有無のように一気に設定した場合についてわからなかったので、調べました。

``` tsx
const Wrapper = styled.div.attrs((props) => ({
  ...props,
}))`
background: ${(props) => (props.hoge ? '#fff' : '#000')};
`

```

# やりたいこと

以下のようにpropsの条件一つでスタイリングが大きく変わる場合を想定します。
上の場合と下の場合で、個別にcssを管理することを目標とします。

![b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132299/bafd9ff8-d63e-fe8b-f1fe-6d181ffc5ca7.gif)


# まずはデフォルトの状態を準備

まずは、props.typeに何も指定していない、アニメーションのない状態を実装します。

``` tsx
import React, { VFC } from "react";
import styled from "styled-components";

export type ButtonProps = {
  className?: string;
  type?: "bgleft" | "bgright";
};

const Button: VFC<ButtonProps> = ({ className, type }: ButtonProps) => {
  return (
    <WrapperButton className={className} type={type}>
      <span>ボタン</span>
    </WrapperButton>
  );
};

const WrapperButton = styled.a.attrs((props) => ({ ...props }))`
  /* default */
  position: relative;
  overflow: hidden;

  text-decoration: none;
  display: inline-block;

  border: 1px solid #555;
  padding: 10px 30px;
  text-align: center;
  outline: none;
  transition: ease 0.2s;

  span {
    position: relative;
    z-index: 3;
    color: #333;
  }
  :hover {
    span {
      color: #fff;
    }
  }
`;

export default Button;

```

# MixInの定義

次に左右から動作するアニメーションをMixInで定義します。
最初に、styled-componenstからcssをインポートします。

``` tsx
import React, { VFC } from "react";
import styled, { css } from "styled-components"; //ここ
```
次にMixInを定義します。
それぞれ、左右から動作する部分のcssです。

``` tsx
const MixInBgLeft = css`
  &:before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    background-color: #333;
    width: 100%;
    height: 100%;
    transition: transform 0.6s cubic-bezier(0.8, 0, 0.2, 1) 0s;
    transform: scale(0, 1);
    transform-origin: right top;
  }

  &:hover:before {
    transform-origin: left top;
    transform: scale(1, 1);
  }
`;

const MixInBgRight = css`
  &:before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    background-color: #333;
    width: 100%;
    height: 100%;
    transition: transform 0.6s cubic-bezier(0.8, 0, 0.2, 1) 0s;
    transform: scale(0, 1);
    transform-origin: left top;
  }

  &:hover:before {
    transform-origin: right top;
    transform: scale(1, 1);
  }
`;

```

# 条件付きでレンダリング

最後に`styled`の中にMixInを条件付きで追加します。

``` tsx
const WrapperButton = styled.a.attrs((props) => ({ ...props }))`
   //....省略

  /* typeにbgleftを選択した場合 */
  ${(props) => props.type === "bgleft" && MixInBgLeft}

  /* typeにbgrightを選択した場合 */
  ${(props) => props.type === "bgright" && MixInBgRight}
`;


```

このbuttonを使う側は以下のようにして使います。

``` tsx
import Button from '***'


<Button type="bgleft" />
// or
<Button type="bgright" />
```

# 最後に

今回の例だと、最初に紹介した、項目ごとに場合分けする場合でもよかったかも知れませんが、`props.type`が増える場合を考えると、このような方ほでの実装も悪くはないかも知れません。
もっと良い方法があればぜひ共有してください。

最後に全部まとめたコードを貼ります。

``` tsx
import React, { VFC } from "react";
import styled, { css } from "styled-components";

export type ButtonProps = {
  className?: string;
  type?: "bgleft" | "bgright";
};

const Button: VFC<ButtonProps> = ({ className, type }: ButtonProps) => {
  return (
    <WrapperButton className={className} type={type}>
      <span>ボタン</span>
    </WrapperButton>
  );
};

const MixInBgLeft = css`
  &:before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    background-color: #333;
    width: 100%;
    height: 100%;
    transition: transform 0.6s cubic-bezier(0.8, 0, 0.2, 1) 0s;
    transform: scale(0, 1);
    transform-origin: right top;
  }

  &:hover:before {
    transform-origin: left top;
    transform: scale(1, 1);
  }
`;

const MixInBgRight = css`
  &:before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    background-color: #333;
    width: 100%;
    height: 100%;
    transition: transform 0.6s cubic-bezier(0.8, 0, 0.2, 1) 0s;
    transform: scale(0, 1);
    transform-origin: left top;
  }

  &:hover:before {
    transform-origin: right top;
    transform: scale(1, 1);
  }
`;

const WrapperButton = styled.a.attrs((props) => ({ ...props }))`
  /* default */
  position: relative;
  overflow: hidden;

  text-decoration: none;
  display: inline-block;

  border: 1px solid #555;
  padding: 10px 30px;
  text-align: center;
  outline: none;
  transition: ease 0.2s;

  span {
    position: relative;
    z-index: 3;
    color: #333;
  }
  :hover {
    span {
      color: #fff;
    }
  }

  /* typeにbgleftを選択した場合 */
  ${(props) => props.type === "bgleft" && MixInBgLeft}

  /* typeにbgrightを選択した場合 */
  ${(props) => props.type === "bgright" && MixInBgRight}
`;

export default Button;
```