Can I render a Phoenix Component interactively inside a for?

Recently I created a simple library to provide function components to render lucide icons, formally feather icons. My complete code can be found here.

Then in my application, I have these function components:

  1. Navbar
  attr :conn, :any
  attr :hidden?, :boolean, default: true

  def navbar(assigns) do
    ~H"""
    <nav class="navbar bg-white w-full">
      <div class="navbar-start p-1 w-3/4 flex justify-between lg:hidden">
        <div class="dropdown">
          <label tabindex="0">
            <!-- hamburguer icon -->
            <svg
              xmlns="http://www.w3.org/2000/svg"
              class="h-12 w-12"
              fill="none"
              viewBox="0 0 24 24"
              stroke="#F8961E"
            >
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="2"
                d="M4 6h16M4 12h8m-8 6h16"
              />
            </svg>
          </label>
          <ul
            tabindex="0"
            class="menu menu-compact dropdown-content dropdown-left mt-3 p-2 shadow bg-white rounded-box w-52"
          >
            <.menu_links current_user={@conn.assigns.current_user} path={@conn.path_info} />
          </ul>
        </div>
        <.menu_logo hidden?={@hidden?} />
      </div>
      <div class="navbar-center container hidden lg:flex lg:justify-center">
        <ul class="menu menu-horizontal p-0">
          <.menu_logo hidden?={false} />
          <.menu_links current_user={@conn.assigns.current_user} path={@conn.path_info} />
        </ul>
      </div>
    </nav>
    """
  end
  1. Menu links
  attr :path, :string
  attr :current_user, Pescarte.Accounts.Models.User, default: nil

  defp menu_links(assigns) do
    ~H"""
    <%= if @current_user do %>
      <%= for item <- authenticated_menu() do %>
        <.menu_item
          path={item.path}
          label={item.label}
          method={item.method}
          current?={is_current_path?(@conn, item.path)}
        >
          <%= render_slot(item.icon) %>
        </.menu_item>
      <% end %>
      <.link type="button" navigate={~p"/acessar"}>
        Acessar
      </.link>
    <% else %>
      <%= for item <- guest_menu() do %>
        <.menu_item
          path={item.path}
          label={item.label}
          method={item.method}
          current?={is_current_path?(@path, item.path)}
        >
          <Lucideicons.{item.icon} />
        </.menu_item>
      <% end %>
      <.link type="button" navigate={~p"/acessar"}>
        Acessar
      </.link>
    <% end %>
    """
  end

  defp is_current_path?(path_info, to) do
    # get from %Plug.Conn{}
    path = Enum.join(path_info, "/")

    to =~ path
  end

  defp guest_menu do
    [
      %{path: "/", label: "Home", method: :get, icon: &Lucideicons.home/1},
      %{path: "/pesquisa", label: "Pesquisa", method: :get, icon: "file"},
      %{path: "/biblioteca", label: "Biblioteca", method: :get, icon: "book"},
      %{
        path: "/agenda_socioambiental",
        label: "Agenda Socioambiental",
        method: :get,
        icon: "agenda"
      }
    ]
  end

  defp authenticated_menu do
    [
      %{path: "/app/dashboard", label: "Home", method: :get, icon: "home"},
      %{path: "/app/pesquisadores", label: "Pesquisadores", method: :get, icon: "accounts"},
      %{path: "/app/relatorios", label: "RelatĂłrios", method: :get, icon: "file"},
      %{path: "/app/agenda", label: "Agenda", method: :get, icon: "agenda"},
      %{path: "/app/mensagens", label: "Mensagens", method: :get, icon: "message"}
    ]
  end
  1. Menu Item
  attr :path, :string
  attr :method, :string, default: "get"
  attr :current?, :boolean, default: false
  attr :label, :string

  slot :icon, required: false

  defp menu_item(assigns) do
    ~H"""
    <li class="menu-item">
      <.link navigate={@path} method={@method} class={@current? && "current"}>
        <%= if assigns[:icon], do: render_slot(@icon) %>
        <%= @label %>
      </.link>
    </li>
    """
  end

Before I built the lucide icons library, I used to render icons manually. Still, for now, I want to render those function components provided by my library, however, I don’t know if it’s possible.

So I think the issue is this line if I understand your post correctly:

That doesn’t work, right? And the idea is that you want dynamically rendered icons.
Someone else was able to figure this out in another icon library.
Check out this this issue.

use Phoenix.Component

attr :rest, :global,
  doc: "the arbitrary HTML attributes for the svg container",
  include: ~w(fill stroke stroke-width)

attr :name, :atom, required: true
attr :outline, :boolean, default: true
attr :solid, :boolean, default: false
attr :mini, :boolean, default: false

def icon(assigns) do
  apply(Heroicons, assigns.name, [assigns])
end
<.icon name={:academic_cap} class="h-4 w-4" />
2 Likes