Packages

A Phoenix LiveView component library

Current section

Files

Jump to
ptah_ui lib ptah_ui components pagination.ex
Raw

lib/ptah_ui/components/pagination.ex

defmodule PtahUi.Components.Pagination do
@moduledoc """
Generic join-based pagination component — groups buttons with shared borders
(the DaisyUI `join` pattern).
## Example
<.pager>
<:item disabled>«</:item>
<:item active={@page == 1} on_click={JS.push("goto", value: %{page: 1})}>1</:item>
<:item active={@page == 2} on_click={JS.push("goto", value: %{page: 2})}>2</:item>
<:item on_click={JS.push("goto", value: %{page: @page + 1})}>»</:item>
</.pager>
"""
use Phoenix.Component
attr :size, :string, default: "md", values: ~w(xs sm md lg),
doc: "Size of each page button."
attr :direction, :string, default: "horizontal", values: ~w(horizontal vertical),
doc: "Layout direction."
attr :equal_width, :boolean, default: false,
doc: "Stretch all items to equal width (useful for Prev/Next two-button layout)."
attr :class, :string, default: nil
attr :rest, :global
slot :item, required: true, doc: "A single page button or nav link." do
attr :active, :boolean, doc: "Marks this page as current (dark fill)."
attr :disabled, :boolean, doc: "Grays out and disables the button."
attr :on_click, :any, doc: "phx-click event."
attr :href, :string, doc: "Renders an <a> tag instead of a button."
attr :class, :string
end
def pager(assigns) do
items_wi = Enum.with_index(assigns.item)
n = length(assigns.item)
assigns = assign(assigns, _items: items_wi, _n: n)
~H"""
<div
class={[
"flex",
@direction == "vertical" && "flex-col",
@equal_width && "[&>*]:flex-1",
@class
]}
{@rest}
>
<%= for {item, i} <- @_items do %>
<%= if item[:href] do %>
<a
href={item[:href]}
class={[item_class(@size, @direction, i, @_n, item)]}
>
<%= render_slot(item) %>
</a>
<% else %>
<button
type="button"
phx-click={item[:on_click]}
disabled={item[:disabled]}
class={[item_class(@size, @direction, i, @_n, item)]}
>
<%= render_slot(item) %>
</button>
<% end %>
<% end %>
</div>
"""
end
defp item_class(size, direction, i, n, item) do
is_first = i == 0
is_last = i == n - 1
[
"relative inline-flex items-center justify-center font-medium transition-colors",
"select-none whitespace-nowrap focus:outline-none",
size_class(size),
"border border-gray-300",
# border collapsing
direction == "horizontal" && !is_first && "-ml-px",
direction == "vertical" && !is_first && "-mt-px",
# border radius
direction == "horizontal" && is_first && "rounded-l-lg",
direction == "horizontal" && is_last && "rounded-r-lg",
direction == "vertical" && is_first && "rounded-t-lg",
direction == "vertical" && is_last && "rounded-b-lg",
# color states
state_class(item[:active], item[:disabled]),
item[:class]
]
end
defp state_class(_active, true),
do: "bg-gray-50 text-gray-300 border-gray-200 cursor-not-allowed pointer-events-none"
defp state_class(true, _),
do: "bg-gray-900 text-white border-gray-900 z-10"
defp state_class(_, _),
do: "bg-white text-gray-700 hover:bg-gray-50 cursor-pointer"
defp size_class("xs"), do: "h-6 min-w-6 text-[11px] px-2"
defp size_class("sm"), do: "h-8 min-w-8 text-xs px-3"
defp size_class("md"), do: "h-10 min-w-10 text-sm px-4"
defp size_class("lg"), do: "h-12 min-w-12 text-base px-5"
end