Skip to content

Commit

Permalink
Breadcrumb component migration
Browse files Browse the repository at this point in the history
Makes the following updates:
  - migrates/modernizes breadcrumb component
  - adds breadcrumb story
  - migrates tests
  • Loading branch information
keatz55 authored Dec 13, 2024
1 parent 0555dc7 commit 7de19e0
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
60 changes: 60 additions & 0 deletions lib/phoenix_ui_web/components/breadcrumb.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
defmodule PhoenixUIWeb.Components.Breadcrumb do
@moduledoc """
Provides breadcrumb-related components.
"""
use Phoenix.Component

alias Phoenix.LiveView.Rendered
alias Phoenix.LiveView.Socket

import PhoenixUIWeb.CoreComponents

@doc """
A breadcrumbs is a list of links that help visualize a page's location
within a site's hierarchical structure, it allows navigation up to any
of the ancestors.
## Examples
```heex
<.breadcrumb>
<:item>Phoenix UI</:item>
<:item>Core</:item>
<:item 'aria-current="page"'>Breadcrumbs</:item>
</.breadcrumb>
```
"""

attr :class, :any, doc: "Extend existing component styles"
attr :separator_icon, :string, default: "hero-chevron-right-mini"
attr :rest, :global

slot :item, required: true, validate_attrs: false

@spec breadcrumb(Socket.assigns()) :: Rendered.t()
def breadcrumb(assigns) do
~H"""
<nav aria-label="Breadcrumb" class={["breadcrumb flex", assigns[:class]]} {@rest}>
<ol role="list" class="flex items-center space-x-4 text-sm font-medium">
<%= for {item, idx} <- Enum.with_index(@item) do %>
<li :if={idx != 0} aria-hidden="true" class="text-zinc-400 dark:text-zinc-600">
<.icon class="icon h-5 w-5" name={@separator_icon} />
</li>
<li class="flex items-center">
<.link
class={[
"text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200 transition-colors duration-300",
"aria-[current=page]:text-zinc-700 dark:aria-[current=page]:text-zinc-200",
item[:class]
]}
{assigns_to_attributes(item, [:class])}
>
{render_slot(item)}
</.link>
</li>
<% end %>
</ol>
</nav>
"""
end
end
38 changes: 38 additions & 0 deletions storybook/components/breadcrumb.story.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Storybook.Components.Breadcrumb do
use PhoenixStorybook.Story, :component

def function, do: &PhoenixUIWeb.Components.Breadcrumb.breadcrumb/1

def variations do
[
%Variation{
id: :basic_breadcrumbs,
slots: [
"""
<:item>Phoenix UI</:item>
<:item>Core</:item>
<:item aria-current="page">Breadcrumbs</:item>
"""
]
},
%VariationGroup{
id: :custom_separator,
variations:
for icon <-
~w(hero-slash-mini hero-chevron-double-right-mini hero-arrow-right-mini hero-arrow-right-circle-mini)a do
%Variation{
attributes: %{separator_icon: to_string(icon)},
id: icon,
slots: [
"""
<:item>Phoenix UI</:item>
<:item>Core</:item>
<:item aria-current="page">Breadcrumbs</:item>
"""
]
}
end
}
]
end
end
73 changes: 73 additions & 0 deletions test/phoenix_ui_web/components/breadcrumb_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
defmodule PhoenixUIWeb.Components.BreadcrumbTest do
use PhoenixUIWeb.ConnCase, async: true

import PhoenixUIWeb.Components.Breadcrumb

setup do
[assigns: %{}]
end

describe "breadcrumb/1" do
test "should render with defaults", %{assigns: assigns} do
markup = ~H"""
<.breadcrumb id="basic-breadcrumb">
<:item>Phoenix UI</:item>
<:item>Core</:item>
<:item aria-current="page">Breadcrumbs</:item>
</.breadcrumb>
"""

assert {:ok, doc} = markup |> rendered_to_string() |> Floki.parse_document()

assert Floki.attribute(doc, "nav", "aria-label") == ["Breadcrumb"]
assert Floki.attribute(doc, "nav", "id") == ["basic-breadcrumb"]
assert hd(Floki.attribute(doc, "nav", "class")) =~ "breadcrumb "

assert Floki.attribute(doc, "ol", "role") == ["list"]

assert doc |> Floki.find("[aria-current=page]") |> Floki.text() =~ "Breadcrumbs"
end

test "should render with set separator_icon value", %{assigns: assigns} do
markup = ~H"""
<.breadcrumb separator_icon="hero-cake">
<:item>Phoenix UI</:item>
<:item>Core</:item>
<:item aria-current="page">Breadcrumbs</:item>
</.breadcrumb>
"""

assert {:ok, doc} = markup |> rendered_to_string() |> Floki.parse_document()

assert hd(Floki.attribute(doc, "span.icon", "class")) =~ "hero-cake"
end

test "should render with extended classes", %{assigns: assigns} do
markup = ~H"""
<.breadcrumb class="extended-class">
<:item>Phoenix UI</:item>
<:item>Core</:item>
<:item aria-current="page">Breadcrumbs</:item>
</.breadcrumb>
"""

assert {:ok, doc} = markup |> rendered_to_string() |> Floki.parse_document()

assert hd(Floki.attribute(doc, "nav", "class")) =~ "extended-class"
end

test "should render with set `:rest` attribute", %{assigns: assigns} do
markup = ~H"""
<.breadcrumb data-test="abc">
<:item>Phoenix UI</:item>
<:item>Core</:item>
<:item aria-current="page">Breadcrumbs</:item>
</.breadcrumb>
"""

assert {:ok, doc} = markup |> rendered_to_string() |> Floki.parse_document()

assert Floki.attribute(doc, "nav", "data-test") == ["abc"]
end
end
end

0 comments on commit 7de19e0

Please sign in to comment.