【React + Typescript】ボタン一つでコンポーネントのscssをコピーできるサイトを作った

こんにちは、[Yui](https://twitter.com/yui_active)です。

[先週](https://qiita.com/Yui_active/items/b8062b9f89f72c6865ed)から引き続き週イチ発信をしています。
今週はボタン一つでコンポーネントのscssをコピーできるサイトを作ったので、その紹介をします。

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">ボタン一つでscssをコピーできるコンポーネント集作りました！<br>個人的によく実装するけど忘れがちなものを集めました。<br>素材はこれからどんどん増やしていきます！<br>頑張って作ったので拡散してもらえると嬉しいです。<a href="https://t.co/q9mYT3W4aL">https://t.co/q9mYT3W4aL</a> <a href="https://t.co/xK4Lz8rbyl">pic.twitter.com/xK4Lz8rbyl</a></p>&mdash; Yui 🌖 Yuiko Ito (@yui_active) <a href="https://twitter.com/yui_active/status/1398990248609161223?ref_src=twsrc%5Etfw">May 30, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

サイトはこちら→https://ui-components.com/
github→https://github.com/YuikoIto/ui-components


## 機能紹介
今回、入れている機能は以下です。

- 表示されているコンポーネントをクリックすると内部のコードが見える
- コードをボタン一つでコピーできる

めちゃくちゃシンプルです。

そして今は``ローディングアニメーション``と``ボタン``と``インプット``の3パターンしか用意できてないですが、徐々に増やしていきます！
正直このままだと**コンポーネント**集というよりも**アトム**集になりかねないので、今後はフォーム一式とかも作ってきたいなと思っています。

**ローディングアニメーション**

[![Image from Gyazo](https://i.gyazo.com/36aa4274ffdcb6d9b488b4b23253b608.gif)](https://gyazo.com/36aa4274ffdcb6d9b488b4b23253b608)

**ボタン**

[![Image from Gyazo](https://i.gyazo.com/69390f8f1069d88c555b5f63c7f618f0.gif)](https://gyazo.com/69390f8f1069d88c555b5f63c7f618f0)

**インプット**
[![Image from Gyazo](https://i.gyazo.com/769ec64bd6add4e24ef18130a00a5366.gif)](https://gyazo.com/769ec64bd6add4e24ef18130a00a5366)

ちなみに今回要素をクリックしたらモーダルが開くようになっているので、インプットに関しては子要素のクリックで親要素が開かないように、``stopPropagation``を使って調整しました。

後半につれて要素の数が少なくなっていっているのは、私が力尽きたからです。
徐々に増やしていきます。

## なぜ作ったか
作った動機ですが、**私がずっと欲しかったから**です。笑
私は仕事柄よくアニメーションを書くことがあるのですが、**何回書いてもアニメーションは覚えることができない**し、アニメーション以外でもshadowの感じとかは**毎回ググってる**しで**いい加減効率が悪くない？**と思うようになりました。

cssフレームワークを使えたらそれが一番ラクだとは思うんですけど、案件によっては入れることができなかったり、ちょっとしたアニメーションはやはり直接書かないといけないというときが出てきます。

そういうときに**少しでも時間を短縮できたら良いな**と思って、作りました。
デザインは100%私の好みです！シンプルなのが好きなので2色+白で統一してます。

## 雛形作成

今回も、React+TSで作りました。

```
npx create-react-app ui-components --template typescript
yarn add react-router-dom @types/react-router-dom
yarn add node-sass@5
```

node-sass@5に指定しているのは、create-react-appで作ったアプリがnode-sass@6に対応してないため[Node Sass version 6.0.0 is incompatible with ^4.0.0 || ^5.0.0.](https://github.com/sass/node-sass/issues/3103)のエラーが出るのを防ぐためです。

個人的にimport時のパス先が長くなるのは嫌なので、scssたちはsrc/stylesフォルダを作って、デフォルトでbaseとなるパスをsrcにしておきます。

```json:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src"
  },
}
```

これで``import ./styles/App.scss``と書かないといけないところから``import styles/App.scss``とかけるようになりました。

こういう設定はいつも業務ではやるものの個人開発ではサボりがちなので、ちゃんとやっていきたいですね。

というわけでこれでひな形が完成。

## 全体の構成

全部書くと長いので、ざっくりとした構成だけ書きます。

```
src/
　├ components/
　│　└ button/
　│　└ common/
　│　└ input/
　│　└ loader/
　├ hooks/
　├ models/
　├ pages/
　├ styles/
　│　└ styles.scss
　│　└ components/
　│　    └ button/
　│　    └ common/
　│　    └ input/
　│　    └ loader/
　└ App.tsx
```

まず、今回の肝となるコード表示部分に関してはsrc/components配下にまとめています。
下の画像で表示しているコードたちです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670446/2d2c05df-2788-f340-b953-3dd5f58141c1.png)

これは例えばbutton1クラスだと、以下のようにしてbutton1.tsを作って格納しています。

```typescript:button1.ts
import { styleModel } from "models/styleModel";

export const button1: styleModel = {
  title: "button1",
  scss: `.button1 {
  position: relative;
  border-radius: 20px;
  margin: 65px auto;
  width: 150px;
  padding: 1rem 0;
  font-size: 1.5rem;
  background: #333c5f;
  color: #fff;
  font-weight: bold;
  -webkit-box-shadow: 0 5px 0 #576199;
  box-shadow: 0 5px 0 #576199;
  &:hover {
    -webkit-transform: translate(0, 3px);
    transform: translate(0, 3px);
    -webkit-box-shadow: 0 2px 0 #576199;
    box-shadow: 0 2px 0 #576199;
  }
}
`,
} as const;
```

そして、buttonフォルダの中にindex.tsを作って、button1〜button4を以下のように格納します。

```typescript:index.ts
import { button1 } from "components/button/button1";
import { button2 } from "components/button/button2";
import { button3 } from "components/button/button3";
import { button4 } from "components/button/button4";
import { styleModel } from "models/styleModel";

export const buttons: styleModel[] = [button1, button2, button3, button4];
```

後はbuttonsをmapでまわして表示をするだけです。この辺もう少し効率良いやり方があれば知りたいです。

hooksフォルダに関してはモーダルの開閉部分をカスタムフックにして入れてますが、正直これぐらいの量なら直接ファイル内に書いても良かったかなとも思いました。

modelsに独自で作成した型を格納しています。
そして実際にレイアウトを整えるのに使うscssに関してはstyles配下にまとめています。


## シンタックスハイライトの設定をする

今回、コードの表示をするということで、絶対にシンタックスハイライトを導入したかったので、良さそうなライブラリを探しました。

[highlight.js](https://highlightjs.org/)が軽量で良さそうなので、それを使います。

```
yarn add highlight.js @types/highlight.js
```

```react
import hljs from "highlight.js";
import "highlight.js/styles/atom-one-dark.css";
// 今回はscssに関するハイライトなので、以下をインポート
import scss from "highlight.js/lib/languages/scss";
// これでpreタグ内のcodeタグにクラス名としてscssを付与すれば良い
// つけなくてもハイライトはされるが、つけると確実にその言語に沿ったハイライトをしてくれるらしい。
hljs.registerLanguage("scss", scss);

const CodeBlock = ({ style }: Props) => {
// このコンポーネントが描画されるタイミングでハイライトをかける
  useEffect(() => {
    hljs.highlightAll();
  });

  return (
    <div className="code-block-container">
      <div className={style.title}></div>
      {isOpen && (
        <Modal closeModal={clickHandler} copyText={copyText}>
          <code>&lt;div class="{style.title}"&gt;&lt;/div&gt;</code>
          <pre>
            <code className="scss">{style.scss}</code>
          </pre>
        </Modal>
      )}
    </div>
  );
};
```

シンタックスハイライトを付けたい部分に関しては``<pre><code></code></pre>``で囲います。
私はdivタグの要素に関しては、シンタックスハイライトをつけたくなかったので、``<code>``タグだけにしました。

ちなみに、シンタックスハイライトには**ダークモード**と**ライトモード**がありますが、私は今回ダークモードで表示をしたかったので、以下のリンクから今回適用している``atom-one-dark``モードを見つけました。

https://github.com/atom/atom/tree/master/packages/one-dark-syntax


本当はdivタグ要素にはライトモードを適用して、scss部分にはダークモードを適用して、よりわかりやすくしたかったんですが、別ファイルでライトモードを適用してインポートしても全部ダークモードに上書きされてしまったので諦めました。
そのうちなんとかすると思います。

## デプロイ
今回もデプロイは[Vercel](https://vercel.com/)を使いました。
めちゃくちゃ簡単にデプロイできるので好きです。

## 制作時間
大体20時間ぐらいです。
そのうち15時間ぐらいは素材をせっせと書いてました。
15時間かかってこんだけしかかけないのかと絶望してますが、このサイトに追加していったら今後はコピペで大丈夫なので結果的に時間短縮になるだろうと信じてます。笑

## あとがき
完全に私得なコンポーネント集ができました。
後は要素を増やしていくだけなので、どんどん増やしていきます。

もしこんなのほしい、というのがあればリクエストいただければ嬉しいです。