Current section

41 Versions

Jump to

Compare versions

5 files changed
+267 additions
-265 deletions
  @@ -1,56 +1,54 @@
1 1 {<<"links">>,
2 2 [{<<"GitHub">>,<<"https://github.com/phcurado/daisy_ui_components">>}]}.
3 3 {<<"name">>,<<"daisy_ui_components">>}.
4 - {<<"version">>,<<"0.8.6">>}.
4 + {<<"version">>,<<"0.8.7-rc.0">>}.
5 5 {<<"description">>,<<"DaisyUI component library for LiveView">>}.
6 6 {<<"elixir">>,<<"~> 1.14">>}.
7 7 {<<"app">>,<<"daisy_ui_components">>}.
8 8 {<<"licenses">>,[<<"Apache-2.0">>]}.
9 9 {<<"files">>,
10 - [<<"lib">>,<<"lib/daisy_ui_components">>,
11 - <<"lib/daisy_ui_components/utils.ex">>,
12 - <<"lib/daisy_ui_components/drawer.ex">>,
13 - <<"lib/daisy_ui_components/collapse.ex">>,
10 + [<<"lib">>,<<"lib/daisy_ui_components.ex">>,<<"lib/daisy_ui_components">>,
11 + <<"lib/daisy_ui_components/checkbox.ex">>,
12 + <<"lib/daisy_ui_components/menu.ex">>,<<"lib/daisy_ui_components/hero.ex">>,
14 13 <<"lib/daisy_ui_components/textarea.ex">>,
15 - <<"lib/daisy_ui_components/avatar.ex">>,
16 - <<"lib/daisy_ui_components/pagination.ex">>,
17 - <<"lib/daisy_ui_components/list.ex">>,
18 - <<"lib/daisy_ui_components/tooltip.ex">>,
19 - <<"lib/daisy_ui_components/loading.ex">>,
20 - <<"lib/daisy_ui_components/card.ex">>,<<"lib/daisy_ui_components/swap.ex">>,
14 + <<"lib/daisy_ui_components/fieldset.ex">>,
21 15 <<"lib/daisy_ui_components/modal.ex">>,
22 - <<"lib/daisy_ui_components/hero.ex">>,
23 - <<"lib/daisy_ui_components/navbar.ex">>,
24 - <<"lib/daisy_ui_components/progress.ex">>,
25 - <<"lib/daisy_ui_components/badge.ex">>,
26 - <<"lib/daisy_ui_components/tabs.ex">>,
27 - <<"lib/daisy_ui_components/footer.ex">>,
28 - <<"lib/daisy_ui_components/table.ex">>,
29 - <<"lib/daisy_ui_components/dropdown.ex">>,
30 - <<"lib/daisy_ui_components/js_helpers.ex">>,
16 + <<"lib/daisy_ui_components/tooltip.ex">>,
31 17 <<"lib/daisy_ui_components/range.ex">>,
32 18 <<"lib/daisy_ui_components/button.ex">>,
33 - <<"lib/daisy_ui_components/radio.ex">>,
34 - <<"lib/daisy_ui_components/checkbox.ex">>,
35 - <<"lib/daisy_ui_components/select.ex">>,
36 - <<"lib/daisy_ui_components/accordion.ex">>,
37 - <<"lib/daisy_ui_components/alert.ex">>,
38 - <<"lib/daisy_ui_components/label.ex">>,
39 - <<"lib/daisy_ui_components/form.ex">>,
40 - <<"lib/daisy_ui_components/toggle.ex">>,
41 - <<"lib/daisy_ui_components/breadcrumbs.ex">>,
42 - <<"lib/daisy_ui_components/steps.ex">>,
43 - <<"lib/daisy_ui_components/icon.ex">>,
44 - <<"lib/daisy_ui_components/fieldset.ex">>,
45 - <<"lib/daisy_ui_components/sidebar.ex">>,
19 + <<"lib/daisy_ui_components/progress.ex">>,
20 + <<"lib/daisy_ui_components/list.ex">>,
46 21 <<"lib/daisy_ui_components/text_input.ex">>,
47 - <<"lib/daisy_ui_components/indicator.ex">>,
48 - <<"lib/daisy_ui_components/menu.ex">>,<<"lib/daisy_ui_components/join.ex">>,
49 - <<"lib/daisy_ui_components/header.ex">>,
22 + <<"lib/daisy_ui_components/avatar.ex">>,
23 + <<"lib/daisy_ui_components/alert.ex">>,
24 + <<"lib/daisy_ui_components/breadcrumbs.ex">>,
25 + <<"lib/daisy_ui_components/drawer.ex">>,
26 + <<"lib/daisy_ui_components/tabs.ex">>,
27 + <<"lib/daisy_ui_components/js_helpers.ex">>,
28 + <<"lib/daisy_ui_components/select.ex">>,
29 + <<"lib/daisy_ui_components/toggle.ex">>,
30 + <<"lib/daisy_ui_components/join.ex">>,
31 + <<"lib/daisy_ui_components/steps.ex">>,
50 32 <<"lib/daisy_ui_components/input.ex">>,
51 - <<"lib/daisy_ui_components/stat.ex">>,<<"lib/daisy_ui_components/back.ex">>,
52 - <<"lib/daisy_ui_components.ex">>,<<".formatter.exs">>,<<"mix.exs">>,
53 - <<"README.md">>,<<"LICENSE">>]}.
33 + <<"lib/daisy_ui_components/loading.ex">>,
34 + <<"lib/daisy_ui_components/form.ex">>,
35 + <<"lib/daisy_ui_components/badge.ex">>,
36 + <<"lib/daisy_ui_components/card.ex">>,<<"lib/daisy_ui_components/swap.ex">>,
37 + <<"lib/daisy_ui_components/back.ex">>,
38 + <<"lib/daisy_ui_components/utils.ex">>,
39 + <<"lib/daisy_ui_components/table.ex">>,
40 + <<"lib/daisy_ui_components/footer.ex">>,
41 + <<"lib/daisy_ui_components/pagination.ex">>,
42 + <<"lib/daisy_ui_components/radio.ex">>,
43 + <<"lib/daisy_ui_components/label.ex">>,
44 + <<"lib/daisy_ui_components/accordion.ex">>,
45 + <<"lib/daisy_ui_components/stat.ex">>,<<"lib/daisy_ui_components/icon.ex">>,
46 + <<"lib/daisy_ui_components/indicator.ex">>,
47 + <<"lib/daisy_ui_components/navbar.ex">>,
48 + <<"lib/daisy_ui_components/dropdown.ex">>,
49 + <<"lib/daisy_ui_components/header.ex">>,
50 + <<"lib/daisy_ui_components/collapse.ex">>,<<".formatter.exs">>,
51 + <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}.
54 52 {<<"requirements">>,
55 53 [[{<<"name">>,<<"phoenix_live_view">>},
56 54 {<<"app">>,<<"phoenix_live_view">>},
  @@ -8,6 +8,7 @@ defmodule DaisyUIComponents.Form do
8 8 import DaisyUIComponents.Fieldset
9 9 import DaisyUIComponents.Icon
10 10 import DaisyUIComponents.Input
11 + import DaisyUIComponents.Label
11 12
12 13 @doc """
13 14 Renders a simple form.
  @@ -65,7 +66,8 @@ defmodule DaisyUIComponents.Form do
65 66
66 67 attr :type, :string,
67 68 default: "text",
68 - values: ~w(checkbox color date datetime-local email file hidden month number password
69 + values:
70 + ~w(checkbox color date datetime-local email file hidden month number password
69 71 range radio search select tel text textarea time url week checkbox_group radio_group)
70 72
71 73 attr :field, Phoenix.HTML.FormField,
  @@ -133,9 +135,10 @@ defmodule DaisyUIComponents.Form do
133 135
134 136 ~H"""
135 137 <.fieldset class="mt-2">
138 + <.fieldset_label :if={@label}>{@label}</.fieldset_label>
136 139 <input type="hidden" name={@name} value="" disabled={@rest[:disabled]} />
137 140 <div :for={{{label, value}, index} <- Enum.with_index(@options)}>
138 - <.fieldset_label for={"#{@id}-#{index}"}>
141 + <.label for={"#{@id}-#{index}"}>
139 142 <.input
140 143 id={"#{@id}-#{index}"}
141 144 type="checkbox"
  @@ -147,7 +150,7 @@ defmodule DaisyUIComponents.Form do
147 150 />
148 151
149 152 {label}
150 - </.fieldset_label>
153 + </.label>
151 154 </div>
152 155 <.error :for={msg <- @errors}>{msg}</.error>
153 156 </.fieldset>
  @@ -184,29 +187,23 @@ defmodule DaisyUIComponents.Form do
184 187 end
185 188
186 189 def form_input(%{type: "radio_group"} = assigns) do
187 - assigns =
188 - assigns
189 - |> assign(:multiple, true)
190 - |> assign(:name, assigns.name <> "[]")
191 - |> assign(:value, assigns.value || [])
192 -
193 190 ~H"""
194 191 <.fieldset class="mt-2">
195 - <input type="hidden" name={@name} value="" disabled={@rest[:disabled]} />
192 + <.fieldset_label :if={@label}>{@label}</.fieldset_label>
196 193 <div :for={{{label, value}, index} <- Enum.with_index(@options)}>
197 - <.fieldset_label for={"#{@id}-#{index}"}>
194 + <.label for={"#{@id}-#{index}"}>
198 195 <.input
199 196 id={"#{@id}-#{index}"}
200 197 type="radio"
201 198 name={@name}
202 199 value={value}
203 - checked={to_string(value) == @value}
200 + checked={to_string(value) == to_string(@value)}
204 201 class={@class}
205 202 {@rest}
206 203 />
207 204
208 205 {label}
209 - </.fieldset_label>
206 + </.label>
210 207 </div>
211 208 <.error :for={msg <- @errors}>{msg}</.error>
212 209 </.fieldset>
  @@ -1,163 +0,0 @@
1 - defmodule DaisyUIComponents.Sidebar do
2 - @moduledoc """
3 - Sidebar component
4 - """
5 -
6 - use DaisyUIComponents, :live_component
7 -
8 - import DaisyUIComponents.Button
9 - import DaisyUIComponents.Drawer
10 - import DaisyUIComponents.Icon
11 - import DaisyUIComponents.Menu
12 -
13 - attr :id, :string, required: true, doc: "identifier to toggle the sidebar"
14 - attr :class, :any, default: nil
15 - attr :open, :boolean, default: nil, doc: "Forces the sidebar to be open"
16 - attr :expanded, :boolean, default: false, doc: "Sidebar expand width"
17 - attr :rest, :global
18 -
19 - slot :content, required: true do
20 - attr :class, :any
21 - end
22 -
23 - slot :item do
24 - attr :path, :string
25 - attr :method, :any
26 - attr :active, :boolean
27 - attr :icon, :string
28 - end
29 -
30 - @doc """
31 - Sidebar is a LiveComponent, it handles expanded and open states
32 - """
33 - def sidebar(assigns) do
34 - ~H"""
35 - <.live_component
36 - module={__MODULE__}
37 - id={@id}
38 - class={@class}
39 - open={@open}
40 - expanded={@expanded}
41 - content={@content}
42 - item={@item}
43 - {@rest}
44 - />
45 - """
46 - end
47 -
48 - @impl true
49 - def render(assigns) do
50 - ~H"""
51 - <div>
52 - <.button
53 - phx-click={JS.toggle_class("lg:drawer-open", to: "##{@id}")}
54 - phx-target={@myself}
55 - shape="circle"
56 - size="xs"
57 - >
58 - <.icon :if={@expanded} name="hero-chevron-double-left" class="h-5 w-5" />
59 - <.icon :if={!@expanded} name="hero-chevron-double-right" class="h-5 w-5" />
60 - </.button>
61 - <.drawer id={@id} class={[@class]} selector_id={"#{@id}_selector"}>
62 - <:drawer_content :for={content <- @content} class={Map.get(content, :class)}>
63 - {render_slot(@content)}
64 - </:drawer_content>
65 - <:drawer_side class={["h-full absolute border-r", !@expanded && "sticky "]}>
66 - <div class="absolute top-0 right-0 z-10 will-change-unset transform-none">
67 - <.button
68 - phx-click={JS.push("toggle_expand")}
69 - phx-target={@myself}
70 - shape="circle"
71 - size="xs"
72 - >
73 - <.icon :if={@expanded} name="hero-chevron-double-left" class="h-5 w-5" />
74 - <.icon :if={!@expanded} name="hero-chevron-double-right" class="h-5 w-5" />
75 - </.button>
76 - </div>
77 - <.drawer_items id={"#{@id}_items"} expanded={@expanded}>
78 - <.sidebar_item
79 - :for={item <- @item}
80 - path={item.path}
81 - icon={item.icon}
82 - expanded={@expanded}
83 - active={Map.get(item, :active)}
84 - method={Map.get(item, :method)}
85 - >
86 - {render_slot(item)}
87 - </.sidebar_item>
88 - </.drawer_items>
89 - </:drawer_side>
90 - </.drawer>
91 - </div>
92 - """
93 - end
94 -
95 - ## Events
96 - @impl true
97 - def handle_event("toggle_expand", _params, socket) do
98 - socket = socket |> assign(:expanded, !socket.assigns.expanded)
99 -
100 - {:noreply, socket}
101 - end
102 -
103 - ## Internal Components
104 -
105 - attr :id, :any
106 - attr :expanded, :boolean, default: false
107 - slot :inner_block, required: true
108 -
109 - defp drawer_items(assigns) do
110 - ~H"""
111 - <.menu
112 - id={@id}
113 - class={[
114 - "bg-base-100 text-base-content min-h-full",
115 - @expanded && "p-4 w-80",
116 - !@expanded && "w-20"
117 - ]}
118 - >
119 - {render_slot(@inner_block)}
120 - </.menu>
121 - """
122 - end
123 -
124 - attr :path, :string, default: nil
125 - attr :method, :any, default: nil
126 - attr :active, :boolean, default: false
127 - attr :expanded, :boolean, default: false
128 - attr :icon, :string
129 - slot :inner_block, required: true
130 -
131 - def sidebar_item(assigns) do
132 - ~H"""
133 - <li>
134 - <.link_method
135 - class={!@expanded && "justify-center"}
136 - active={@active}
137 - path={@path}
138 - method={@method}
139 - expanded={@expanded}
140 - >
141 - <.icon :if={@icon} name={@icon} class="w-5 h-5 text-center" />
142 - <span :if={@expanded}>{render_slot(@inner_block)}</span>
143 - </.link_method>
144 - </li>
145 - """
146 - end
147 -
148 - defp link_method(%{method: nil} = assigns) do
149 - ~H"""
150 - <.link class={[@active && "active", @class]} navigate={@path}>
151 - {render_slot(@inner_block)}
152 - </.link>
153 - """
154 - end
155 -
156 - defp link_method(assigns) do
157 - ~H"""
158 - <.link class={[@active && "active", @class]} href={@path} method={@method}>
159 - {render_slot(@inner_block)}
160 - </.link>
161 - """
162 - end
163 - end
  @@ -7,6 +7,8 @@ defmodule DaisyUIComponents.Table do
7 7
8 8 use DaisyUIComponents, :component
9 9
10 + import DaisyUIComponents.Icon
11 +
10 12 @doc ~S"""
11 13 Renders a table with generic styling.
12 14
  @@ -19,23 +21,57 @@ defmodule DaisyUIComponents.Table do
19 21
20 22 or declaring the table components
21 23
22 - <.table id="users">
23 - <.thead>
24 - <.tr>
25 - <.th>id</.th>
26 - <.th>username</.th>
27 - </.tr>
28 - </.thead>
29 - <.tbody>
30 - <.tr :for={user <- @users}>
31 - <.td>{user.id}</.td>
32 - <.td>{user.username}</.td>
33 - </.tr>
34 - </.tbody>
35 - </.table>
24 + <.table id="users">
25 + <.thead>
26 + <.tr>
27 + <.th>id</.th>
28 + <.th>username</.th>
29 + </.tr>
30 + </.thead>
31 + <.tbody>
32 + <.tr :for={user <- @users}>
33 + <.td>{user.id}</.td>
34 + <.td>{user.username}</.td>
35 + </.tr>
36 + </.tbody>
37 + </.table>
38 +
39 + sorting can be achieved by defining the `:sort_key` attribute in the `:col` slot.
40 + The `:sorted_columns` attribute should be a list of tuples with the column key and direction, e.g. `[{id, :asc}, {name, :desc}]`.
41 +
42 + <.table id="users" rows={@users} sorted_columns={@sorted_columns}>
43 + <:col :let={user} sort_key={:id} label="Id">
44 + {user.id}
45 + </:col>
46 + <:col :let={user} label="Username">
47 + {user.username}
48 + </:col>
49 + <:col :let={user} label="Email">
50 + {user.email}
51 + </:col>
52 + </.table>
53 +
54 + # When clicked on the header, the `:sort_key` will trigger an event with the `:event` attribute.
55 + def handle_event("sort", %{"sort_key" => sort_key, "sort_direction" => sort_direction}, socket) do
56 + sorted_columns = update_sort(socket.assigns.sorted_columns, sort_key, sort_direction)
57 + # Sort the users based on your sorting logic
58 + users = Users.order_by(sorted_columns)
59 + {:noreply, assign(socket, users: users, sorted_columns: sorted_columns)}
60 + end
36 61 """
37 62 attr :id, :string, default: nil
38 63 attr :class, :any, default: nil
64 +
65 + attr :container_element, :boolean,
66 + default: true,
67 + doc: "whether to wrap the table in a div with overflow-x-auto class"
68 +
69 + attr :sorted_columns, :list,
70 + default: [],
71 + doc: """
72 + list of columns sorted by, each column is a tuple with the column key and direction, e.g. [{:id, :asc}, {:name, :desc}]
73 + """
74 +
39 75 attr :rows, :list
40 76 attr :row_id, :any, default: nil, doc: "the function for generating the row id"
41 77 attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
  @@ -46,10 +82,19 @@ defmodule DaisyUIComponents.Table do
46 82
47 83 slot :col do
48 84 attr :class, :any
49 - # attr :collapse
85 + attr :sort_key, :any, doc: "the key for sorting the column when phx-click is triggered"
86 + attr :collapse_breakpoint, :string, doc: "the breakpoint for collapsing the column"
50 87 attr :label, :string
51 88 end
52 89
90 + attr :target, :string,
91 + default: nil,
92 + doc: "the target for phx-click on header columns for sorting"
93 +
94 + attr :event, :string,
95 + default: "sort",
96 + doc: "the event name for phx-click on header columns for sorting"
97 +
53 98 slot :action, doc: "the slot for showing user actions in the last table column"
54 99
55 100 attr :zebra, :boolean, default: false
  @@ -76,34 +121,13 @@ defmodule DaisyUIComponents.Table do
76 121 end
77 122
78 123 ~H"""
79 - <div class="overflow-x-auto">
80 - <table class={@class} {@rest}>
81 - <.thead>
82 - <.tr>
83 - <.th :for={col <- @col}>{col[:label]}</.th>
84 - <.th :if={@action != []}>
85 - <span class="sr-only"><span class="sr-only">{translate("Actions")}</span></span>
86 - </.th>
87 - </.tr>
88 - </.thead>
89 - <.tbody id={@id} phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}>
90 - <.tr :for={row <- @rows} id={@row_id && @row_id.(row)}>
91 - <.td
92 - :for={col <- @col}
93 - phx-click={@row_click && @row_click.(row)}
94 - class={[@row_click && "hover:cursor-pointer", @col[:class]]}
95 - >
96 - {render_slot(col, @row_item.(row))}
97 - </.td>
98 - <.td :if={@action != []}>
99 - <span :for={action <- @action}>
100 - {render_slot(action, @row_item.(row))}
101 - </span>
102 - </.td>
103 - </.tr>
104 - </.tbody>
105 - </table>
106 - </div>
124 + <%= if @container_element do %>
125 + <div class="overflow-x-auto">
126 + <.table_rows {assigns} />
127 + </div>
128 + <% else %>
129 + <.table_rows {assigns} />
130 + <% end %>
107 131 """
108 132 end
109 133
  @@ -121,11 +145,59 @@ defmodule DaisyUIComponents.Table do
121 145 )
122 146
123 147 ~H"""
124 - <div class="overflow-x-auto">
148 + <%= if @container_element do %>
149 + <div class="overflow-x-auto">
150 + <table class={@class} {@rest}>
151 + {render_slot(@inner_block)}
152 + </table>
153 + </div>
154 + <% else %>
125 155 <table class={@class} {@rest}>
126 156 {render_slot(@inner_block)}
127 157 </table>
128 - </div>
158 + <% end %>
159 + """
160 + end
161 +
162 + defp table_rows(assigns) do
163 + ~H"""
164 + <table class={@class} {@rest}>
165 + <.thead>
166 + <.tr>
167 + <.th
168 + :for={col <- @col}
169 + class={col[:class]}
170 + sort_key={col[:sort_key]}
171 + sort_direction={map_sort_direction(@sorted_columns, col[:sort_key])}
172 + target={@target}
173 + event={@event}
174 + collapse_breakpoint={col[:collapse_breakpoint]}
175 + >
176 + {col[:label]}
177 + </.th>
178 + <.th :if={@action != []}>
179 + <span class="sr-only"><span class="sr-only">{translate("Actions")}</span></span>
180 + </.th>
181 + </.tr>
182 + </.thead>
183 + <.tbody id={@id} phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}>
184 + <.tr :for={row <- @rows} id={@row_id && @row_id.(row)}>
185 + <.td
186 + :for={col <- @col}
187 + phx-click={@row_click && @row_click.(row)}
188 + class={[@row_click && "hover:cursor-pointer", @col[:class]]}
189 + collapse_breakpoint={col[:collapse_breakpoint]}
190 + >
191 + {render_slot(col, @row_item.(row))}
192 + </.td>
193 + <.td :if={@action != []}>
194 + <span :for={action <- @action}>
195 + {render_slot(action, @row_item.(row))}
196 + </span>
197 + </.td>
198 + </.tr>
199 + </.tbody>
200 + </table>
129 201 """
130 202 end
131 203
  @@ -154,45 +226,108 @@ defmodule DaisyUIComponents.Table do
154 226 """
155 227 end
156 228
229 + attr :class, :any, default: nil
230 + attr :collapse_breakpoint, :string, values: sizes()
157 231 attr :rest, :global
158 232 slot :inner_block
159 233
160 234 def td(assigns) do
235 + assigns =
236 + assign(
237 + assigns,
238 + :class,
239 + classes([
240 + collapse_breakpoint(assigns[:collapse_breakpoint]),
241 + assigns.class
242 + ])
243 + )
244 +
161 245 ~H"""
162 - <td {@rest}>
246 + <td class={@class} {@rest}>
163 247 {render_slot(@inner_block)}
164 248 </td>
165 249 """
166 250 end
167 251
252 + attr :class, :any, default: nil
253 + attr :collapse_breakpoint, :string, values: sizes()
254 +
255 + attr :sort_key, :any,
256 + default: nil,
257 + doc: "the key for sorting the column when phx-click is triggered"
258 +
259 + attr :sort_direction, :string,
260 + default: "asc",
261 + values: ["asc", "desc", nil],
262 + doc: "the direction for sorting the column when phx-click is triggered"
263 +
264 + attr :target, :string,
265 + default: nil,
266 + doc: "the target for phx-click on header columns for sorting"
267 +
268 + attr :event, :string,
269 + default: "sort",
270 + doc: "the event name for phx-click on header columns for sorting"
271 +
168 272 attr :rest, :global
169 273 slot :inner_block
170 274
171 275 def th(assigns) do
276 + assigns =
277 + assign(
278 + assigns,
279 + :class,
280 + classes([
281 + collapse_breakpoint(assigns[:collapse_breakpoint]),
282 + maybe_add_class(sortable?(assigns[:sort_key]), "group/th cursor-pointer"),
283 + assigns.class
284 + ])
285 + )
286 +
172 287 ~H"""
173 - <th {@rest}>
288 + <th
289 + class={@class}
290 + phx-click={
291 + sortable?(@sort_key) &&
292 + JS.push(@event,
293 + value: %{
294 + sort_key: @sort_key,
295 + sort_direction: next_sort_direction(@sort_direction)
296 + }
297 + )
298 + }
299 + phx-target={@target}
300 + {@rest}
301 + >
302 + <.icon
303 + :if={sortable?(@sort_key)}
304 + name={sort_icon(@sort_direction)}
305 + class={classes(["w-3 h-3", !@sort_direction && "invisible group-hover/th:visible"])}
306 + />
174 307 {render_slot(@inner_block)}
175 308 </th>
176 309 """
177 310 end
178 311
312 + attr :class, :any, default: nil
179 313 attr :rest, :global
180 314 slot :inner_block
181 315
182 316 def thead(assigns) do
183 317 ~H"""
184 - <thead {@rest}>
318 + <thead class={@class} {@rest}>
185 319 {render_slot(@inner_block)}
186 320 </thead>
187 321 """
188 322 end
189 323
324 + attr :class, :any, default: nil
190 325 attr :rest, :global
191 326 slot :inner_block
192 327
193 328 def tbody(assigns) do
194 329 ~H"""
195 - <tbody {@rest}>
330 + <tbody class={@class} {@rest}>
196 331 {render_slot(@inner_block)}
197 332 </tbody>
198 333 """
  @@ -205,4 +340,39 @@ defmodule DaisyUIComponents.Table do
205 340 defp table_size("lg"), do: "table-lg"
206 341 defp table_size("xl"), do: "table-xl"
207 342 defp table_size(_size), do: nil
343 +
344 + # Collapse Breakpoint
345 + defp collapse_breakpoint("xs"), do: "hidden xs:table-cell"
346 + defp collapse_breakpoint("sm"), do: "hidden sm:table-cell"
347 + defp collapse_breakpoint("md"), do: "hidden md:table-cell"
348 + defp collapse_breakpoint("lg"), do: "hidden lg:table-cell"
349 + defp collapse_breakpoint("xl"), do: "hidden xl:table-cell"
350 + defp collapse_breakpoint(_breakpoint), do: nil
351 +
352 + defp sortable?(nil), do: false
353 + defp sortable?(_), do: true
354 +
355 + defp map_sort_direction(sorted_columns, sort_key) do
356 + case Enum.find(sorted_columns, fn {key, _} -> to_string(key) == to_string(sort_key) end) do
357 + nil -> nil
358 + {_sort_key, "asc"} -> "asc"
359 + {_sort_key, "desc"} -> "desc"
360 + _ -> nil
361 + end
362 + end
363 +
364 + defp next_sort_direction(nil), do: "asc"
365 + defp next_sort_direction("asc"), do: "desc"
366 + defp next_sort_direction("desc"), do: nil
367 +
368 + defp sort_icon("desc"), do: "hero-arrow-down"
369 + defp sort_icon(_), do: "hero-arrow-up"
370 +
371 + @spec update_sort(list(), atom(), binary()) :: list()
372 + def update_sort(sorted_columns, sort_key, sort_direction) do
373 + # Remove the existing sort key if it exists so it's replaced to the end of the list,
374 + # so it can be the last sorted column
375 + sorted_columns = Enum.reject(sorted_columns, fn {k, _} -> k == sort_key end)
376 + List.insert_at(sorted_columns, -1, {sort_key, sort_direction})
377 + end
208 378 end
  @@ -2,7 +2,7 @@ defmodule DaisyUIComponents.MixProject do
2 2 use Mix.Project
3 3
4 4 @source_url "https://github.com/phcurado/daisy_ui_components"
5 - @version "0.8.6"
5 + @version "0.8.7-rc.0"
6 6
7 7 def project do
8 8 [