Packages
petal_components
0.15.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 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-700 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 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-700 border-gray-200 text-gray-400">...</span>
</li>
<% end %>
<%= if item.type == "next" do %>
<div>
<.link 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-700 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_start = max(total_pages - boundary_count + 1, boundary_count + 1)
end_pages_end = total_pages
end_pages = end_pages_start..end_pages_end |> 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, only: total_pages == 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 =
if siblings_start <= siblings_end do
Enum.reduce(siblings_start..siblings_end, items, fn i, acc ->
acc ++ [%{type: "page", number: i}]
end)
else
items
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 =
if end_pages_start <= end_pages_end do
Enum.reduce(end_pages, items, fn i, acc ->
acc ++ [%{type: "page", number: i, last: i == total_pages}]
end)
else
items
end
# Next button
items = if current_page < total_pages,
do: items ++ [%{type: "next", number: current_page + 1}],
else: items
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 dark:border-gray-700"
active_classes =
if is_active,
do: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300",
else: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-400"
rounded_classes =
cond do
item[:only] ->
"rounded"
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