React + TypeScript + emotion + storybookの環境構築

# ライブラリとバージョン

| ライブラリ | バージョン |
|:-----------|:------------|
| react       | 18.2.0        |
| react-dom       | 18.2.0        |
| typescript       | 4.7.4        |
| @emotion/react       | 11.9.3        |
| @emotion/babel-preset-css-prop       | 11.2.0        |
| @storybook/react       | 6.5.9        |

# Reactプロジェクト作成

```
$ npx create-react-app react-storybook --template typescript
```

```
$ cd react-storybook
```

# StoryBookをインストール

```
$ npx storybook init
```

↓のコマンドで立ち上げを確認
```
$ npm run storybook
```

![スクリーンショット 2022-06-22 0.39.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1484667/b2745d8d-edee-7c44-dd3c-1db916b58e05.png)


# emotionをインストール

```
$ npm i @emotion/react @emotion/babel-preset-css-prop
```

# Storybookにemotionを適用させる

.storybookディレクトリの直下にwebpack.config.jsを作成。
以下を記述。

```js:webpack.config.js
module.exports = ({ config }) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    loader: require.resolve("babel-loader"),
    options: {
      presets: [
        ["react-app", { flow: false, typescript: true }],
        // css prop を使えるように，追加
        require.resolve("@emotion/babel-preset-css-prop"),
      ],
    },
  });

  config.resolve.extensions.push(".ts", ".tsx");

  return config;
};
```

# Global cssを作成
srcディレクトリ直下にGlobalStyle.tsxを作成
中に`リセットcss`などGlobalで当てたいcssを記述。

```ts:GlobalStyle.tsx
import { Global, css } from "@emotion/react";

const GlobalStyle = () => (
  <Global
    styles={css`
      * {
        box-sizing: border-box;
      }

      html,
      body,
      div,
      span,
      object,
      iframe,
      h1,
      h2,
      h3,
      h4,
      h5,
      h6,
      p,
      blockquote,
      pre,
      abbr,
      address,
      cite,
      code,
      del,
      dfn,
      em,
      img,
      ins,
      kbd,
      q,
      samp,
      small,
      strong,
      sub,
      sup,
      var,
      b,
      i,
      dl,
      dt,
      dd,
      ol,
      ul,
      li,
      fieldset,
      form,
      label,
      legend,
      table,
      caption,
      tbody,
      tfoot,
      thead,
      tr,
      th,
      td,
      article,
      aside,
      canvas,
      details,
      figcaption,
      figure,
      footer,
      header,
      hgroup,
      menu,
      nav,
      section,
      summary,
      time,
      mark,
      audio,
      video {
        padding: 0;
        margin: 0;
        font-size: 100%;
        vertical-align: baseline;
        background: transparent;
        border: 0;
        outline: 0;
      }

      article,
      aside,
      details,
      figcaption,
      figure,
      footer,
      header,
      hgroup,
      menu,
      nav,
      section {
        display: block;
      }

      ul,
      li {
        list-style: none;
      }

      a {
        color: inherit;
        text-decoration: none;
        outline: none;
      }

      a:visited {
        border: none;
      }

      /* Form Style Reset */
      input,
      button,
      select,
      textarea {
        font: inherit;
        background: transparent;
        border: none;
        border-radius: 0;
        outline: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
      }

      textarea {
        resize: none;
      }

      input[type="checkbox"],
      input[type="radio"] {
        display: none;
      }

      input[type="submit"],
      input[type="button"],
      label,
      button,
      select {
        cursor: pointer;
      }

      select::-ms-expand {
        display: none;
      }

      input:-webkit-autofill {
        -webkit-box-shadow: 0 0 0 1000px #fff inset;
      }

      input[type="number"] {
        -moz-appearance: textfield;
      }

      input[type="number"]::-webkit-outer-spin-button,
      input[type="number"]::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
      }
    `}
  />
);
export default GlobalStyle;
```

# Global cssを適用
.storybookディレクトリ内のpreview.js内上部に以下を追記。

```js
import GlobalStyle from "../src/GlobalStyle";

export const decorators = [
  (Story) => (
    <>
      <GlobalStyle />
      <Story />
    </>
  ),
];

```

↓のようになればOK

```diff_javascript:preview.js
+ import GlobalStyle from "../src/GlobalStyle";

+ export const decorators = [
+   (Story) => (
+     <>
+       <GlobalStyle />
+       <Story />
+     </>
+   ),
+ ];
 
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};
```

最後に、tsconfig.jsonに`jsxImportSource`オプションを追加

```js:tsconfig.json
{
  "compilerOptions": {
    // ...
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  },
  // ...
}
```

# 試してみる

コンポーネントの作成

```ts:TestButton.tsx
import { css } from "@emotion/react";

export const TestButton = () => {
  const buttonStyle = css`
    color: #fff;
    background-color: blue;
    border-radius: 3em;
  `;

  return <button css={buttonStyle}>Test</button>;
};
```

ストーリーズの作成

```ts:TestButton.stories.tsx
import { TestButton } from "./TestButton";

export default {
  title: "Example/TestButton",
  component: TestButton,
};

const Template = () => <TestButton />;

export const Primary = Template.bind({});
```

![スクリーンショット 2022-06-25 16.38.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1484667/3bae38b3-df97-ab69-839c-10c1a8918859.png)