Packages
moon
2.76.5
2.90.5
retired
2.90.4
2.90.3
2.90.2
2.90.1
2.90.0
2.89.4
2.89.1
2.89.0
2.88.0
2.87.11
2.87.10
2.87.9
2.87.8
2.87.7
2.87.6
2.87.5
2.87.4
2.87.3
2.87.2
2.87.1
2.87.0
2.86.1
2.86.0
2.85.1
2.85.0
2.84.0
2.83.0
2.82.0
2.81.4
2.81.3
2.81.2
2.81.1
2.81.0
2.80.2
2.80.1
2.80.0
2.79.13
2.79.12
2.79.11
2.79.10
2.79.9
2.79.8
2.79.7
2.79.6
2.79.5
2.79.4
2.79.3
2.79.2
2.79.1
2.79.0
2.78.4
2.78.3
2.78.2
2.78.1
2.78.0
2.77.0
2.76.5
2.76.4
2.76.3
2.76.2
2.76.1
2.76.0
2.75.0
2.74.1
2.74.0
2.73.8
2.73.7
2.73.6
2.73.5
2.73.4
2.73.3
2.73.2
2.73.1
2.73.0
2.72.5
2.72.4
2.72.3
2.72.2
2.72.1
2.72.0
2.71.1
2.71.0
2.70.0
2.69.2
2.69.1
2.69.0
2.68.11
2.68.10
Components-based design system written in elixir
Current section
Files
Jump to
Current section
Files
lib/moon/light/table.exs
defmodule Moon.Light.Table do
@moduledoc false
use Moon.Light.Component
alias Moon.Icon
import Moon.Helpers.MakeList, only: [add_index_as: 1]
attr(:selected, :any, default: [], doc: "List of selected ids, or just id, or [], or nil")
attr(:sort, :any,
default: [],
doc:
"Sorting of the table, format [field:, \"ASC\"|\"DESC\"]. If given, component will sort given items before displaying"
)
attr(:items, :any,
required: true,
doc:
"The list of items to be rendered. If item does not have id - than index is used instead. Able to work with streams"
)
attr(:row_click, :any, doc: "Event that firset on row click")
attr(:row_click_cb, :any,
doc: "Callback for generating on_click per row. row and row_id will bi given as parameters"
)
attr(:sorting_click, :any, doc: "Sorting stuff")
attr(:row_gap, :any,
default: "border-spacing-y-1",
doc: "Can be used to add any class to the table"
)
attr(:row_size, :string,
values: ~w(xs sm md lg xl 2xl),
default: "md",
doc: "Size of the row. not a css class!"
)
attr(:is_cell_border, :boolean, default: false, doc: "If cell has border")
attr(:is_headless, :boolean, default: false, doc: "If table has column headers or not")
attr(:is_zebra_style, :boolean,
default: false,
doc: "If table is styled to present its rows in automatically alternating colours"
)
attr(:row_bg, :any,
default: "bg-goku",
doc: "Can be used as an additional class for all the rows"
)
attr(:selected_bg, :any,
default: "bg-beerus",
doc: "Can be used as an additional class for selected rows"
)
attr(:header_row_class, :any, doc: "Can be used as an additional class for header row")
attr(:even_row_bg, :any,
default: "bg-transparent",
doc: "Can be used as an additional class for even rows in zebra-style table"
)
attr(:hover_bg, :any,
doc: "Can be used as an additional class for all rows. please use hover:... tailwind's format"
)
slot(:cols, generator_prop: :items, doc: "The list of columns defining the Grid")
attr(:id, :string, doc: "Just an id for a table tag")
attr(:testid, :string, doc: "Data-testid attribute for a table tag")
attr(:class, :any, doc: "Additional classes for a table tag")
attr(:body_attrs, :map, default: %{}, doc: "Additional attributes for tbody tag")
def table(assigns) do
~H"""
<table
class={[
merge([
[
"text-sm border-spacing-x-0 border-beerus min-w-full bg-gohan rounded-moon-s-sm",
@row_gap,
"border-spacing-y-0": @is_zebra_style
],
@class
]),
"border-separate"
]}
id={@id}
data-testid={@testid}
>
<thead :if={!@is_headless}>
<tr class={@header_row_class}>
<%= for {col, col_index} <- Enum.with_index(@cols) do %>
<th
class={
merge([
"text-left font-medium whitespace-nowrap",
text_size_classes(@row_size),
[
{:"#{inter_cell_border()}", @is_cell_border && col_index < Enum.count(@cols) - 1},
"cursor-pointer": col.sortable && @sorting_click
],
col.width
])
}
phx-on-click={(col.sortable && col.name && @sorting_click) || nil}
values={["sort-key": col.name, "sort-dir": toggle_sort_dir(@sort[:"#{col.name}"])]}
data-testid={"sort-column-#{col.name}"}
>
<%= col.label %>
<.icon
:if={col.name && col.sortable}
class={[
"text-[1.5em] transition-transform transition-200",
"rotate-180": @sort[:"#{col.name}"] != "ASC",
"opacity-0": !@sort[:"#{col.name}"]
]}
name="arrows_up"
/>
</th>
<% end %>
</tr>
</thead>
<tbody phx-attrs={@body_attrs}>
<%= for {row_index, item} <- stream_data(assigns) do %>
<tr
class={
merge([
(is_selected_item(item, @selected) && @selected_bg) || @row_bg,
@hover_bg,
{:"#{@even_row_bg}",
@is_zebra_style && !is_selected_item(item, @selected) && rem(row_index, 2) == 1},
"cursor-pointer": @row_click
])
}
phx-on-click={(@row_click_cb && @row_click_cb.(item, row_index)) || @row_click}
values={[selected: "#{item.id}", domid: dom_id(row_index, @id)]}
data-testid={"row-#{row_index}"}
id={dom_id(row_index, @id)}
>
<%= for {col, col_index} <- Enum.with_index(@cols) do %>
<td
class={
merge([
"first:rounded-l-moon-s-sm last:rounded-r-moon-s-sm",
col.width,
text_size_classes(@row_size),
[
{:"#{inter_cell_border()}",
@is_cell_border && col_index < Enum.count(@cols) - 1}
],
col.class
])
}
data-testid={"row-#{row_index}-col-#{col_index}"}
>
<%= render_slot(col) %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
"""
end
def sort_items(items, sort) do
import Enum, only: [reverse: 1, reduce: 3, sort_by: 3]
reverse(sort)
|> reduce(items, fn {field, sort_dir}, list ->
sort_by(
list,
& &1[field],
&if sort_dir == "ASC" do
&1 < &2
else
&1 > &2
end
)
end)
end
defp toggle_sort_dir(sort_dir) do
if "#{sort_dir}" == "ASC" do
"DESC"
else
"ASC"
end
end
defp is_selected_item(item, selected) do
item[:is_selected] || is_selected(item[:id], selected)
end
defp is_selected(id, selected) when is_list(selected) do
"#{id}" in selected
end
defp is_selected(id, selected) do
"#{id}" == "#{selected}"
end
defp inter_cell_border() do
"after:content-[\"\"] after:absolute after:w-px after:bg-beerus " <>
"after:h-3/5 after:bottom-[20%] after:right-0 after:translate-x-[-50%] relative"
end
defp text_size_classes(row_size) do
case row_size do
"xs" -> "text-moon-12 py-1 px-2"
"sm" -> "text-moon-14 py-1 px-3"
"md" -> "text-moon-14 py-2 px-3"
"lg" -> "text-moon-14 py-3 px-3"
"xl" -> "text-moon-14 py-4 px-3"
"2xl" -> "text-moon-14 py-5 px-3"
end
end
defp stream_data(%{items: stream = %Phoenix.LiveView.LiveStream{}}) do
stream
end
defp stream_data(%{items: items, sort: sort}) when is_list(items) do
items |> add_index_as() |> sort_items(sort) |> Enum.with_index(&{&2, &1})
end
defp dom_id(id, _) when is_binary(id) do
id
end
defp dom_id(id, id2) do
"#{id2}-row-#{id}"
end
end