t3-appとcvaとstorybookを使ってみる

## やってみた理由

Next.js Conf 2022でもお話があったり
t3-appのドキュメントでも紹介されたので手を出してみようかなと。

ただ、cvaをインストールしても、コンポーネントの確認が必要になってくるので、
この機会にstorybookもインストールします(今まで触ったことがない)

https://github.com/joe-bell/cva#getting-started

https://create.t3.gg/en/other-recs#class-variance-authority

## 手順

1. t3-appの作成
    `default`フラグをつけて、tailwindcss,next-auth,prisma,trpcを全てインストールする(つけなかったら、何をインストールするか聞かれる)
    ```shell
    yarn create t3-app --default <app_name>
    ```

1. `class-variance-authority`をインストール
    ```shell
    yarn add class-variance-authority
    ```
1. class-variance-authorityが使えるか確認してみる
    1. Buttonコンポーネントの作成(READMEからREADMEから取ってきた奴)
        ```tsx:./src/components/button.tsx
        import React from "react";
        import { cva, type VariantProps } from "class-variance-authority";
        
        // ⚠️ Disclaimer: Use of Tailwind CSS is optional
        const button = cva(["font-semibold", "border", "rounded"], {
          variants: {
            intent: {
              primary: [
                "bg-blue-500",
                "text-white",
                "border-transparent",
                "hover:bg-blue-600",
              ],
              // **or**
              // primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600",
              secondary: [
                "bg-white",
                "text-gray-800",
                "border-gray-400",
                "hover:bg-gray-100",
              ],
            },
            size: {
              small: ["text-sm", "py-1", "px-2"],
              medium: ["text-base", "py-2", "px-4"],
            },
          },
          compoundVariants: [
            {
              intent: "primary",
              size: "medium",
              class: "uppercase",
              // **or** if you're a React.js user, `className` may feel more consistent:
              // className: "uppercase"
            },
          ],
          defaultVariants: {
            intent: "primary",
            size: "medium",
          },
        });
        
        export interface ButtonProps
          extends React.ButtonHTMLAttributes<HTMLButtonElement>,
            VariantProps<typeof button> {}
        
        export const Button: React.FC<ButtonProps> = ({
          className,
          intent,
          size,
          ...props
        }) => <button className={button({ intent, size, className })} {...props} />;
        ```
    
        pagesディレクトリで適当にコンポーネントを呼び出す
        ```tsx:./src/pages/test.tsx
        import { Button } from "../components/button";
        
        const Page = () => {
          return (
            <section>
              <div><Button intent={"primary"}>Test</Button></div>
              <div><Button intent={"secondary"}>Test</Button></div>
            </section>
          );
        };
        
        export default Page;
        ```

    1. Next.jsを起動させて作成したtestページにアクセスしてみる
        http://localhost:3000/test
        ```shell
        yarn dev
        ```
        こんな感じで表示されます。
        作ったコンポーネントをいちいちNext.jsのページに作成して確認したくないので、
        storybookをインストールしましょう
        ![スクリーンショット 2023-01-15 1.12.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565925/8e51f8f3-91ee-d85d-79c3-8c17c776d8e7.png)

1. storybookのインストール
    ```shell
    yarn dlx storybook init
    ```
    eslint使ってるか聞かれると思うので、y/nで答えて終了を待つ
    (デフォルトではインストールされてないのでn)

    initコマンドで、package.jsonの書き込みや、`./.storybook/`,`./src/stories/`が作成される

1. storybookの起動
    ```shell
    yarn run storybook
    ```
    
    こんな感じで初期画面で表示されるので、
    自分が作成したボタンも表示させてみる
    ![スクリーンショット 2023-01-15 1.21.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565925/bf79dadb-3d67-2c29-e14a-4dc4d6c1be00.png)

1. storyを作成する
    ```tsx:./src/components/Button.stories.tsx
    import { ComponentStory, ComponentMeta } from "@storybook/react";
    import { Button } from "./button";
    
    export default {
      title: "Example/custom-button",
      component: Button,
      argTypes: {},
    } as ComponentMeta<typeof Button>;
    
    const Template: ComponentStory<typeof Button> = (args) => (
      <Button {...args}>Button</Button>
    );
    
    export const Primary = Template.bind({});
    Primary.args = {
      intent: "primary",
      size: "medium",
    };
    
    export const Secondary = Template.bind({});
    Secondary.args = {
      intent: "secondary",
    };
    ```

    もう一度storybookを確認する(hotreloadされてるのでコマンド終了してしまった人は再度起動して確認)
    ![スクリーンショット 2023-01-15 1.25.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565925/7a82a762-0572-15c8-063a-847322e3d859.png)

    普通のボタンだ...
    Chromeの検証で確認するとclassは反映されているので、
    CSSが読み込まれてないことに気づく...
    ![スクリーンショット 2023-01-15 1.27.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565925/0ef8f447-da2b-1790-386b-f81fcd77dd19.png)

    postcss用のアドオンをインストール
    ```shell
    yarn add --dev @storybook/addon-postcss
    ```

    設定ファイルを編集
    ```diff_javascript:./.storybook/main.js
    module.exports = {
      stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
      addons: [
        "@storybook/addon-links",
        "@storybook/addon-essentials",
        "@storybook/addon-interactions",
    +     {
    +       name: "@storybook/addon-postcss",
    +       options: {
    +         postcssLoaderOptions: {
    +           implementation: require("postcss"),
    +         },
    +       },
    +     },
      ],
      framework: "@storybook/react",
      core: {
        builder: "@storybook/builder-webpack5",
      },
    };

    ```

    ```diff_javascript:./.storybook/preview.js
    + import "../src/styles/globals.css";
    
    export const parameters = {
      actions: { argTypesRegex: "^on[A-Z].*" },
      controls: {
        matchers: {
          color: /(background|color)$/i,
          date: /Date$/,
        },
      },
    }
    ```

    この状態でstorybookを確認すると,,,

    無事cssが適応されてますね
    ![スクリーンショット 2023-01-15 1.33.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565925/c137c2f2-f44c-436a-4c65-7cb0e7207326.png)

1. 最後に不要なファイルや設定を削除する(必要な人は)
    デフォルトのstoriesフォルダを削除
    ```shell
    rm -rf ./src/stories/
    ```

    storiesフォルダをもっと明確に指定
    ```diff_javascript:./.storybook/main.js
    module.exports = {
    +   stories: ["../src/components/**/*.stories.@(js|jsx|ts|tsx)"],
      addons: [
        "@storybook/addon-links",
        "@storybook/addon-essentials",
        "@storybook/addon-interactions",
        {
          name: "@storybook/addon-postcss",
          options: {
            postcssLoaderOptions: {
              implementation: require("postcss"),
            },
          },
        },
      ],
      framework: "@storybook/react",
      core: {
        builder: "@storybook/builder-webpack5",
      },
    };
    ```
    

## まとめ

以上、t3-appのプロジェクトにclass-variance-authorityとstorybookを入れる流れでした。
storybookには触れたことがなかったんですが、
class-variance-authorityでよりtailwindcssが使いやすくなりそうだったので導入してみました。
記事では述べていませんが、headlessuiと組み合わせることでより効果を発揮してくれそうです。