Packages
petal_components
3.0.2
4.6.2
4.6.1
4.6.0
4.5.0
4.4.0
4.3.0
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.12
4.0.11
4.0.10
4.0.6
4.0.5
4.0.4
4.0.3
4.0.1
3.2.2
3.2.1
3.2.0
3.1.0
3.0.2
3.0.1
3.0.0
2.9.3
2.9.2
retired
2.9.1
retired
2.9.0
retired
2.8.4
2.8.3
2.8.2
2.8.1
2.8.0
2.7.4
2.7.3
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.2
2.5.1
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.1
2.2.0
2.1.2
2.1.1
2.1.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.9.3
1.9.2
1.9.1
1.9.0
1.8.0
1.7.1
1.7.0
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.14
1.2.13
1.2.12
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.19.10
0.19.9
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.15.0
0.14.1
0.14.0
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.0
Shadcn-style Phoenix LiveView components that AI assistants can actually use. Pair with the MCP server so AI coding tools can inspect the real component API.
Current section
Files
Jump to
Current section
Files
lib/petal_components/input.ex
defmodule PetalComponents.Input do
use Phoenix.Component
import PetalComponents.Icon
@moduledoc """
Renders pure inputs (no label or errors).
"""
attr :id, :any, default: nil
attr :name, :any
attr :label, :string
attr :value, :any
attr :type, :string,
default: "text",
values: ~w(checkbox color date datetime-local email file hidden month number password
range radio search select switch tel text textarea time url week)
attr :size, :string, default: "md", values: ~w(xs sm md lg xl), doc: "the size of the switch"
attr :viewable, :boolean,
default: false,
doc: "If true, adds a toggle to show/hide the password text"
attr :copyable, :boolean,
default: false,
doc: "If true, adds a copy button to the field and disables the input"
attr :clearable, :boolean,
default: false,
doc: "If true, adds a clear button to clear the field value"
attr :field, Phoenix.HTML.FormField,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
attr :checked, :boolean, doc: "the checked flag for checkbox inputs"
attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
attr :class, :any, default: nil, doc: "the class to add to the input"
attr :rest, :global,
include:
~w(autocomplete autocorrect autocapitalize disabled form max maxlength min minlength list
pattern placeholder readonly required size step value name multiple prompt selected default year month day hour minute second builder options layout cols rows wrap checked accept)
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns
|> assign(field: nil, id: assigns.id || field.id)
|> assign_new(:name, fn ->
if assigns.multiple, do: field.name <> "[]", else: field.name
end)
|> assign_new(:value, fn -> field.value end)
|> assign_new(:label, fn -> PhoenixHTMLHelpers.Form.humanize(field.field) end)
|> assign(class: [assigns.class, get_class_for_type(assigns.type)])
|> input()
end
def input(%{type: "select"} = assigns) do
~H"""
<select id={@id} name={@name} class={[@class, "pc-text-input"]} multiple={@multiple} {@rest}>
<option :if={@prompt} value="">{@prompt}</option>
{Phoenix.HTML.Form.options_for_select(@options, @value)}
</select>
"""
end
def input(%{type: "textarea"} = assigns) do
~H"""
<textarea id={@id} name={@name} class={[@class, "pc-text-input"]} {@rest}><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
"""
end
def input(%{type: "switch", value: value} = assigns) do
assigns =
assigns
|> assign_new(:checked, fn -> Phoenix.HTML.Form.normalize_value("checkbox", value) end)
~H"""
<label class={["pc-switch", "pc-switch--#{@size}"]}>
<input
type="checkbox"
id={@id}
name={@name}
value="true"
checked={@checked}
class={["sr-only peer", @class]}
{@rest}
/>
<span class={["pc-switch__fake-input", "pc-switch__fake-input--#{@size}"]}></span>
<span class={["pc-switch__fake-input-bg", "pc-switch__fake-input-bg--#{@size}"]}></span>
</label>
"""
end
def input(%{type: "password", viewable: true} = assigns) do
assigns = assign(assigns, class: [assigns.class, get_class_for_type(assigns.type)])
~H"""
<div class="pc-password-field-wrapper" x-data="{ show: false }">
<input
x-bind:type="show ? 'text' : 'password'"
name={@name}
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={[@class, "pc-password-field-input"]}
{@rest}
/>
<button type="button" class="pc-password-field-toggle-button" @click="show = !show">
<span x-show="!show" class="pc-password-field-toggle-icon-container">
<.icon name="hero-eye-solid" class="pc-password-field-toggle-icon" />
</span>
<span x-show="show" class="pc-password-field-toggle-icon-container" style="display: none;">
<.icon name="hero-eye-slash-solid" class="pc-password-field-toggle-icon" />
</span>
</button>
</div>
"""
end
def input(%{type: type, copyable: true} = assigns) when type in ["text", "url", "email"] do
assigns = assign(assigns, class: [assigns.class, get_class_for_type(assigns.type)])
~H"""
<div class="pc-copyable-field-wrapper" x-data="{ copied: false }">
<input
x-ref="copyInput"
type={@type || "text"}
name={@name}
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type || "text", @value)}
class={[@class, "pc-copyable-field-input"]}
readonly
{@rest}
/>
<button
type="button"
class="pc-copyable-field-button"
@click="
navigator.clipboard.writeText($refs.copyInput.value)
.then(() => { copied = true; setTimeout(() => copied = false, 2000); })
"
>
<span x-show="!copied" class="pc-copyable-field-icon-container">
<.icon name="hero-clipboard-document-solid" class="pc-copyable-field-icon" />
</span>
<span x-show="copied" class="pc-copyable-field-icon-container" style="display: none;">
<.icon name="hero-clipboard-document-check-solid" class="pc-copyable-field-icon" />
</span>
</button>
</div>
"""
end
def input(%{type: type, clearable: true} = assigns)
when type in ["text", "search", "url", "email", "tel"] do
assigns = assign(assigns, class: [assigns.class, get_class_for_type(assigns.type)])
~H"""
<div
class="pc-clearable-field-wrapper"
x-data="{ showClearButton: false }"
x-init="showClearButton = $refs.clearInput.value.length > 0"
>
<input
x-ref="clearInput"
type={@type || "text"}
name={@name}
id={@id}
value={@value}
class={[@class, "pc-clearable-field-input"]}
{@rest}
x-on:input="showClearButton = $event.target.value.length > 0"
/>
<button
type="button"
class="pc-clearable-field-button"
x-show="showClearButton"
x-on:click="
$refs.clearInput.value = '';
showClearButton = false;
$refs.clearInput.dispatchEvent(new Event('input'));
"
style="display: none;"
aria-label="Clear input"
>
<span class="pc-clearable-field-icon-container">
<.icon name="hero-x-mark-solid" class="pc-clearable-field-icon" />
</span>
</button>
</div>
"""
end
def input(%{type: type} = assigns)
when type in ["date", "datetime-local", "time", "month", "week"] do
assigns =
assign(assigns,
class: [assigns.class, "pc-date-input pc-date-picker-indicator"],
icon_name: get_icon_for_type(assigns.type)
)
~H"""
<div class="pc-date-input-wrapper">
<input
type={@type}
name={@name}
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={@class}
{@rest}
/>
<button
type="button"
class="pc-date-input-icon"
onclick="this.previousElementSibling.showPicker()"
>
<.icon name={@icon_name} class="w-5 h-5 text-gray-400" />
</button>
</div>
"""
end
def input(assigns) do
~H"""
<input
type={@type}
name={@name}
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={@class}
{@rest}
/>
"""
end
defp get_class_for_type("radio"), do: "pc-radio"
defp get_class_for_type("checkbox"), do: "pc-checkbox"
defp get_class_for_type("color"), do: "pc-color-input"
defp get_class_for_type("file"), do: "pc-file-input"
defp get_class_for_type("range"), do: "pc-range-input"
defp get_class_for_type(_), do: "pc-text-input"
defp get_icon_for_type("date"), do: "hero-calendar"
defp get_icon_for_type("datetime-local"), do: "hero-calendar"
defp get_icon_for_type("month"), do: "hero-calendar"
defp get_icon_for_type("week"), do: "hero-calendar"
defp get_icon_for_type("time"), do: "hero-clock"
end