Packages
petal_components
0.13.0
4.7.0
4.6.2
4.6.1
4.6.0
4.5.0
4.4.0
4.3.0
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.12
4.0.11
4.0.10
4.0.6
4.0.5
4.0.4
4.0.3
4.0.1
3.2.2
3.2.1
3.2.0
3.1.0
3.0.2
3.0.1
3.0.0
2.9.3
2.9.2
retired
2.9.1
retired
2.9.0
retired
2.8.4
2.8.3
2.8.2
2.8.1
2.8.0
2.7.4
2.7.3
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.2
2.5.1
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.1
2.2.0
2.1.2
2.1.1
2.1.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.9.3
1.9.2
1.9.1
1.9.0
1.8.0
1.7.1
1.7.0
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.14
1.2.13
1.2.12
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.19.10
0.19.9
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.15.0
0.14.1
0.14.0
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.0
Shadcn-style Phoenix LiveView components that AI assistants can actually use. Pair with the MCP server so AI coding tools can inspect the real component API.
Current section
Files
Jump to
Current section
Files
lib/petal_components/pagination.ex
defmodule PetalComponents.Pagination do
use Phoenix.Component
alias PetalComponents.Heroicons
import PetalComponents.Link
# prop path, :string
# prop class, :string
# prop sibling_count, :integer
# prop boundary_count, :integer
# prop link_type, :string, options: ["a", "live_patch", "live_redirect"]
@doc """
In the `path` param you can specify :page as the place your page number will appear.
e.g "/posts/:page" => "/posts/1"
"""
def pagination(assigns) do
assigns =
assigns
|> assign_new(:class, fn -> "" end)
|> assign_new(:link_type, fn -> "a" end)
|> assign_new(:sibling_count, fn -> 1 end)
|> assign_new(:boundary_count, fn -> 1 end)
|> assign_new(:path, fn -> "/:page" end)
|> assign_new(:extra_assigns, fn ->
assigns_to_attributes(assigns, ~w(
link_type
sibling_count
boundary_count
total_pages
current_page
path
class
)a)
end)
~H"""
<div {@extra_assigns} class={"#{@class} flex"}>
<ul class="inline-flex -space-x-px text-sm font-medium">
<%= for item <- get_items(@total_pages, @current_page, @sibling_count, @boundary_count) do %>
<%= if item.type == "previous" do %>
<div>
<.link type={@link_type} to={get_path(@path, item.number, @current_page)} class="mr-2 inline-flex items-center justify-center rounded leading-5 px-2.5 py-2 bg-white hover:bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 border dark:border-gray-400 border-gray-200 text-gray-600 hover:text-gray-800">
<Heroicons.Solid.chevron_left class="w-5 h-5 text-gray-600 dark:text-gray-400" />
</.link>
</div>
<% end %>
<%= if item.type == "page" do %>
<li>
<%= if item.number == @current_page do %>
<span class={get_box_class(item, true)}><%= item.number %></span>
<% else %>
<.link type={@link_type} to={get_path(@path, item.number, @current_page)} class={get_box_class(item)}>
<%= item.number %>
</.link>
<% end %>
</li>
<% end %>
<%= if item.type == "ellipsis" do %>
<li>
<span class="inline-flex items-center justify-center leading-5 px-3.5 py-2 bg-white border dark:bg-gray-900 dark:border-gray-400 border-gray-200 text-gray-400">...</span>
</li>
<% end %>
<%= if item.type == "next" do %>
<div>
<.link type={@link_type} to={get_path(@path, item.number, @current_page)} class="ml-2 inline-flex items-center justify-center rounded leading-5 px-2.5 py-2 bg-white hover:bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 dark:border-gray-400 border border-gray-200 text-gray-600 hover:text-gray-800">
<Heroicons.Solid.chevron_right class="w-5 h-5 text-gray-600 dark:text-gray-400" />
</.link>
</div>
<% end %>
<% end %>
</ul>
</div>
"""
end
defp get_items(total_pages, current_page, sibling_count, boundary_count) do
start_pages = 1..min(boundary_count, total_pages) |> Enum.to_list()
end_pages =
max(total_pages - boundary_count + 1, boundary_count + 1)..total_pages |> Enum.to_list()
siblings_start =
max(
min(current_page - sibling_count, total_pages - boundary_count - sibling_count * 2 - 1),
boundary_count + 2
)
siblings_end =
min(
max(current_page + sibling_count, boundary_count + sibling_count * 2 + 2),
if(length(end_pages) > 0, do: List.first(end_pages) - 2, else: total_pages - 1)
)
items = []
# Previous button
items =
if current_page > 1,
do: items ++ [%{type: "previous", number: current_page - 1}],
else: items
# Start pages
items =
Enum.reduce(start_pages, items, fn i, acc ->
acc ++ [%{type: "page", number: i, first: i == 1}]
end)
# First ellipsis
items =
if siblings_start > boundary_count + 2,
do: items ++ [%{type: "ellipsis"}],
else:
if(boundary_count + 1 < total_pages - boundary_count,
do: items ++ [%{type: "page", number: boundary_count + 1}],
else: items
)
# Siblings
items =
Enum.reduce(siblings_start..siblings_end, items, fn i, acc ->
acc ++ [%{type: "page", number: i}]
end)
# Second ellipsis
items =
if siblings_end < total_pages - boundary_count - 1,
do: items ++ [%{type: "ellipsis"}],
else:
if(total_pages - boundary_count > boundary_count,
do: items ++ [%{type: "page", number: total_pages - boundary_count}],
else: items
)
# End pages
items =
Enum.reduce(end_pages, items, fn i, acc ->
acc ++ [%{type: "page", number: i, last: i == total_pages}]
end)
# Next button
if current_page < total_pages,
do: items ++ [%{type: "next", number: current_page + 1}],
else: items
end
defp get_box_class(item, is_active \\ false) do
base_classes =
"inline-flex items-center justify-center leading-5 px-3.5 py-2 border border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 dark:border-gray-400 border border-gray-200 text-gray-600 hover:text-gray-800"
active_classes =
if is_active,
do: "bg-gray-100 dark:bg-gray-800 dark:text-gray-300 hover:bg-gray-100 text-gray-800",
else: "dark:text-gray-400 text-gray-600 hover:text-gray-800 dark:hover:text-gray-400"
rounded_classes =
cond do
item[:first] ->
"rounded-l "
item[:last] ->
"rounded-r"
true ->
""
end
Enum.join([base_classes, active_classes, rounded_classes], " ")
end
defp get_path(path, "previous", current_page) do
String.replace(path, ":page", Integer.to_string(current_page - 1))
end
defp get_path(path, "next", current_page) do
String.replace(path, ":page", Integer.to_string(current_page + 1))
end
defp get_path(path, page_number, _current_page) do
String.replace(path, ":page", Integer.to_string(page_number))
end
end