Current section

Files

Jump to
sigma_kit lib components table.ex
Raw

lib/components/table.ex

defmodule SigmaKit.Components.Table do
use Phoenix.LiveComponent
import SigmaKit.Components.Icons, only: [icon: 1]
@doc """
Renders a table with generic styling.
## Examples
<.table id="users" rows={@users}>
<:col :let={user} label="id">{user.id}</:col>
<:col :let={user} label="username">{user.username}</:col>
</.table>
"""
attr :id, :string, required: true
attr :rows, :list, required: true
attr :row_id, :any, default: nil, doc: "the function for generating the row id"
attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
attr :alternate, :boolean, default: false
attr :paginate, :boolean, default: false
attr :page_number, :integer
attr :total_pages, :integer
attr :page_size, :integer
attr :total_entries, :integer
attr :row_item, :any,
default: &Function.identity/1,
doc: "the function for mapping each row before calling the :col and :action slots"
slot :col, required: true do
attr :label, :string
end
slot :action do
attr :label, :string
attr :icon, :string
attr :event, :string
end
def table(%{rows: %Scrivener.Page{}} = assigns) do
assigns
|> assign(rows: assigns.rows.entries)
|> assign(page_number: assigns.rows.page_number)
|> assign(total_pages: assigns.rows.total_pages)
|> assign(total_entries: assigns.rows.total_entries)
|> assign(page_size: assigns.rows.page_size)
|> assign(paginate: true)
|> table()
end
def table(assigns) do
assigns =
with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
end
~H"""
<div class="overflow-y-auto px-4 sm:overflow-visible sm:px-0 w-full">
<table :if={!Enum.empty?(@rows)} class="w-full">
<thead class="text-sm text-left leading-6 text-zinc-600">
<tr>
<th
:for={col <- @col}
class="uppercase p-0 py-1 pr-6 font-bold border-b border-t border-gray-300 text-xs"
>
{col[:label]}
</th>
<th
:if={!SigmaKit.Util.blank?(@action)}
class="uppercase p-0 py-1 pr-6 font-bold border-b border-t border-gray-300 text-xs"
>
</th>
</tr>
</thead>
<tbody
id={@id}
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
>
<tr
:for={row <- @rows}
id={"row-#{@id}-#{@row_id && @row_id.(row)}"}
class={[
"group border-b",
@row_click && "hover:bg-blue-100",
@alternate && "even:bg-gray-50 odd:bg-white"
]}
>
<td
:for={{col, i} <- Enum.with_index(@col)}
phx-click={@row_click && row_click(row, @row_click)}
phx-value-id={@row_id && @row_id.(row)}
class={["relative p-0", @row_click && "hover:cursor-pointer"]}
>
<div class="block py-1 pr-6">
<span class={[
"absolute -inset-y-px right-0 -left-4 sm:rounded-l-xl",
@row_click && "group-hover:bg-blue-100"
]} />
<span class={[
"absolute -inset-y-px left-0 -right-4 sm:rounded-r-xl",
@row_click && "group-hover:bg-blue-100"
]} />
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
{render_slot(col, @row_item.(row))}
</span>
</div>
</td>
<td
:if={!SigmaKit.Util.blank?(@action)}
class={[
"relative w-14 p-0",
@row_click && "group-hover:bg-blue-100"
]}
>
<button
type="button"
class="z-50 flex relative text-sm text-zinc-800 py-1 px-4 rounded-lg"
data-dropdown-toggle={"action-dropdown-#{@id}-#{@row_id && @row_id.(row)}"}
data-dropdown-placement="left"
>
...
</button>
<span class={[
"absolute -inset-y-px left-0 -right-4 sm:rounded-r-xl",
@row_click && "group-hover:bg-blue-100"
]} />
<!-- Dropdown menu -->
<div
class="z-50 hidden my-4 text-base list-none divide-y rounded-lg shadow bg-gray-50 divide-gray-200 overflow-hidden"
id={"action-dropdown-#{@id}-#{@row_id && @row_id.(row)}"}
>
<ul class="divide-y divide-gray-200">
<li :for={action <- @action}>
<.link
phx-click={action.event}
phx-value-id={row.id}
class="px-4 block text-sm hover:bg-gray-200 text-zinc-800 text-nowrap font-medium flex items-center"
>
<div class="me-2 border-r py-2">
<.icon :if={action[:icon]} name={action.icon} class="w-4 h-4 me-2" />
</div>
<div class="py-2">
{action.label}
</div>
</.link>
</li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
<div
:if={@paginate && @total_pages > 1}
class="px-2 pt-6 mt-4 border-t border-gray-700"
id={"#{@id}-pager"}
>
<.pagination
id={@id}
page_number={@page_number}
total_pages={@total_pages}
page_size={@page_size}
total_entries={@total_entries}
/>
</div>
<div :if={Enum.empty?(@rows)} class="p-8 text-center border-2 border-dashed bg-zinc-50">
<div class="mb-1">
<.icon name="hero-information-circle" class="h-12 w-12 text-zinc-400" />
</div>
<h4 class="font-medium text-md">No Data Available</h4>
</div>
</div>
"""
end
defp row_click(row, v) when is_function(v), do: v.(row)
defp row_click(_row, v), do: v
attr :id, :any, default: nil
attr :page_number, :integer, default: 1
attr :total_pages, :integer, required: true
attr :page_size, :integer, required: true
attr :total_entries, :integer, required: true
def pagination(assigns) do
~H"""
<div class="flex justify-between items-center w-full">
<div class="text-xs text-zinc-300 hidden md:block">
Showing {@page_number * @page_size} of {@total_entries}
</div>
<div class="flex divide-x divide-gray-700 border border-gray-700 rounded">
<button
class="py-1 px-2 hover:bg-gray-700 disabled:text-zinc-500 disabled:hover:bg-transparent"
phx-click="paginate"
disabled={@page_number == 1}
phx-value-page={@page_number - 1}
phx-value-id={@id}
>
<.icon name="hero-chevron-left" class="w-4 h-4" />
</button>
<button
:for={page <- page_button_range(@page_number, @total_pages)}
phx-click="paginate"
phx-value-page={page}
disabled={page > @total_pages}
phx-value-id={@id}
class={[
"py-1 px-2 w-[2.5em] text-center font-black",
page == @page_number && "bg-purple-700/50 text-white",
page != @page_number && "text-gray-300 hover:bg-gray-700",
"disabled:hover:bg-transparent disabled:text-gray-800"
]}
>
{page}
</button>
<button
class="py-1 px-2 hover:bg-gray-700 disabled:text-zinc-500 disabled:hover:bg-transparent"
phx-click="paginate"
disabled={@page_number == @total_pages}
phx-value-page={@page_number + 1}
phx-value-id={@id}
>
<.icon name="hero-chevrons-right" class="w-4 h-4" />
</button>
</div>
</div>
"""
end
defp page_button_range(n, _max) when n <= 3, do: [1, 2, 3, 4, 5]
defp page_button_range(n, max) when n >= max - 2, do: [max - 4, max - 3, max - 2, max - 1, max]
defp page_button_range(n, _max) when n >= 4, do: [n - 2, n - 1, n, n + 1, n + 2]
end