lib/phoenix/ui/components/breadcrumbs.ex

defmodule Phoenix.UI.Components.Breadcrumbs do
  @moduledoc """
  Provides breadcrumbs component.
  """
  import Phoenix.UI.Components.{Heroicon, Link}

  use Phoenix.UI, :component

  @default_link_color "slate"
  @default_separator "chevron-right"

  @doc """
  Renders breadcrumbs component.

  ## Examples

      ```
      <.breadcrumbs>
        <:a href={[to: "#"]}>Users</:a>
        <:a href={[to: "#"]}>John Doe</:a>
        <:a>Edit</:a>
      </.breadcrumbs>
      ```

  """
  @spec breadcrumbs(Socket.assigns()) :: Rendered.t()
  def breadcrumbs(raw) do
    assigns =
      raw
      |> assign_new(:separator, fn -> @default_separator end)
      |> build_nav_attrs()
      |> build_separator_attrs()
      |> normalize_links()

    ~H"""
    <nav {@nav_attrs}>
      <ol class="flex items-center space-x-2" role="list">
        <%= for link <- @a do %>
          <li class="flex items-center">
            <.a {link}>
              <%= render_slot(link) %>
            </.a>
            <%= if !link[:"aria-current"] do %>
              <.heroicon {@separator_attrs} />
            <% end %>
          </li>
        <% end %>
      </ol>
    </nav>
    """
  end

  ### Nav Attrs ##########################

  defp build_nav_attrs(assigns) do
    class = build_class(~w(
      breadcrumb text-sm font-medium my-6
      #{Map.get(assigns, :extend_class)}
    ))

    attrs =
      assigns
      |> assigns_to_attributes([:extend_class, :a, :size])
      |> Keyword.put_new(:class, class)
      |> Keyword.put_new(:"aria-label", "Breadcrumb")

    assign(assigns, :nav_attrs, attrs)
  end

  ### Separator Attrs ##########################

  defp build_separator_attrs(assigns) do
    attrs = %{
      name: assigns[:separator],
      extend_class: "ml-2",
      color: "slate"
    }

    assign(assigns, :separator_attrs, attrs)
  end

  ### Normalize Links ##########################

  defp normalize_links(assigns) do
    links =
      assigns
      |> Map.get(:a, [])
      |> Enum.reverse()
      |> Enum.reduce([], fn link, acc ->
        updated = link |> Map.put_new(:color, @default_link_color) |> apply_aria_current(acc)
        [updated | acc]
      end)

    assign(assigns, :a, links)
  end

  defp apply_aria_current(link, []), do: Map.put(link, :"aria-current", "page")
  defp apply_aria_current(link, _acc), do: link
end