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

## はじめに

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

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

https://github.com/phoenixframework/phoenix_live_view

Bloom は Tailwind CSS を使った UI コンポーネントのライブラリです

https://bloom-ui.fly.dev/

shadcn にインスパイアされたという点では SaladUI と同じですね

https://qiita.com/RyoWakabayashi/items/c3dc2ec8bb67adb6dc3e

Bloom は面白いコンポーネントを簡単に導入できるので、「これは使えそう！」と思ったコンポーネントがあれば、ぜひ使ってみましょう

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

https://github.com/RyoWakabayashi/phoenix_bloom_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_bloom_sample --no-ecto
```

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

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

```bash
cd phoenix_bloom_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 でアプリケーションを停止します

## Bloom の導入

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

```diff_elixir
defmodule PhoenixBloomSample.MixProject do
...
  defp deps do
    [
      {:phoenix, "~> 1.7.18"},
      {:phoenix_html, "~> 4.1"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
-     {:phoenix_live_view, "~> 1.0.0"},
+     {:phoenix_live_view, "~> 1.0.0", override: true},
...
-     {:bandit, "~> 1.5"}
-     {:bandit, "~> 1.5"},
+     {:bloom, "~> 0.0.8"}
    ]
  end
...
end
```

まだ Bloom の依存関係が更新されていないため、 phoenix_live_view のバージョン指定に `, override: true` を加えています

Bloom をインストールします

```bash
mix deps.get
```

`assets/tailwind.config.js` に safelist の指定を加えます

:::note info
safelist を指定しない場合、変数の組み合わせで作られるクラスは TailwindCSS のスタイルが生成されないため、色が表示されません
:::

```javascript
...
module.exports = {
  ...
  safelist: [
    {
      pattern: /bg-+/,
      variants: ["focus", "hover", "group-hover", "active"],
    },
    {
      pattern: /text-+/,
      variants: ["focus", "hover", "group-hover", "active"],
    },
    {
      pattern: /from-+/,
      variants: ["focus", "hover", "group-hover", "active"],
    },
    {
      pattern: /to-+/,
      variants: ["focus", "hover", "group-hover", "active"],
    },
    {
      pattern: /inset-+/,
      variants: ["focus", "hover", "group-hover", "active"],
    },
    {
      pattern: /opacity-+/,
      variants: ["focus", "hover", "group-hover", "active"],
    },
    {
      pattern: /transition-+/,
      variants: ["focus", "hover", "group-hover", "active"],
    },
  ],
  ...
}
```

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

以下のコマンドでインストール可能なコンポーネントが確認できます

```bash
mix bloom.install help
```

実行結果

```
Usage: mix bloom.install [component_name]
Available components: avatar | glow_button | code_snippet | hero | gradient_text | bento_grid | card | marquee
```

具体的なコンポーネントのデザインや挙動はストーリーブックで確認できます

https://bloom-ui.fly.dev/storybook/welcome

今回は Avatar など、4種類をインストールしてみます

以下のコマンドを実行してください

```bash
mix bloom.install avatar 
```

すると、 `lib/phoenix_bloom_sample_web/components/avatar.ex` が作成されます

コマンドの実行結果として以下のメッセージが表示されます

```
Don't forget to import the component to your phoenix_bloom_sample_web.ex` file.
```

その通りに、 `lib/phoenix_bloom_sample_web.ex` を編集します

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

      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components
      import PhoenixBloomSampleWeb.CoreComponents
+     import PhoenixBloomSampleWeb.Components.Avatar

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

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

同様の手順で code_snippet 、 glow_button 、gradient_text もインストールしましょう

最終的に、以下のファイルが追加されます

- `lib/phoenix_bloom_sample_web/components/avatar.ex`
- `lib/phoenix_bloom_sample_web/components/code_snippet.ex`
- `lib/phoenix_bloom_sample_web/components/glow_button.ex`
- `lib/phoenix_bloom_sample_web/components/gradient_text.ex`

また、 `lib/phoenix_bloom_sample_web.ex` は以下のように編集します

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

      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components
      import PhoenixBloomSampleWeb.CoreComponents
+     import PhoenixBloomSampleWeb.Components.Avatar
+     import PhoenixBloomSampleWeb.Components.CodeSnippet
+     import PhoenixBloomSampleWeb.Components.GradientText
+     import PhoenixBloomSampleWeb.Components.GlowButton

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

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

`lib/phoenix_bloom_sample_web/controllers/page_html/home.html.heex` に各種コンポーネントを追加します

```heex
<.flash_group flash={@flash} />
<div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]">
  ...
    <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>
    <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>
    <div class="flex items-center gap-4 mt-4">
      <.avatar name="Jessica&rotate=30" style="adventurer" />
      <.avatar
        name="Me"
        img_src="https://api.dicebear.com/9.x/adventurer/svg?seed=Oliver&rotate=20&eyebrows=variant02&eyes=variant01&hairColor=85c2c6&mouth=variant24"
      />
    </div>
    <.code_snippet
      class="mt-4"
      delimiter=">"
      text_color="emerald-800"
      background_color="green-200"
    >
      mix bloom.install code_snippet
    </.code_snippet>
    <.gradient_text
      class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-balance"
      from_color="pink-500"
      to_color="cyan-500"
    >
      Peace of mind from prototype to production.
    </.gradient_text>
    <.glow_button class="mt-4" from_color="emerald-600" to_color="indigo-600">
      Hello, Bloom!
    </.glow_button>
    <p class="mt-4 text-base leading-7 text-zinc-600">
      Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.
    </p>
...
</div>
```

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

```bash
mix phx.server
```

![スクリーンショット 2024-12-25 14.15.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/5b0f98c2-1156-8ae4-ed34-47d4f90a75ea.png)

確かにコンポーネントが表示されました

## まとめ

コンポーネント単位で必要なものだけインストールできるのは良いですね

ちなみに、アバターは DiceBear というアバター作成サービスを利用していて、顔のパーツや角度などを調節して、自分のアバターを作成できます

https://www.dicebear.com/

他にも面白いコンポーネントが充実していくと良いですね