tailwind-variantsを触ってみた

## 1. はじめに
今回は個人開発でTailwind Variantsを触ったので忘れないようにアウトプットします！
TailwindCSS初学者の方や、Tailwind Variantsを触ったことがない方に参考になれば幸いです！

## 2. Tailwind Variantsとは？
### 概要
Tailwind Variantsは、Tailwind CSSのユーティリティクラスを組み合わせて再利用可能なコンポーネントやバリエーションを作成できるもののようです。

### メリット
TailwindCSSだけで書くと背景色だけ変えたい時に条件式を書いたり複数コンポーネントを作成していたところを、Tailwind Variantsを使うことで、1つのコンポーネントで複数のバリエーションを作成することができるので可読性やコンポーネントがめちゃ増えるみたいなことがなくなるので保守性も良くなるのでは！と思ってます。

## 3. Tailwind Variantsの基本的な使い方
### インストール方法
Tailwind Variantsの導入手順（npmまたはyarnでのインストール）。

```bash
npm install tailwind-variants
```
### variantsを使用したシンプルな例
* baseで全てに適応される基本のスタイルを設定できる
* variantsの中に自分の好きな名前でバリエーションを設定できる（今回は文字サイズと色を指定したいので、sizeとcolorで指定）
* defaultVariantsには何も指定しなかった場合にあたるスタイルを設定できる

```tsx
import { tv } from 'tailwind-variants';

const button = tv({
  base: 'px-4 py-2 rounded',
  variants: {
    size: {
      small: "text-sm p-2",
      base: "text-base p-4",
      large: "text-lg p-6",
    },
    color: {
      primary: "bg-blue-500 hover:bg-blue-700",
      secondary: "bg-red-500 hover:bg-red-700",
      success: "bg-green-500 hover:bg-green-700"
    },
  },
  defaultVariants: {
    size: 'large',
    color: 'primary',
  },
});


// 別ファイルでButtonコンポーネントを呼び出してcolorとかsizeをpropsで渡す
import { Button } from "./conponents/Button";

// 色をそれぞれつけてあるので上からprimary（赤）, secondary（青）, success（緑）に設定した色が当たる
<div className="flex gap-5 flex-col">
    <h2 className="font-bold text-lg">色だけつけたやつ</h2>
    <Button>base</Button>
    <Button color="secondary">secondary</Button>
    <Button color="success">success</Button>
</div>

// size="large"を指定しているのでtext-lgが適応される
<div className="flex gap-5 flex-col">
    <h2 className="font-bold text-lg">sizeがlgのやつ</h2>
    <Button size="large">base-disable</Button>
    <Button size="large" color="secondary">
        secondary
    </Button>
    <Button size="large" color="success">
        success
    </Button>
</div>
```

#### disableの時のスタイルも簡単に作成可能

```tsx
import { tv } from 'tailwind-variants';

const button = tv({
  base: 'px-4 py-2 rounded',
  variants: {
    size: {
      small: "text-sm p-2",
      base: "text-base p-4",
      large: "text-lg p-6",
    },
    color: {
      primary: "bg-blue-500 hover:bg-blue-700",
      secondary: "bg-red-500 hover:bg-red-700",
      success: "bg-green-500 hover:bg-green-700"
    },
    disable: {
      true: "pointer-events-none opacity-20",
    },
  },
  defaultVariants: {
    size: 'large',
    color: 'primary',
  },
});

// 別ファイルでButtonコンポーネントを呼び出してcolorとかsizeをpropsで渡す
import { Button } from "./conponents/Button";

// disableがtrueになっているのdisable時のスタイルが当たる
<div className="flex gap-5 flex-col">
    <h2 className="font-bold text-lg">disableがtrueのやつ</h2>
    <Button disable={true}>base-disable</Button>
    <Button color="secondary" disable={true}>
        secondary
    </Button>
    <Button color="success" disable={true}>
        success
    </Button>
</div>
```

#### ↓↓↓ 今のところこんな感じ ↓↓↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3746825/cd84694e-674d-baae-573f-aa656d25e82b.png)



### slotsを使った複数の要素のスタイルを同時に変更するやり方
今回は、入力フォームのlabelやinputにそれぞれスタイルを適用し、エラーが発生した際に基本の色が赤色に変わるようにする実装を、slotsを使用して試してみました。

```tsx
import React from "react";
import { tv } from "tailwind-variants";

const inputField = tv({
  slots: {
    label: "mb-2 block text-sm font-medium text-gray-900 dark:text-white",
    input:
      "block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-black",
    error: "mt-2 text-sm text-red-600 dark:text-red-500",
  },
  variants: {
    size: {
      lg: {
        label: "text-lg",
        input: "text-lg",
      },
      default: {},
    },
    error: {
      true: {
        label: "text-red-700 dark:text-red-500",
        input:
          "border-red-500 bg-red-50 text-red-900 placeholder-red-700 focus:border-red-500 focus:ring-red-500",
      },
      false: {},
    },
  },
  defaultVariants: {
    size: "default",
    error: false,
  },
});

interface inputProps {
  label: string;
  error?: boolean;
  errorMessage?: string;
  size?: "lg" | "default";
  placeholder?: string;
}

export const Input: React.FC<inputProps> = ({
  label,
  errorMessage,
  error,
  size,
}) => {
  const {
    label: labelClass,
    error: errorClass,
    input: inputClass,
  } = inputField();

  return (
    <div>
      <label className={labelClass({ error, size })}>{label}</label>
      <input type="text" className={inputClass({ error, size })} />
      {errorMessage && <div className={errorClass()}>{errorMessage}</div>}
    </div>
  );
};

//他のページで呼び出すとき
import { Input } from "./conponents/Input";

<div className="flex gap-5 flex-col">
　<Input
     label="エラーがあった時"
     errorMessage="エラーです"
     error={true}
  />
　<Input label="エラーがない＋サイズをlgにした場合" size="lg" />
</div>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3746825/3dcfc70f-4d00-7df8-c5d0-9e79c7ac8360.png)



## 7. まとめ
Tailwind Variants・slotsの基本的な使い方をまとめてみました！
Tailwind Variantsには、Compound variantsやレスポンシブ対応など、まだ多くの機能がありますので、気になる方は公式ドキュメントをぜひみてみてください！

## 8. 参考資料
* [Tailwind Variants公式ドキュメント](https://www.tailwind-variants.org/)