10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirDesktopでスマホ的なUIでアプリを作る〜ナビゲーション〜

Last updated at Posted at 2023-09-12

はじめに

本記事は以下の記事で作成したCRUDアプリにスマホなUIを組み込んでいく内容になります

前提

  • 本棚アプリ
  • サーバーとの通信はしないスタンドアローン
  • DBはローカルのSQlite3
  • 本棚(shelf)のCRUD画面がある
  • confirm modal を作成してある

スマホ的なUIとは?

参考にならないくらいめっちゃオシャレなんですけど、トレンドなUIはこちらから色々見ることができます

こちらを例にして要素を抽出しますと

  • 現在のページ名、戻る、サブアクションを表示するヘッダー
  • グローバルメニューとして使うボトムタブナビゲーション
  • コンテンツをカードにして境界をわかりやすくしている

  • 最初起動したときにどんなアプリかを軽く説明する Welcome画面
  • 詳細に段階的に説明したり、実際に1つやってみましょうとガイドするオンボーディングもある
  • コアな機能に素早くアクセスできる Floating Action Button

といったものが多く見られます

これらを踏まえてアプリとして作成する場合に以下が必要になりそうです

  • UI部分
    • コンポーネントライブラリ daisyui
    • ヘッダー
    • ボトムタブ
    • フローティングアクションボタン
    • Welcome画面
    • カードUI
  • 設定画面
    • 個人設定
    • ライセンス
    • 利用規約
    • お問い合わせ
    • プライバシーポリシー
    • ログアウト/初期化
  • API通信
    • Google Books
    • APIキーの保存
  • ネイティブ側
    • アイコンを変える
    • スプラッシュ画面を変える
    • アプリ名を変える
    • ネイティブ機能を使う(ブラウザを開く)

UIコンポーネントライブラリ

PhoenixではデフォルトでCSSフレームワークでTailwindCSSが採用されています
ですが、PhoenixのCoreComponentとTailwindでUIを実装するのは大変なので、
UIコンポーネントライブラリを使用して、ちゃちゃっと作りましょう

UIコンポーネントの選定は気をつけることがあって、
ReactやVue,SveltなどJSのフレームワークの上で動かすものが多くヒットするので
Tailwind単体で動いて、JSを使用していないもの、またはLiveViewに対応している物を選ぶ必要があります
JSを使用していないものを選ぶのは、LiveViewでUI制御を行うときに干渉してしまうため調整が面倒だからです

Tailwind単体で動いて、JSを使用していないもの

こちらは2種類あります

  1. tailwindのユーティリティクラスだけで作成した実装サンプル集
  2. applyでcssのクラス的にまとめたコンポーネント集

大半は1です実装の時にこのUIどう実装するのかがわかるので、参考にするのにも大変良いです

ですがこれだと、記述がどうしても多くなってしまいますので
いい感じにまとめてくれている2を心情的には使いたいのですが、該当するものはほとんどなく
いい感じに揃っているのはこちらです

今回はこれを使用します

LiveViewに対応している物を選ぶ必要があります

一応LiveViewの文脈に沿ってくれるライブラリは提供しているのですが、
重厚そうなので今回は見送ります
ライトに使えて、ライトに改修できる方が楽なので

実装例は結構あるので参考にすると良いかもしれません

ではライブラリをインストールします
JSのライブラリをインストールするときは assetsに移動する必要があるので注意が必要です

cd assets
npm i daisyui

インストールしたら、pluginsに requireを追加します

ebookworm/assets/tailwind.config.js
module.exports = {
  content: ["./js/**/*.js", "../lib/*_web.ex", "../lib/*_web/**/*.*ex"],
  theme: {...},
  plugins: [
    require("@tailwindcss/forms"),
    require("daisyui"), // 追加
    ...
  ],
  

これでDaisyUIが使用可能になりました

ヘッダー

全体で共通して表示するヘッダーコンポーネントを作成します
最初に不要なデフォルトのヘッダーを削除します

lib/ebookworm_web/components/layouts/app.html.heex
- <header class="px-4 sm:px-6 lg:px-8">
-  <div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
-    <div class="flex items-center gap-4">
-      <a href="/">
-        <img src={~p"/images/logo.svg"} width="36" />
-      </a>
-      <p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
-        v<%= Application.spec(:phoenix, :vsn) %>
-      </p>
-    </div>
-    <div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
-      <a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
-        @elixirphoenix
-      </a>
-      <a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700">
-        GitHub
-      </a>
-      <a
-        href="https://hexdocs.pm/phoenix/overview.html"
-        class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80"
-      >
-        Get Started <span aria-hidden="true">&rarr;</span>
-      </a>
-    </div>
-  </div>
- </header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
  <div class="mx-auto max-w-2xl">
    <.flash_group flash={@flash} />
    <%= @inner_content %>
  </div>
</main>

UIコンポーネントはこちらを使います

コンポーネントファイルを作成してそこに記載します

今回作るコンポーネントは Phoenix.Componentを使用して、ステートレスなコンポーネントで与えられた引数の値を表示するだけのコンポーネントになります

状態をもたせる(ステートフル)なコンポーネントは Phoenix.LiveComponentを作成します

通常のComponentは <.link>...<./link>といったふうに使用できますが
LiveComponentは <.live_component /> 内でモジュールを指定し、ID属性が必須になります

では navigation.exというファイルを作成してコンポーネントを実装しましょう

lib/ebookworm_web/components/navigation.ex
defmodule EbookwormWeb.Navigation do
  use Phoenix.Component

  attr(:title, :string, default: "")

  slot(:back, doc: "add back navigation within .link component")
  slot(:actions, doc: "add action navigation such as add, edit and etc... within .link component")

  def gheader(assigns) do
    ~H"""
    <div class="fixed navbar bg-orange-300 text-white w-full z-10 top-0 left-0">
      <div class="navbar-start">
        <span :if={@back != []} class="normal-case text-xl">
          <%= render_slot(@back) %>
        </span>
      </div>
      <div class="navbar-center">
        <span class="normal-case text-4xl">
          <%= @title %>
        </span>
      </div>
      <div class="navbar-end">
        <span :if={@actions != []} class="normal-case text-xl">
          <%= render_slot(@actions) %>
        </span>
      </div>
    </div>
    """
  end
end

attrとslotの2つの要素が関数の上に記載されています

  • attrは呼び出し元で設定できるパラメータです、デフォルト値や必須フラグ、ドキュメントを含めることができます
  • slotはslotに指定した名前で :tagで囲んだ要素を渡すことによってrender_slotの箇所にレンダリングされます

頻繁に使用するコンポーネントは一々importするのが面倒なので
xx_web.exで他のと一緒に読み込んでもらうようにします

lib/ebookworm_web.ex:L82
defmodule EbookwormWeb
...
  defp html_helpers do
    quote do
      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components and translation
      import EbookwormWeb.CoreComponents
      import EbookwormWeb.Navigation # 追加
      import EbookwormWeb.Gettext

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

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

では実際にindex.exとshow.exで使用してみましょう

lib/ebookworm_web/live/shlef_live/index.html.heex
+ <.gheader title="Shleves">
+   <:actions>
+     <.link patch={~p"/shelves/new"}>
+         <.icon name="hero-plus-solid" class="h-6 w-6 mr-4" />
+     </.link>
+   </:actions>
+ </.gheader>

- <.header>
-  Listing Shelves
-  <:actions>
-    <.link patch={~p"/shelves/new"}>
-      <.button>New Shelf</.button>
-    </.link>
-  </:actions>
- </.header>
...

もとからある.headerをけして先程作成した.gheaderを追加します
右側のアクション領域に新規作成用のボタンを付けます
CoreComponentに.iconがありそこからheroiconsを使えるので +(plus)を表示します

命名規則は hero-[icon名]-[solid or outline]となってます

スクリーンショット 2023-09-09 23.38.15.png

表示はこんな感じになります

show.exにも追加しましょう

lib/ebookworm_web/live/shlef_live/show.html.heex
+ <.gheader title={@shelf.name}>
+  <:back>
+    <.link navigate={~p"/shelves"}>
+      <.icon name="hero-chevron-left-solid" class="h-6 w-6"/>
+    </.link>
+  </:back>

+  <:actions>
+    <.link patch={~p"/shelves/#{@shelf}/show/edit"} phx-click={JS.push_focus()}>
+      <.icon name="hero-pencil-square-solid" class="h-6 w-6 mr-4" />
+    </.link>
+  </:actions>
+ </.gheader>

- <.header>
-  Shelf <%= @shelf.id %>
-  <:subtitle>This is a shelf record from your database.</:subtitle>
-  <:actions>
-    <.link patch={~p"/shelves/#{@shelf}/show/edit"} phx-click={JS.push_focus()}>
-      <.button>Edit shelf</.button>
-    </.link>
-  </:actions>
- </.header>

<.list>
  <:item title="Name"><%= @shelf.name %></:item>
</.list>

- <.back navigate={~p"/shelves"}>Back to shelves</.back>
...

戻るボタンは chevron-leftを設定します

スクリーンショット 2023-09-09 23.51.23.png

こんな感じになります

ボトムタブナビゲーション

本棚一覧の他に本検索画面と、設定画面を作りたいのでナビゲーションを作ります

コンポーネントはこちらを使います

lib/ebookworm_web/components/navigation.ex
defmodule EbookwormWeb.Navigation do
  use Phoenix.Component

  import EbookwormWeb.CoreComponents # 追加
  ...
  attr(:title, :string, default: "")

  def bottom_tab(assigns) do
    ~H"""
    <div class="btm-nav">
      <%= for {title, icon ,path} <- links() do %>
        <a href={path} class={if @title == title, do: "active", else: ""}>
          <button>
            <.icon name={icon} class="w-5 h-5" />
            <p class="btm-nav-label"><%= title %></p>
          </button>
        </a>
      <% end %>
    </div>
    """
  end

  defp links() do
    [
      {"Shelf", "hero-book-open-solid", "/shelves"},
      {"Search", "hero-magnifying-glass-solid", "/search"},
      {"Setting", "hero-cog-6-tooth-solid", "/settings"}
    ]
  end
end

linksで定義したリンクでタブを作成し、ページのタイトルと同じタブをアクティブ状態にします

早速使ってみましょう
コンテンツ部分とモーダルの間あたりにいれます、今回は.tableの下になります

lib/ebookworm_web/live/shlef_live/index.html.heex
...
<.table
  id="shelves"
  rows={@streams.shelves}
  row_click={fn {_id, shelf} -> JS.navigate(~p"/shelves/#{shelf}") end}
>
  ... 
</.table>

<.bottom_tab title="Shelf" />

スクリーンショット 2023-09-10 17.26.58.png

lib/ebookworm_web/live/shlef_live/show.html.heex
...
<.list>
  <:item title="Name"><%= @shelf.name %></:item>
</.list>

<.bottom_tab title="Shelf" />
...

スクリーンショット 2023-09-10 17.32.12.png

最後に

本記事ではスマホっぽいUIのヘッダーとボトムタブナビゲーションを実装しました
次はAPIを叩いてデータを持ってきて保存するところまで行います

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?