Clarity Design System って知ってる？

こんにちわ！TUNA-JPの運営メンバーをしている @hirosat です。
本記事は、「[TUNA-JP Advent Calendar 2022](https://qiita.com/advent-calendar/2022/tuna-jp)」の9日目のエントリです。

# TL;DR (この記事の要約)
ClarityをReactで実装する方法を教えちゃいます

# Clarity Design System もOSSだよ
Clarity Design System を一言でいうと、VMwareっぽいUI/UXが作れちゃう、Frontend用デザインテンプレートです！
[GitHub](https://github.com/vmware-clarity/core) によると、以下の通り。

:::note info
Clarity Design System
Clarity is an open source design system that brings together UX guidelines, design resources, and coding implementations with Web Components. This repository includes everything you need to build, customize, test, and deploy Clarity. For complete documentation, visit the Clarity website.
:::

:::note warn
(日本語訳)
Clarity は、UX ガイドライン、デザイン リソース、および Web コンポーネントによるコーディング実装をまとめたオープン ソースのデザイン システムです。このリポジトリには、Clarity の構築、カスタマイズ、テスト、および配備に必要なすべてのものが含まれています。完全なドキュメントについては、Clarity の Web サイトを参照してください。
:::

また、Clarityは以下の5つのnpmパッケージとして公開されています。（と書いてますが、どうみても4つです :sweat_smile:）
- @cds/core : あらゆるJavaScriptフレームワークで動作するWeb Componentsが含まれている
- @cds/angular : Angular 環境で、@cds/core を使用するためのパッケージ
- @cds/react : React 環境で、@cds/core を使用するためのパッケージ
- @cds/city : オープンソースの sans-serif 書体

ふむふむ。Reactで使うには、`@cds/core` / `@cds/react` / `@cds/city` があれば、良さそうですね。
詳細は、[Clarityドキュメント](https://clarity.design/) を見ろ。と言われるので、飛んでみると・・・

![Screen Shot 2022-12-04 at 14.44.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18601/96651d1b-61f5-2356-7a26-9a303ae75334.png)

んん？「Angular componentsと連携して・・・」とか書いてあるぞ。。もしかして、Angular専用？！？！
このドキュメントを読み進めても、Angularに関する話しか書いてありません。

## 分かりづらいけど、Reactにも使えそう
いきなり座礁に乗りかけましたが、私、すごいことを発見しちゃいました！なんと、
- https://clarity.design/ とは別に、
- https://core.clarity.design/ というサイトが存在します！

ざっとGitHubや公式サイトを探しましたが、こちらの core.clarity.design の方は、`どこからもリンクがない`んです！
このサイトは、`react clarity` をググって、いくつか見ているうちに気がつきました。

恐らくは、下記のような状況かと思います。（誰か詳しい人がいたら教えて下さいｗ）
- Clatiryのcoreコンポーネント自体は、汎用的に作られていている。
    - https://core.clarity.design/ に、こっそりドキュメントが用意されている。
    - Reactの説明もここに書いている。
- 正式にサポートされているのは、Angularだけ（?）
    - https://clarity.design/ が公式サイトで、更新も活発

という訳で、Reactの使い方も載っている、https://core.clarity.design/ をベースに解説いたします。

# Reactの準備
何はともあれ、とりあえずClarityを使うためのReactアプリケーションを用意しましょう。
こういう時は定番の、[create-react-app](https://create-react-app.dev/)で、サクッと作ってしまうことがオススメです。
```
% npx create-react-app clarity-react-sample
(インストール処理は省略)
% cd clarity-react-sample
% npm start
```
すると、以下のようなサイトが、`localhost:3000` として立ち上がります

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18601/aefc6eeb-8536-89b1-2fda-28639c5f05fe.png" width=50%>

ブラウザにも書いてあるとおり、以後、`src/App.js` を編集すれば画面が動的に変わるはずなので、ここに、Clarityを仕込んでいこうと思います。始めから作ってみたい人は、いったんここで、gitにpushしておきましょう。

# Clarityの準備
[Developing](https://core.clarity.design/get-started/developing/) のページに、始め方が書いています。

## 1. 必要なClarityモジュールをインストール
ドキュメントでは、`@cds/core` と `@cds/city` だけを最初にインストールしていますが、後ほど `@cds/react` もインストールすることになるので、ここで一緒にインストールしてしまいましょう。

```
% npm install @cds/core @cds/city @cds/react --save
(インストールされます)
% git diff package.json
diff --git a/package.json b/package.json
index a813a5d..6287915 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,9 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@cds/city": "^1.1.0",
+    "@cds/core": "^6.2.2",
+    "@cds/react": "^6.2.2",
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^13.4.0",
     "@testing-library/user-event": "^13.5.0",
```

## 2. スタイルシートの設定
ドキュメントには、HTMLで直接CSSを指定する方法と、Sass/Lessでインポートする方法が記載されています。
今回は、HTMLに記載しようと思うので、`public/index.html` に記載します。

```
% git diff public/index.html
diff --git a/public/index.html b/public/index.html
index aa069f2..5dd3477 100644
--- a/public/index.html
+++ b/public/index.html
@@ -24,9 +24,12 @@
       work correctly both with client-side routing and a non-root public URL.
       Learn how to configure a non-root public URL by running `npm run build`.
     -->
+    <link href="/node_modules/modern-normalize.css/modern-normalize.css" rel="stylesheet" />
+    <link href="/node_modules/@cds/core/global.min.css" rel="stylesheet" />
+    <link href="/node_modules/@cds/city/css/bundles/default.min.css" rel="stylesheet" />
     <title>React App</title>
   </head>
-  <body>
+  <body cds-text="body">
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <div id="root"></div>
     <!--
```

# React開発
src/App.js を編集し、ブラウザ確認しながら進めていきましょう。
基本的には、[Reactに関する説明](https://core.clarity.design/get-started/developing/#react) を読みながら進めるのですが、サンプルが非常に良くないです。
例えば、Classで書かれた例があるのですが、今は関数コンポーネントが主流だったりします。（[参考](https://zenn.dev/swata_dev/articles/7f8ef4333057d7)）
なので、私の方でアレンジを加えながら解説します。

## 1. Buttonの実装
基本的には、冒頭で使うモジュールをimportして、コンテンツ内で呼び出すば、UIが表示されます。
注意点として、`@cds/react`では、パスカルケース(単語ごとに1文字目を大文字)に変換されるため、
本来のClarity coreは`<cds-button>`要素なのですが、Reactで使う時には、`<CdsAlert>`となります。

```
% git diff src/App.js
diff --git a/src/App.js b/src/App.js
index 3784575..559cbc9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,11 +1,13 @@
 import logo from './logo.svg';
 import './App.css';
+import { CdsButton } from '@cds/react/button';

 function App() {
   return (
     <div className="App">
       <header className="App-header">
         <img src={logo} className="App-logo" alt="logo" />
+        <CdsButton>My button</CdsButton>
         <p>
           Edit <code>src/App.js</code> and save to reload.
         </p>
```

上記のように編集すると、以下のように画面内にボタンが表示されるはずです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18601/119aa51b-8662-e1f6-01f9-8d0730c52ba1.png" width=50%>

## 2. ボタンに機能を実装
[フックを導入](https://ja.reactjs.org/docs/hooks-intro.html)のドキュメントを参考にして、
ボタンをクリックするたびに数字があがっていくようにしてみました。
このように、Clarityモジュールであっても、他のパーツと同じように機能が実装できます。
git diff ではなく、全文掲載いたします。

```
import { useState } from 'react';
import logo from './logo.svg';
import './App.css';
import { CdsButton } from '@cds/react/button';

function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <CdsButton onClick={() => setCount(count + 1)}>
          Click me
        </CdsButton>
        <p>You clicked {count} times.</p>
      </header>
    </div>
  );
}

export default App;
```

ブラウザ上で、ボタンをクリックして、動作確認してみましょう

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18601/3e990d67-1300-2042-350c-f82474355818.png" width=50%>

## 3. 好きなパーツを利用する

[Components](https://core.clarity.design/core-components/)のメニュー以下に、各パーツの説明があるので、好きなように置いてみましょう！ 
今回は以下のパーツを使用してみました。
- CdsButton
- CdsTag
- CdsBadge
- CdsAlert
- CdsIcon

下記にサンプルを示しますが、CdsIconは使用の仕方が面倒でした。
一度、`@cds/core/icon` （パスは、`node_modules/@cds/core/icon/index.js`）から利用したいアイコン（サンプルだと、`userIcon`と`bugIcon`）を呼び出して、`ClarityIcons.addIcons(userIcon);`のように呼び出し直す必要がありそうでした。
なお、Icon一覧は以下のページから確認できます。
- https://core.clarity.design/foundation/icons/shapes/

```
import { useState } from 'react';
import './App.css';
import { CdsButton } from '@cds/react/button';
import { CdsTag } from '@cds/react/tag';
import { CdsBadge } from '@cds/react/badge';
import { CdsAlert, CdsAlertGroup } from '@cds/react/alert';
import { CdsIcon } from '@cds/react/icon';
import { ClarityIcons, userIcon, bugIcon } from '@cds/core/icon';
ClarityIcons.addIcons(userIcon);
ClarityIcons.addIcons(bugIcon);

function App() {
  const [count, setCount] = useState(0);
  const [show, setShow] = useState(false);
  return (
    <div>
      <h1>Clarity React samples!</h1>
      <h2 cds-text="section">Buttons</h2>
      <section cds-layout="horizontal gap:sm">
        <CdsButton status="primary">primary</CdsButton>
        <CdsButton status="success">success</CdsButton>
        <CdsButton status="danger">danger</CdsButton>
        <CdsButton status="danger" disabled>disabled</CdsButton>
        標準ボタン
      </section>
      <section cds-layout="horizontal gap:sm">
        <CdsButton action="outline">outline</CdsButton>
        <CdsButton action="outline" status="success">success</CdsButton>
        <CdsButton action="outline" status="danger">danger</CdsButton>
        <CdsButton action="outline" disabled>disabled</CdsButton>
        フチのみボタン
      </section>
      <section cds-layout="horizontal gap:sm">
        <CdsButton action="flat">flat</CdsButton>
        <CdsButton action="flat" disabled>flat disabled</CdsButton>
        フラットボタン
      </section>
      <h2 cds-text="section">Tags & Badges</h2>
      <section cds-layout="horizontal gap:sm">
        <CdsTag readonly status="info">Info</CdsTag>
        <CdsTag readonly status="success">Success</CdsTag>
        <CdsTag readonly status="warning">Warning</CdsTag>
        <CdsTag readonly status="danger">Danger</CdsTag>
        タグのみ
      </section>
      <section cds-layout="horizontal gap:sm">
        <CdsTag color="gray">Austin <CdsBadge>1</CdsBadge></CdsTag>
        <CdsTag color="purple">New York <CdsBadge>2</CdsBadge></CdsTag>
        <CdsTag color="blue">Palo Alto <CdsBadge>3</CdsBadge></CdsTag>
        <CdsTag color="orange">Ohio <CdsBadge>12</CdsBadge></CdsTag>
        <CdsTag color="light-blue">Seattle <CdsBadge>15</CdsBadge></CdsTag>
        タグ＋バッジ
      </section>
      <section cds-layout="horizontal gap:sm">
        <CdsBadge status="info">2</CdsBadge>
        <CdsBadge status="success">3</CdsBadge>
        <CdsBadge status="warning">12</CdsBadge>
        <CdsBadge status="danger">15</CdsBadge>
        <CdsBadge color="gray">1</CdsBadge>
        <CdsBadge color="purple">1</CdsBadge>
        <CdsBadge color="blue">15</CdsBadge>
        <CdsBadge color="orange">2</CdsBadge>
        <CdsBadge color="light-blue">3</CdsBadge>
        バッジのみ
      </section>
      <h2 cds-text="section">Icons</h2>
      <section cds-layout="horizontal gap:sm">
        <CdsIcon size="lg" shape="user"></CdsIcon>
        <CdsIcon size="lg" shape="user" badge="info"></CdsIcon>
        <CdsIcon size="lg" shape="user" badge="success"></CdsIcon>
        <CdsIcon size="lg" shape="user" badge="danger"></CdsIcon>
        <CdsIcon size="lg" shape="user" badge="warning-triangle"></CdsIcon>
        userアイコン/フチのみ表示
      </section>
      <section cds-layout="horizontal gap:sm">
        <CdsIcon size="lg" shape="bug" solid></CdsIcon>
        <CdsIcon size="lg" shape="bug" solid badge="info"></CdsIcon>
        <CdsIcon size="lg" shape="bug" solid badge="success"></CdsIcon>
        <CdsIcon size="lg" shape="bug" solid badge="danger"></CdsIcon>
        <CdsIcon size="lg" shape="bug" solid badge="warning-triangle"></CdsIcon>
        bugアイコン/塗りつぶし
      </section>
      <h2 cds-text="section">Action Button</h2>
      <CdsButton onClick={() => {
        setCount(count + 1);
        setShow({ show: true });
      }}>You clicked {count} times.</CdsButton>
      <br></br>
      {show ? (
        <CdsAlertGroup status="warning">
          <CdsAlert onCloseChange={() => setShow({ show: false })} closable>
            You Voted
          </CdsAlert>
        </CdsAlertGroup>
      ) : ('')}
    </div>
  );
}

export default App;
```

画面は、以下のように変化するはずです。vSphere製品で見覚えのあるパーツが登場してきましたね！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18601/e9743967-8e92-3f53-0939-bd9f3d1d1dd2.png" width=50%>


## 4. UIの実装

最後に、vCenterっぽいレイアウトに挑戦してみましょう。

### UI用のスタイルシートを追加
なんと、この話は https://clarity.design のサイトの方にしか書いていません。
[Getting Started](https://clarity.design/documentation/get-started)ページのHTML/CSS Frameworkのところをみると、`@clr/ui` の使用方法が記載されているので、読み込みましょう。
npmでインストールする方法と、直接CSSを読み込む方法がありますが、今回は後者でいきます。

```
<link rel="stylesheet" href="https://unpkg.com/@clr/ui/clr-ui.min.css" />
```

### 既存APPの退避
現在、`src/App.js` は、下記のような構造になっているかと思います。
```
import (※importしたいものを列挙);

function App() {
  return (
    ※既存APPの内容
  );
}

export default App;
```
これを、以下のように書き換えましょう。これからレイアウトを配置するために、既存のAPPを退避させました。
```
import (※importしたいものを列挙);

function Home() {
  return (
    ※既存APPの内容
  );
}
function App() {
  return (
    <div></div>
  );
}
export default App;
```

### VMware風レイアウトに変更
再び、 https://core.clarity.design/ に戻り、作業を続けます。
[Navigation](https://core.clarity.design/foundation/navigation/)のところにあるお好きなサンプルを `App()` のブロックに貼りましょう。
今回は、[Sidnav + Sidenav](https://core.clarity.design/foundation/navigation/#subnav-sidnav)のパターンを使用します。

また、ここからいくつかReact向けに直す必要があります。

1. まず、各所に`class=`という書き方がされているので、`className=`という書き方に一括置換します。
1. `<label for="...">`といった記載は、`<label htmlFor="...">`に修正。
1. `<a href="javascript://">`といった記載は、いったん`<a href="#">` にする。

これで、Consoleからエラーは消えるはずです。
おぉ、vSphereっぽい画面になってきたぞ・・。
![Screen Shot 2022-12-09 at 3.17.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18601/022f406e-f144-04cd-602d-a50a1988d2a3.png)

### アイコンの修正
先ほどのCdsIconの話を参考にしながら、以下の箇所を直しましょう。
```
# Iconのインポート
+import { ClarityIcons, userIcon, bugIcon, vmBugIcon, cogIcon } from '@cds/core/icon';
+ClarityIcons.addIcons(vmBugIcon);
+ClarityIcons.addIcons(cogIcon);

# CdsIconに修正
+              <CdsIcon shape="vm-bug"></CdsIcon>
(中略)
+            <CdsIcon shape="cog"></CdsIcon>
```

### 退避したAPPを読み込み
`className="content-area"`がコンテンツを表示する場所なので、そこの中身を`<Home />`に差し替えましょう。
うまく行けば、以下のように表示されるはずです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18601/bb4e43a4-fff8-4a85-e035-6ada19f86673.png" width=50%>

おぉ、まごうことなき、VMwareサイト！！！(笑)

ここまでのカスタマイズの成果を[GitHub](https://github.com/hirosat/clarity-react-sample)にあげておきました！
コード内容の確認など、お好きなようにお使い下さいませ:+1:

https://github.com/hirosat/clarity-react-sample

# あとがき

参照するサイトが一つにまとまってなくて、意外に大変でした。。
個人的には、割と良い記事ができあがったのではないかと思います。
それでは、また！