Phoenix LiveView アプリケーションに MishkaChelekom の UI コンポーネントを導入する

## はじめに

Phoenix LiveView は Elixir の Web フレームワークです

JavaScript をほぼ書くことなく、 Elixir でフロントエンドもバックエンドも実装できます

https://github.com/phoenixframework/phoenix_live_view

MishkaChelekom は UI コンポーネントのジェネレーターです

https://mishka.tools/chelekom

UI コンポーネントを直接定義しているわけではなく、必要な UI コンポーネントを生成して Phoenix プロジェクト内に追加します

実装したアプリケーションはこちら

https://github.com/RyoWakabayashi/phoenix_mishka_chelekom_sample

## 実行環境（前提条件）

Erlang と Elixir 、 Node.js をインストールしているものとします

各言語のバージョン

- Erlang 27.2
- Elixir 1.17.3
- Node.js 22.12.0

macOS であれば [mise](https://github.com/jdx/mise) や [asdf](https://asdf-vm.com/) を使ってインストールするのがおすすめです

mise の場合

```bash
mise use -g erlang@27.2
mise use -g elixir@1.17.3
mise use -g node@22.12.0
```

asdf の場合

```bash
asdf plugin add erlang
asdf install erlang 27.2
asdf global erlang 27.2

asdf plugin add elixir
asdf install elixir 1.17.3
asdf global elixir 1.17.3

asdf plugin add nodejs
asdf install nodejs 22.12.0
asdf global nodejs 22.12.0
```

elixir のパッケージマネージャー [Hex](https://hex.pm/) をインストールします

```bash
mix local.hex
```

Phoenix プロジェクトを生成するための [phx_new](https://hex.pm/packages/phx_new) をインストールします

```bash
mix archive.install hex phx_new
```

## Phoenix プロジェクトの作成

Phoenix プロジェクトを作成します

今回は UI だけ見たいので DB 操作をしないため `--no-ecto` のオプションを付けています

```bash
mix phx.new phoenix_mishka_chelekom_sample --no-ecto
```

`Fetch and install dependencies? [Yn]` と質問されるので、そのまま Enter を押下（デフォルトの yes で回答）します

phoenix_mishka_chelekom_sample ディレクトリーが作成されるので、中に移動します

```bash
cd phoenix_mishka_chelekom_sample
```

Phoenix アプリケーションを起動します

```bash
mix phx.server
```

http://localhost:4000 にアクセスすると、以下のような画面が表示されます

![スクリーンショット 2024-12-25 10.35.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/5580d361-675f-003f-1ffe-55755f3de7e4.png)

一旦、 Ctrl + C でアプリケーションを停止します

## MishkaChelekom の導入

`mix.exs` を以下のように編集します

```diff_elixir
defmodule PhoenixBloomSample.MixProject do
...
  defp deps do
    [
...
-     {:bandit, "~> 1.5"}
-     {:bandit, "~> 1.5"},
+     {:mishka_chelekom, "~> 0.0.1", only: :dev}
    ]
  end
...
end
```

MishkaChelekom 自体はコードを生成するだけで実行環境には不要なため、 `only: :dev` を指定しています

これにより、リリースされるバイナリは必要最小限のものになります

MishkaChelekom をインストールします

```bash
mix deps.get
```

## コンポーネントの導入

MishkaChelekom では以下のようなコマンドでコードを生成します

```elixir
mix mishka.ui.gen.component <コンポーネント名>
```

公式ドキュメントからコンポーネントの種類、表示、動作、プロパティ、使用例などが確認できます

https://mishka.tools/chelekom/docs

:::note warn
まだバージョン 0.0.1 であることもあるのか、コンポーネントのプロパティについてはドキュメントと実装が一致していません

1.0.0 リリースまでにはきっと整備されると思います
:::

Rating コンポーネントを追加してみましょう

```elixir
mix mishka.ui.gen.component rating
```

コマンドにより、 `lib/phoenix_mishka_chelekom_sample_web/components/rating.ex` が生成されます

コンポーネントを関数名だけで呼び出したいので、 `lib/phoenix_mishka_chelekom_sample_web.ex` を以下のように編集します

```diff_elixir
defmodule PhoenixMishkaChelekomSampleWeb do
  ...
  defp html_helpers do
    quote do
      # Translation
      use Gettext, backend: PhoenixMishkaChelekomSampleWeb.Gettext

      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components
      import PhoenixMishkaChelekomSampleWeb.CoreComponents
+     import PhoenixMishkaChelekomSampleWeb.Components.Rating

      # Shortcut for generating JS commands
      alias Phoenix.LiveView.JS

      # Routes generation with the ~p sigil
      unquote(verified_routes())
    end
  end
  ...
end
```

`lib/phoenix_mishka_chelekom_sample_web/controllers/page_html/home.html.heex` に Rating を追加します

```heex
...
    <h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
      Phoenix Framework
      <small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
        v{Application.spec(:phoenix, :vsn)}
      </small>
    </h1>
    <.rating />
    <.rating select={3} />
    <.rating select={5} />
    <.rating interactive />
    <p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 text-balance">
      Peace of mind from prototype to production.
    </p>
...
```

アプリケーションを起動します

```bash
mix phx.server
```

ブラウザで表示すると、以下のように Rating が表示されます

![rating.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c31ffa14-e8da-d1bc-3803-422510314a01.gif)

`interactive` を指定した場合はマウスオーバーで評価が変わっています

続けて Alert を追加してみます

極力コードを少なくするため、利用する予定のある色とスタイルのみを指定します

```bash
mix mishka.ui.gen.component alert --color info,error --variant default
```

`lib/phoenix_mishka_chelekom_sample_web/components/alert.ex` が生成されました

内容を確認すると、例えば色の指定は以下のように `:error` 、 `:danger` 、 `:info` だけが指定されています
（ `:error` と `:danger` は同じ色を使っているので、片方しか指定しなくても両方ついてくるようです）

```elixir
  defp color_variant("default", type) when type in [:error, :danger] do
    "bg-[#FFE6E6] text-[#E73B3B] border border-[#E73B3B] hover:[&>button]:text-[#F0756A]"
  end

  defp color_variant("default", :info) do
    "bg-[#E5F0FF] text-[#004FC4] border border-[#004FC4] hover:[&>button]:text-[#3680DB]"
  end
```

これにより、最終的に TailwindCSS が生成する CSS ファイルも小さくなり、その分フロントエンドが軽くなります

`lib/phoenix_mishka_chelekom_sample_web/controllers/page_html/home.html.heex` に Alert を追加します

```diff_elixir
defmodule PhoenixMishkaChelekomSampleWeb do
  ...
  defp html_helpers do
    quote do
      # Translation
      use Gettext, backend: PhoenixMishkaChelekomSampleWeb.Gettext

      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components
-     import PhoenixMishkaChelekomSampleWeb.CoreComponents
+     import PhoenixMishkaChelekomSampleWeb.CoreComponents, except: [flash_group: 1]
+     import PhoenixMishkaChelekomSampleWeb.Components.Alert
      import PhoenixMishkaChelekomSampleWeb.Components.Rating

      # Shortcut for generating JS commands
      alias Phoenix.LiveView.JS

      # Routes generation with the ~p sigil
      unquote(verified_routes())
    end
  end
  ...
end
```

Alert 内で `flash_group` を定義しているため、 CoreComponents からはインポートしないように指定しています

`lib/phoenix_mishka_chelekom_sample_web/controllers/page_html/home.html.heex` に Alert を追加します

```heex
...
    <h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
      Phoenix Framework
      <small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
        v{Application.spec(:phoenix, :vsn)}
      </small>
    </h1>
    <.alert kind={:info}>Info</.alert>
    <.alert kind={:error}>Error</.alert>
    <.rating />
...
```

アプリケーションを起動すると、 Alert の表示が確認できます

![スクリーンショット 2024-12-25 17.00.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f164f012-6600-26be-6524-21e2b09d5894.png)

## まとめてコンポーネントを作成する

以下のように実行すると、 Alert と Rating をまとめて読み込むためのモジュール MishkaComponents が生成されます

```bash
mix mishka.ui.gen.components alert,rating --import 
```

:::note warn
`mishka.ui.gen.components` <- 最後に `s` がついています
:::


生成されたファイル `lib/phoenix_mishka_chelekom_sample_web/components/mishka_components.ex` の中身は以下のようになっています

```elixir
defmodule PhoenixMishkaChelekomSampleWeb.Components.MishkaComponents do
  defmacro __using__(_) do
    quote do
      import PhoenixMishkaChelekomSampleWeb.Components.Alert,
        only: [flash: 1, flash_group: 1, alert: 1]

      import PhoenixMishkaChelekomSampleWeb.Components.Rating, only: [rating: 1]
    end
  end
end
```

MishkaComponents を使う場合、 `lib/phoenix_mishka_chelekom_sample_web/controllers/page_html/home.html.heex` は以下のように編集します

```diff_elixir
defmodule PhoenixMishkaChelekomSampleWeb do
  ...
  defp html_helpers do
    quote do
      # Translation
      use Gettext, backend: PhoenixMishkaChelekomSampleWeb.Gettext

      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components
-     import PhoenixMishkaChelekomSampleWeb.CoreComponents
+     import PhoenixMishkaChelekomSampleWeb.CoreComponents, except: [flash_group: 1]
+     use PhoenixMishkaChelekomSampleWeb.Components.MishkaComponents

      # Shortcut for generating JS commands
      alias Phoenix.LiveView.JS

      # Routes generation with the ~p sigil
      unquote(verified_routes())
    end
  end
  ...
end
```

`use PhoenixMishkaChelekomSampleWeb.Components.MishkaComponents` により、 MishkaComponents の中に記述されている各種コンポーネントの import を実施しています

ただし、 `mix mishka.ui.gen.components` を実行した場合、 Alert や Rating など個別のコンポーネントにオプションを指定できないため、コンポーネントの色やスタイルは全て生成されます

また、以下のコマンドを実行することで MishkaComponents で生成できる全てのコンポーネントを一括生成します

```bash
mix mishka.ui.gen.components --import --yes
```

## まとめ

MishkaComponents はコンポーネントのジェネレーターという、他の UI ライブラリーとは毛色が違うものでした

コンポーネント自体の種類もオプションも豊富なので、 1.0.0 が出るのが楽しみですね