Files
Rivertalk/lib/river_connect_web/components/ui/form.ex
T
2026-02-12 17:10:03 -06:00

188 lines
4.7 KiB
Elixir

defmodule RiverConnectWeb.Components.UI.Form do
@moduledoc """
Form-related components that help build accessible forms.
SaladUI doesn't define its own form component, but instead provides a set of
form-related components to enhance the native Phoenix LiveView form.
## Examples:
<.form
class="space-y-6"
for={@form}
id="project-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.form_item>
<.form_label>What is your project's name?</.form_label>
<.form_control>
<.input field={@form[:name]} type="text" phx-debounce="500" />
</.form_control>
<.form_description>
This is your public display name.
</.form_description>
<.form_message field={@form[:name]} />
</.form_item>
<div class="w-full flex flex-row-reverse">
<.button
class="btn btn-secondary btn-md"
icon="inbox_arrow_down"
phx-disable-with="Saving..."
>
Save project
</.button>
</div>
</.form>
"""
use RiverConnectWeb.Components.UI, :component
@doc """
Form item component that acts as a container for a form field.
## Examples:
<.form_item>
<.form_label>Email</.form_label>
<.form_control>
<.input field={@form[:email]} type="email" />
</.form_control>
<.form_message field={@form[:email]} />
</.form_item>
"""
attr :class, :string, default: nil
slot :inner_block, required: true
attr :rest, :global
def form_item(assigns) do
~H"""
<div class={classes(["space-y-2", @class])} {@rest}>
{render_slot(@inner_block)}
</div>
"""
end
@doc """
Form label component that renders a label for a form field.
Shows validation errors through text color when a field has errors.
## Examples:
<.form_label>Email</.form_label>
<.form_label field={@form[:email]}>Email</.form_label>
"""
attr :class, :string, default: nil
attr :field, Phoenix.HTML.FormField,
default: nil,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
attr :for, :string, default: nil
attr :error, :boolean, default: false, doc: "whether the field has an error"
slot :inner_block, required: true
attr :rest, :global
def form_label(assigns) do
assigns = assign(assigns, error: assigns[:error] || has_error?(assigns[:field]))
~H"""
<RiverConnectWeb.Components.UI.Label.label
for={(@field && @field.id) || @for}
class={
classes([
@error && "text-destructive",
@class
])
}
{@rest}
>
{render_slot(@inner_block)}
</RiverConnectWeb.Components.UI.Label.label>
"""
end
@doc """
Form control component that wraps input elements.
This is a simple pass-through component that renders its inner block.
## Examples:
<.form_control>
<.input field={@form[:email]} type="email" />
</.form_control>
"""
slot :inner_block, required: true
def form_control(assigns) do
~H"""
{render_slot(@inner_block)}
"""
end
@doc """
Form description component that provides additional context for a form field.
## Examples:
<.form_description>
We'll only use your email for account-related purposes.
</.form_description>
"""
attr :class, :string, default: nil
slot :inner_block, required: true
attr :rest, :global
def form_description(assigns) do
~H"""
<p class={classes(["text-muted-foreground text-sm", @class])} {@rest}>
{render_slot(@inner_block)}
</p>
"""
end
@doc """
Form message component that displays validation errors for a form field.
## Examples:
<.form_message field={@form[:email]} />
<.form_message>
Please enter a valid email address.
</.form_message>
"""
attr :field, Phoenix.HTML.FormField,
default: nil,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
attr :errors, :any,
default: [],
doc: "a list of error messages to display"
attr :class, :string, default: nil
slot :inner_block, required: false
attr :rest, :global
def form_message(assigns) do
assigns =
assigns
|> assign(error: has_error?(assigns[:field]) || not Enum.empty?(assigns[:errors]))
|> assign(:message, List.first(assigns[:errors] || field_errors(assigns[:field])))
~H"""
<p
:if={(msg = render_slot(@inner_block)) || not is_nil(@error)}
class={classes(["text-sm font-medium", @error && "text-destructive", @class])}
{@rest}
>
{msg || @message}
</p>
"""
end
end