React初心者がStorybookとAtomicDesignを使ったコンポーネント設計に挑戦していくお話【第2話 最初のコンポーネント】

# はじめに

React初心者がStorybook v6でStorybookに初挑戦、そして少しずつ前に進んでいくお話です。
コンポーネント設計としてAtomicDesign、スタイルはcss-modulesを利用していきます。
同じような境遇の方のお役に立てたら幸いです。
（初心者のため謎設計が多数出現すると思います。予めご了承ください。。。）

今回は、はじめの一歩。最初のコンポーネントを作ってみたのお話。

- [第1話 環境構築](https://qiita.com/himorishige/items/31e7f45e7598ccda9f52)
- 第2話 最初のコンポーネント

# AtomicDesignについて

https://atomicdesign.bradfrost.com/

デザイナーであるBrad Frost氏のUI設計に基づいたデザインシステムのこと。
デザインパーツを５つの分類に分けて構造構築していくというお話です。
この形がAtomicDesignであるという正解を見出すことは難しいようですが、AtomicDesignの思想を参考に、コンポーネント設計していこうというのが今回の目的です。

- Atoms（原子）・・・ボタン、アイコン、インプットエリアなどの要素の最小単位。
- Molecules（分子）・・・Atomsの集合体、アイコン付きのボタンや検索ボックスなど。
- Organisms（有機体）・・・Atoms、Moleculesの集合体。ヘッダーや検索エリアなど。
- Templates（テンプレート）・・・Organismsの集合体。ページレイアウトなど。
- Pages（ページ）・・・表示するページそのもの。

ざっくりの私の解釈としては今回下記のような分類にして構築していこうと思っています。

Propsを受け取るのみ。単体での意味やロジックを持たないコンポーネント。
**Atoms、Molecules**

Propsを渡すことができる。単体での意味やロジックを持つコンポーネント。
**Organisms**

ページレイアウトのみを持つコンポーネント。
**Templates**

最終的なページのみ。
**Pages**

# 最初のコンポーネント

AtomicDesignについては一旦置いておき、Storybookのテストを兼ねてボタンコンポーネントを作成します。
Storybookにコンポーネントを登録するには、通常のコンポーネントとは別に、`***.sotries.tsx`というStorybook用のファイル作成が必要です。

## ボタンの作成

まずは、通常のボタンを作成します。
ボタンはAtomsとして作成したいので今回は、
`/src/components/atoms/Button/index.tsx`として作成していきます。

```react:/src/components/atoms/Button/index.tsx
import React from 'react';

const Button: React.VFC = () => {
  return <button>button</button>;
};

export default Button;
```

他のコンポーネントからAtomsを利用しやすいようにindexファイルを用意します。（Atomsを追加ごとに行を増やしていきます。）

```react:/src/components/atoms/index.ts
export { default as Button } from './Button'
```

上記のファイルを用意することで、App.tsxファイルなど他のファイルからimportする際には、

```react
import { Button, Input } from 'src/components/atoms';
```

このようにまとめてimportができるようになるのでとても便利です。
次に、ボタンコンポーネントの確認のため`App.tsx`を下記のように書き換えます。

```react:/src/App.tsx
import React from 'react';
import { Button } from 'src/components/atoms';

const App: React.VFC = () => {
  return (
    <div>
      <Button />
    </div>
  );
};

export default App;
```

ここで一旦動作確認してみましょう。

```bash
$ yarn start
```

`http://localhost:3000`を開いていてボタンの表示を確認します。
![button](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1268181/df37f9d3-010b-6702-b8e3-575580ddc640.png)

## Storybookへの登録

Storybookに登録するために、
`/src/components/atoms/Button/index.stories.tsx`を作成していきます。

```react:/src/components/atoms/Button/index.stories.tsx
import React from 'react';
import { Meta } from '@storybook/react';
import Button from './index';

export default {
  title: 'Design System/Atoms/Button', // コンポーネント表示名
  component: Button,
} as Meta;

export const Primary: React.VFC<{}> = () => <Button />;
```

登録したらStorybookを起動します。

```bash
$ yarn storybook
```

`http://localhost:6006`を開いていて表示を確認します。
![storybook](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1268181/9d43015b-3b03-0550-3c64-98b7cb9909a5.png)

`title: 'Design System/Atoms/Button'`として記載した部分を基準に、階層が作られ、`Primary`というexportした名前の中にボタンが表示されています。
これで記念すべき最初のコンポーネントが登録できました。

## ボタンの調整

このままだとちょっとさみしいのでもう少し拡張してみます。
ボタンのテキストと種別によってデザインが変わるようにしてみましょう。

css modulesでSASSを利用したいためsass環境をインストールします。

```bash
$ yarn add -D sass
```

PrimaryボタンとSecondaryボタン用のスタイルを作成します。

```scss:/src/components/atoms/Button/index.module.scss
.button {
  border: 2px solid black;
  width: 200px;
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  padding: 10px;
  text-transform: uppercase;
  transition: background-color 0.3s, color 0.3s, border 0.3s;
  &Primary {
    background-color: black;
    color: white;
    &:hover {
      background-color: white;
      color: black;
    }
  }
  &Secondary {
    background-color: gray;
    color: black;
    &:hover {
      background-color: black;
      color: white;
    }
  }
}
```

ButtonコンポーネントとStroybookファイルは一気に色々と増えてきます。

```react:/src/components/atoms/Button/index.tsx
import React from 'react';
import styles from './index.module.scss';

export type ButtonProps = {
  primary?: boolean;
  label: string;
};

const Button: React.VFC<ButtonProps> = ({ primary = false, label, ...props }) => {
  // propsとしてprimaryを受け取った場合と、それ以外で付与するclassを分けます
  const mode = primary ? styles.buttonPrimary : styles.buttonSecondary;
  return (
    <button type="button" className={[styles.button, mode].join(' ')} {...props}>
      {/* propsとして受け取ったlabelをボタンのテキストとして表示します */}
      {label}
    </button>
  );
};

export default Button;
```

記載が一気に増えていますが、ほとんどが決まりごとのようなものになってきますので、Storybookのドキュメントを参考に記載を確認してみてください。

https://storybook.js.org/docs/react/writing-stories/introduction


```react:/src/components/atoms/Button/index.stories.tsx
import React from 'react';
import { Story, Meta } from '@storybook/react';
import Button, { ButtonProps } from './index';

export default {
  title: 'Design System/Atoms/Button',
  component: Button,
} as Meta;

// 共通となるボタンコンポーネントをテンプレートとして作成
const Template: Story<ButtonProps> = (args) => <Button {...args} />;

// Primaryボタンを作成 渡すargs違いでパータンを分けます
export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

// Secondaryボタンを作成
export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};
```
これで2種類のボタンを登録することができました！
![ボタンの登録完了](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1268181/8054215d-a05d-1ecf-aab9-fe1a30fd9d38.png)

Reactコンポーネントとしては下記のように利用します。

```react:/src/App.tsx
import React from 'react';
import { Button } from 'src/components/atoms';

const App: React.VFC = () => {
  return (
    <div>
      <Button primary label="button" />
    </div>
  );
};

export default App;
```

![button](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1268181/05f3a788-e4be-fef4-0feb-b1dd38a224f7.png)
ようやくそれっぽいボタンの登録をすることができました。
まだまだ機能としては何もないボタンですが、最初の大きな一歩です！

次回からは、改めてデザインファイルに合わせてコンポーネントを作成していきます。