261 lines
6.1 KiB
Elixir
261 lines
6.1 KiB
Elixir
defmodule RiverConnectWeb.Components.UI.Pagination do
|
|
@moduledoc """
|
|
Implementation of pagination component for navigation between pages of content.
|
|
|
|
## Examples:
|
|
|
|
<.pagination>
|
|
<.pagination_content>
|
|
<.pagination_item>
|
|
<.pagination_previous href="#" />
|
|
</.pagination_item>
|
|
<.pagination_item>
|
|
<.pagination_link href="#" is_active={@page == 1}>1</.pagination_link>
|
|
</.pagination_item>
|
|
<.pagination_item>
|
|
<.pagination_link href="#" is_active={@page == 2}>2</.pagination_link>
|
|
</.pagination_item>
|
|
<.pagination_item>
|
|
<.pagination_link href="#" is_active={@page == 3}>3</.pagination_link>
|
|
</.pagination_item>
|
|
<.pagination_item>
|
|
<.pagination_ellipsis />
|
|
</.pagination_item>
|
|
<.pagination_item>
|
|
<.pagination_next href="#" />
|
|
</.pagination_item>
|
|
</.pagination_content>
|
|
</.pagination>
|
|
"""
|
|
use RiverConnectWeb.Components.UI, :component
|
|
|
|
@doc """
|
|
Renders a pagination component.
|
|
"""
|
|
attr :class, :string, default: nil
|
|
attr :rest, :global
|
|
slot :inner_block, required: true
|
|
|
|
def pagination(assigns) do
|
|
~H"""
|
|
<nav
|
|
aria-label="pagination"
|
|
role="pagination"
|
|
class={
|
|
classes([
|
|
"mx-auto flex w-full justify-center",
|
|
@class
|
|
])
|
|
}
|
|
{@rest}
|
|
>
|
|
{render_slot(@inner_block)}
|
|
</nav>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Renders pagination content wrapper.
|
|
"""
|
|
attr :class, :string, default: nil
|
|
attr :rest, :global
|
|
slot :inner_block, required: true
|
|
|
|
def pagination_content(assigns) do
|
|
~H"""
|
|
<ul
|
|
class={
|
|
classes([
|
|
"flex flex-row items-center gap-1",
|
|
@class
|
|
])
|
|
}
|
|
{@rest}
|
|
>
|
|
{render_slot(@inner_block)}
|
|
</ul>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Renders a pagination item container.
|
|
"""
|
|
attr :class, :string, default: nil
|
|
attr :rest, :global
|
|
slot :inner_block, required: true
|
|
|
|
def pagination_item(assigns) do
|
|
~H"""
|
|
<li
|
|
class={
|
|
classes([
|
|
"",
|
|
@class
|
|
])
|
|
}
|
|
{@rest}
|
|
>
|
|
{render_slot(@inner_block)}
|
|
</li>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Renders a pagination link.
|
|
|
|
## Options
|
|
|
|
* `:is_active` - Whether this link represents the current page
|
|
* `:size` - Link size variant (default, sm, lg, icon)
|
|
"""
|
|
attr :"is-active", :boolean, default: false
|
|
attr :size, :string, default: "icon", values: ~w(default sm lg icon)
|
|
attr :class, :string, default: nil
|
|
attr :rest, :global
|
|
slot :inner_block, required: true
|
|
|
|
def pagination_link(assigns) do
|
|
is_active = assigns[:"is-active"] in [true, "true"]
|
|
|
|
assigns =
|
|
assigns
|
|
|> assign(
|
|
:variation_class,
|
|
variant(%{size: assigns[:size], variant: (is_active && "outline") || "ghost"})
|
|
)
|
|
|> assign(:"is-active", is_active)
|
|
|
|
~H"""
|
|
<.link
|
|
aria-current={(assigns[:"is-active"] && "page") || ""}
|
|
class={
|
|
classes([
|
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50",
|
|
@variation_class,
|
|
@class
|
|
])
|
|
}
|
|
{@rest}
|
|
>
|
|
{render_slot(@inner_block)}
|
|
</.link>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Renders a next page button.
|
|
"""
|
|
attr :class, :string, default: nil
|
|
attr :rest, :global
|
|
|
|
def pagination_next(assigns) do
|
|
~H"""
|
|
<.pagination_link
|
|
aria-label="Go to next page"
|
|
size="default"
|
|
class={classes(["gap-1 pr-2.5", @class])}
|
|
{@rest}
|
|
>
|
|
<span>Next</span>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke-width="2"
|
|
stroke="currentColor"
|
|
class="size-6 w-3.5"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
|
</svg>
|
|
</.pagination_link>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Renders a previous page button.
|
|
"""
|
|
attr :class, :string, default: nil
|
|
attr :rest, :global
|
|
|
|
def pagination_previous(assigns) do
|
|
~H"""
|
|
<.pagination_link
|
|
aria-label="Go to previous page"
|
|
size="default"
|
|
class={classes(["gap-1 pr-2.5", @class])}
|
|
{@rest}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke-width="2"
|
|
stroke="currentColor"
|
|
class="size-6 w-3.5"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
|
</svg>
|
|
<span>Previous</span>
|
|
</.pagination_link>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Renders an ellipsis for page ranges.
|
|
"""
|
|
attr :class, :string, default: nil
|
|
attr :rest, :global
|
|
|
|
def pagination_ellipsis(assigns) do
|
|
~H"""
|
|
<span
|
|
class={
|
|
classes([
|
|
"flex h-9 w-9 items-center justify-center",
|
|
@class
|
|
])
|
|
}
|
|
{@rest}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke-width="2"
|
|
stroke="currentColor"
|
|
class="size-6 w-4 h-4"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
|
|
/>
|
|
</svg>
|
|
|
|
<span class="sr-only">More pages</span>
|
|
</span>
|
|
"""
|
|
end
|
|
|
|
@variants %{
|
|
variant: %{
|
|
"outline" =>
|
|
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
"ghost" => "hover:bg-accent hover:text-accent-foreground"
|
|
},
|
|
size: %{
|
|
"default" => "h-9 px-4 py-2",
|
|
"sm" => "h-8 rounded-md px-3 text-xs",
|
|
"lg" => "h-10 rounded-md px-8",
|
|
"icon" => "h-9 w-9"
|
|
}
|
|
}
|
|
|
|
defp variant(props) do
|
|
variants =
|
|
Map.take(props, ~w(variant size)a)
|
|
|
|
Enum.map_join(variants, " ", fn {key, value} -> @variants[key][value] end)
|
|
end
|
|
end
|