refactor: use core components

This commit is contained in:
carla 2026-02-25 09:12:33 +01:00
parent f0be98316c
commit b7c93f19cb
26 changed files with 1080 additions and 954 deletions

View file

@ -60,15 +60,15 @@ defmodule MvWeb.CoreComponents do
id={@id}
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
role="alert"
class="z-50 toast toast-top toast-end"
class="z-50 toast toast-bottom toast-end"
{@rest}
>
<div class={[
"alert w-80 sm:w-96 max-w-80 sm:max-w-96 text-wrap",
@kind == :info && "alert-info",
@kind == :error && "alert-error",
@kind == :success && "bg-green-500 text-white",
@kind == :warning && "bg-blue-100 text-blue-800 border border-blue-300"
@kind == :success && "alert-success",
@kind == :warning && "alert-warning"
]}>
<.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" />
<.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" />
@ -90,33 +90,71 @@ defmodule MvWeb.CoreComponents do
@doc """
Renders a button with navigation support.
## Variants (Design Guidelines §5.2)
- primary (main CTA)
- secondary (supporting)
- neutral (cancel/back)
- ghost (low emphasis; table/toolbars)
- outline (alternative CTA)
- danger (destructive)
- link (inline; rare)
- icon (icon-only)
## Sizes
- sm, md (default), lg
## Examples
<.button>Send!</.button>
<.button phx-click="go" variant="primary">Send!</.button>
<.button navigate={~p"/"}>Home</.button>
<.button navigate={~p"/"} variant="secondary">Home</.button>
<.button variant="ghost" size="sm">Edit</.button>
<.button disabled={true}>Disabled</.button>
"""
attr :rest, :global, include: ~w(href navigate patch method data-testid)
attr :variant, :string, values: ~w(primary)
attr :variant, :string,
values: ~w(primary secondary neutral ghost outline danger link icon),
default: "primary"
attr :size, :string, values: ~w(sm md lg), default: "md"
attr :disabled, :boolean, default: false, doc: "Whether the button is disabled"
slot :inner_block, required: true
def button(assigns) do
rest = assigns.rest
variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"}
assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant]))
variant = assigns[:variant] || "primary"
size = assigns[:size] || "md"
variant_classes = %{
"primary" => "btn-primary",
"secondary" => "btn-secondary",
"neutral" => "btn-neutral",
"ghost" => "btn-ghost",
"outline" => "btn-outline",
"danger" => "btn-error",
"link" => "btn-link",
"icon" => "btn-ghost btn-square"
}
size_classes = %{
"sm" => "btn-sm",
"md" => "",
"lg" => "btn-lg"
}
base_class = Map.fetch!(variant_classes, variant)
size_class = size_classes[size]
btn_class = [base_class, size_class] |> Enum.reject(&(&1 == "")) |> Enum.join(" ")
assigns = assign(assigns, :btn_class, btn_class)
if rest[:href] || rest[:navigate] || rest[:patch] do
# For links, we can't use disabled attribute, so we use btn-disabled class
# DaisyUI's btn-disabled provides the same styling as :disabled on buttons
link_class =
if assigns[:disabled],
do: ["btn", assigns.class, "btn-disabled"],
else: ["btn", assigns.class]
do: ["btn", btn_class, "btn-disabled"],
else: ["btn", btn_class]
# Prevent interaction when disabled
# Remove navigation attributes to prevent "Open in new tab", "Copy link" etc.
link_attrs =
if assigns[:disabled] do
rest
@ -138,13 +176,49 @@ defmodule MvWeb.CoreComponents do
"""
else
~H"""
<button class={["btn", @class]} disabled={@disabled} {@rest}>
<button class={["btn", @btn_class]} disabled={@disabled} {@rest}>
{render_slot(@inner_block)}
</button>
"""
end
end
@doc """
Wraps content with a DaisyUI tooltip. Use for icon-only actions, truncated content,
or status badges that need explanation (Design Guidelines §8.2).
## Examples
<.tooltip content={gettext("Edit")}>
<.button variant="icon" size="sm"><.icon name="hero-pencil" /></.button>
</.tooltip>
<.tooltip content={@full_name} position="top">
<span class="truncate max-w-32">{@full_name}</span>
</.tooltip>
"""
attr :content, :string, required: true, doc: "Tooltip text (data-tip)"
attr :position, :string,
values: ~w(top bottom left right),
default: "bottom"
attr :wrap_class, :string, default: nil, doc: "Additional classes for the wrapper"
slot :inner_block, required: true
def tooltip(assigns) do
position_class = "tooltip tooltip-#{assigns.position}"
wrap_class = [position_class, assigns.wrap_class] |> Enum.reject(&is_nil/1) |> Enum.join(" ")
assigns = assign(assigns, :wrap_class, wrap_class)
~H"""
<div class={@wrap_class} data-tip={@content}>
{render_slot(@inner_block)}
</div>
"""
end
@doc """
Renders a dropdown menu.
@ -437,7 +511,7 @@ defmodule MvWeb.CoreComponents do
{@rest}
/>{@label}<span
:if={@is_required}
class="text-red-700 tooltip tooltip-right"
class="text-error tooltip tooltip-right"
data-tip={gettext("This field is required")}
>*</span>
</span>
@ -456,7 +530,7 @@ defmodule MvWeb.CoreComponents do
<span :if={@label} class="mb-1 label">
{@label}<span
:if={@rest[:required]}
class="text-red-700 tooltip tooltip-right"
class="text-error tooltip tooltip-right"
data-tip={gettext("This field cannot be empty")}
>*</span>
</span>
@ -485,7 +559,7 @@ defmodule MvWeb.CoreComponents do
<span :if={@label} class="mb-1 label">
{@label}<span
:if={@rest[:required]}
class="text-red-700 tooltip tooltip-right"
class="text-error tooltip tooltip-right"
data-tip={gettext("This field cannot be empty")}
>*</span>
</span>
@ -514,7 +588,7 @@ defmodule MvWeb.CoreComponents do
<span :if={@label} class="mb-1 label">
{@label}<span
:if={@rest[:required]}
class="text-red-700 tooltip tooltip-right"
class="text-error tooltip tooltip-right"
data-tip={gettext("This field cannot be empty")}
>*</span>
</span>
@ -585,6 +659,11 @@ defmodule MvWeb.CoreComponents do
@doc ~S"""
Renders a table with generic styling.
When `row_click` is set, clicking a row (or a data cell) triggers the handler.
The action column has no phx-click on its `<td>`, so action buttons do not trigger row navigation.
For interactive elements inside other columns (e.g. checkboxes, buttons), use
`Phoenix.LiveView.JS.stop_propagation()` in the element's phx-click so the row click is not fired.
## Examples
<.table id="users" rows={@users}>