I like the breadcrumb navigation in a web app so I wanted to make one what works in Phoenix LiveView apps.
what breadcrumb navigation is
I think I first learned about the breadcrumb from Bootstrap a long time ago, but I do not know the exact definition of it. Let's wikipedia.
A breadcrumb or breadcrumb trail is a graphical control element used as a navigational aid in user interfaces and on web pages. It allows users to keep track and maintain awareness of their locations within programs, documents, or websites.
https://en.wikipedia.org/wiki/Breadcrumb_navigation
my custom breadcrumb component
I do not need anything smart. Simple is best. My breadcrumb component simply accepts a list of navigation items. It might be used like below.
<.breadcrumb items={[
%{text: "Home", navigate: ~p"/"},
%{text: "Examples", navigate: ~p"/examples"},
%{text: "Light"}
]} />
By the way, the LiveView pages in the screen recording above are from Pragmatic Studio Phoenix LiveView Course. The online course is amazing. I added my custom breadcrumb component onto them for my extra learning.
HTML and Tailwind classes
Phoenix 1.7 comes with Tailwind CSS by default. Tailwind CSS just works without any configuration changes.
First of all, I did the Internet search for an example HTML snippet with Tailwind CSS classes because my primary focus is to enjoy programming with Elixir and Phoenix, not HTML, not Tailwind CSS.
I started by using this example snippet from Flowbite.
https://flowbite.com/docs/components/breadcrumb
Phoenix.Component.link/1
For links, I took advangate of Phoenix.Component.link/1 and its :navaigate
attribute so I can navigate between LiveView pages smoothely.
https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#link/1
icons
Phoenix 1.7 ships with SVG icons from heroicons and those SVG icons can be used easily via MyAppWeb.CoreComponents.icon component.
Since all the functions in MyAppWeb.CoreComponents
module are imported by default, the module name can be omitted when we use the icon component.
Thanks to the icon component, we no longer need to wrap our head around in terms of setting up icons, as long as we are happy with what heroicons library provides. It is very nice.
building breadcrumb component
I created a new module named MyAppWeb.Breadcrumb
that is dedicated for the breadcrumb function component. Alternatively it could have been in MyAppWeb.CoreComponents
along with the icon component but I chose to create a new module so that I can focus on the breadcrumb concern. I might change my mind in the future.
Here is the entirety of the module I created.
defmodule MyAppWeb.Breadcrumb do
use Phoenix.Component
import MyAppWeb.CoreComponents
attr :items, :list, required: true
def breadcrumb(assigns) do
assigns = assign(assigns, :size, length(assigns.items))
~H"""
<nav class="flex" aria-label="breadcrumb">
<ol class="inline-flex items-center space-x-1 md:space-x-3">
<.breadcrumb_item
:for={{item, index} <- Enum.with_index(@items)}
type={index_to_item_type(index, @size)}
navigate={item[:navigate]}
text={item[:text]}
/>
</ol>
</nav>
"""
end
defp index_to_item_type(0, _size), do: "first"
defp index_to_item_type(index, size) when index == size - 1, do: "last"
defp index_to_item_type(_index, _size), do: "middle"
attr :type, :string, default: "middle"
attr :navigate, :string, default: "/"
attr :text, :string, required: true
defp breadcrumb_item(assigns) when assigns.type == "first" do
~H"""
<li class="inline-flex items-center">
<.link navigate={@navigate} class="inline-flex items-center text-sm font-medium">
<.icon name="hero-home" class="h-4 w-4" />
</.link>
</li>
"""
end
defp breadcrumb_item(assigns) when assigns.type == "last" do
~H"""
<li aria-current="page">
<div class="flex items-center">
<.icon name="hero-chevron-right" class="h-4 w-4" />
<span class="ml-1 text-sm font-medium md:ml-2">
<%= @text %>
</span>
</div>
</li>
"""
end
defp breadcrumb_item(assigns) do
~H"""
<li>
<div class="flex items-center">
<.icon name="hero-chevron-right" class="h-4 w-4" />
<.link navigate={@navigate} class="ml-1 text-sm font-medium md:ml-2 ">
<%= @text %>
</.link>
</div>
</li>
"""
end
end
only one public function
The module has only one public function called breadcrumb
, which is a function component. For readability, I factored out a sub-component breadcrumb_item
that maps provided info to the breadcrumb item markup according to the breadcrumb item type.
three types of breadcrumb items
Typically the breadcrumb navigation has three types of items and they have different looks and behaviors.
- the leftmost
- the home
- link enabled
- can look special with home icon
- the rightmost
- the current
- link disabled
- the in-between
- all other paths between the home and the current
- link enabled
For each type, the sub-component breadcrumb_item
renders differently. We can easily determine the appropriate breadcrumb item type by the index and length of the list. The private function index_to_item_type
does the job.
We need one preparation before rendering the component. The length of the list varies so we need to find out the list length beforehand.
Enum.with_index/2 gives an index to each element of the list. With that index and the pre-calculated list length, we can determine the item type.
Top comments (0)