Packages
petal_components
1.1.3
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/button.ex
defmodule PetalComponents.Button do
use Phoenix.Component
alias PetalComponents.Loading
alias PetalComponents.Link
alias PetalComponents.Icon
import PetalComponents.Helpers
require Logger
attr(:size, :string, default: "md", values: ["xs", "sm", "md", "lg", "xl"], doc: "button sizes")
attr(:variant, :string,
default: "solid",
values: ["solid", "outline", "inverted", "shadow"],
doc: "button variant"
)
attr(:color, :string,
default: "primary",
values: [
"primary",
"secondary",
"info",
"success",
"warning",
"danger",
"gray",
"pure_white",
"white"
],
doc: "button color"
)
attr(:to, :string, default: nil, doc: "link path")
attr(:loading, :boolean, default: false, doc: "indicates a loading state")
attr(:disabled, :boolean, default: false, doc: "indicates a disabled state")
attr(:icon, :atom, default: nil, doc: "name of a Heroicon at the front of the button")
attr(:with_icon, :boolean, default: false, doc: "adds some icon base classes")
attr(:link_type, :string,
default: "button",
values: ["a", "live_patch", "live_redirect", "button"]
)
attr(:class, :string, default: "", doc: "CSS class")
attr(:label, :string, default: nil, doc: "labels your button")
attr(:rest, :global,
include: ~w(method download hreflang ping referrerpolicy rel target type value name)
)
slot(:inner_block, required: false)
def button(assigns) do
assigns =
assigns
|> assign(:classes, button_classes(assigns))
~H"""
<Link.a to={@to} link_type={@link_type} class={@classes} disabled={@disabled} {@rest}>
<%= if @loading do %>
<Loading.spinner show={true} size_class={get_spinner_size_classes(@size)} />
<% else %>
<%= if @icon do %>
<Icon.icon name={@icon} mini class={get_spinner_size_classes(@size)} />
<% end %>
<% end %>
<%= render_slot(@inner_block) || @label %>
</Link.a>
"""
end
attr(:size, :string, default: "sm", values: ["xs", "sm", "md", "lg", "xl"])
attr(:color, :string,
default: "gray",
values: ["primary", "secondary", "info", "success", "warning", "danger", "gray"]
)
attr(:to, :string, default: nil, doc: "link path")
attr(:loading, :boolean, default: false, doc: "indicates a loading state")
attr(:disabled, :boolean, default: false, doc: "indicates a disabled state")
attr(:with_icon, :boolean, default: false, doc: "adds some icon base classes")
attr(:link_type, :string,
default: "button",
values: ["a", "live_patch", "live_redirect", "button"]
)
attr(:class, :string, default: "", doc: "CSS class")
attr(:tooltip, :string, default: nil, doc: "tooltip text")
attr(:rest, :global, include: ~w(method download hreflang ping referrerpolicy rel target type))
slot(:inner_block, required: false)
def icon_button(assigns) do
~H"""
<Link.a
to={@to}
link_type={@link_type}
class={
build_class([
"pc-icon-button",
get_disabled_classes(@disabled),
get_icon_button_background_color_classes(@color),
get_icon_button_color_classes(@color),
get_icon_button_size_classes(@size),
@class
])
}
disabled={@disabled}
{@rest}
>
<div class={@tooltip && "relative group/pc-icon-button flex flex-col items-center"}>
<%= if @loading do %>
<Loading.spinner show={true} size_class={get_icon_button_spinner_size_classes(@size)} />
<% else %>
<%= render_slot(@inner_block) %>
<div :if={@tooltip} role="tooltip" class="pc-icon-button__tooltip">
<span class="pc-icon-button__tooltip__text">
<%= @tooltip %>
</span>
<div class="pc-icon-button__tooltip__arrow"></div>
</div>
<% end %>
</div>
</Link.a>
"""
end
defp button_classes(opts) do
opts = %{
size: opts[:size] || "md",
variant: opts[:variant] || "solid",
color: opts[:color] || "primary",
loading: opts[:loading] || false,
disabled: opts[:disabled] || false,
with_icon: opts[:with_icon] || opts[:icon] || false,
user_added_classes: opts[:class] || ""
}
color_css = get_color_classes(opts)
size_css = "pc-button--#{opts.size}"
loading_css = if opts[:loading], do: "pc-button--loading", else: ""
icon_css = if opts[:with_icon], do: "pc-button--with-icon", else: ""
[
opts.user_added_classes,
color_css,
size_css,
loading_css,
get_disabled_classes(opts[:disabled]),
icon_css,
"pc-button"
]
|> build_class()
end
defp get_color_classes(%{color: "primary", variant: variant}) do
case variant do
"outline" ->
"pc-button--primary-outline"
"inverted" ->
"pc-button--primary-inverted"
"shadow" ->
"pc-button--primary-shadow"
_ ->
"pc-button--primary"
end
end
defp get_color_classes(%{color: "secondary", variant: variant}) do
case variant do
"outline" ->
"pc-button--secondary-outline"
"inverted" ->
"pc-button--secondary-inverted"
"shadow" ->
"pc-button--secondary-shadow"
_ ->
"pc-button--secondary"
end
end
defp get_color_classes(%{color: "white", variant: variant}) do
case variant do
"outline" ->
"pc-button--white-outline"
"inverted" ->
"pc-button--white-inverted"
"shadow" ->
"pc-button--white-shadow"
_ ->
"pc-button--white"
end
end
defp get_color_classes(%{color: "pure_white", variant: variant}) do
case variant do
_ ->
"pc-button--pure-white"
end
end
defp get_color_classes(%{color: "info", variant: variant}) do
case variant do
"outline" ->
"pc-button--info-outline"
"inverted" ->
"pc-button--info-inverted"
"shadow" ->
"pc-button--info-shadow"
_ ->
"pc-button--info"
end
end
defp get_color_classes(%{color: "success", variant: variant}) do
case variant do
"outline" ->
"pc-button--success-outline"
"inverted" ->
"pc-button--success-inverted"
"shadow" ->
"pc-button--success-shadow"
_ ->
"pc-button--success"
end
end
defp get_color_classes(%{color: "warning", variant: variant}) do
case variant do
"outline" ->
"pc-button--warning-outline"
"inverted" ->
"pc-button--warning-inverted"
"shadow" ->
"pc-button--warning-shadow"
_ ->
"pc-button--warning"
end
end
defp get_color_classes(%{color: "danger", variant: variant}) do
case variant do
"outline" ->
"pc-button--danger-outline"
"inverted" ->
"pc-button--danger-inverted"
"shadow" ->
"pc-button--danger-shadow"
_ ->
"pc-button--danger"
end
end
defp get_color_classes(%{color: "gray", variant: variant}) do
case variant do
"outline" ->
"pc-button--gray-outline"
"inverted" ->
"pc-button--gray-inverted"
"shadow" ->
"pc-button--gray-shadow"
_ ->
"pc-button--gray"
end
end
defp get_spinner_size_classes("xs"), do: "pc-button__spinner-icon--xs"
defp get_spinner_size_classes("sm"), do: "pc-button__spinner-icon--sm"
defp get_spinner_size_classes("md"), do: "pc-button__spinner-icon--md"
defp get_spinner_size_classes("lg"), do: "pc-button__spinner-icon--lg"
defp get_spinner_size_classes("xl"), do: "pc-button__spinner-icon--xl"
def get_icon_button_size_classes("xs"), do: "pc-icon-button--xs"
def get_icon_button_size_classes("sm"), do: "pc-icon-button--sm"
def get_icon_button_size_classes("md"), do: "pc-icon-button--md"
def get_icon_button_size_classes("lg"), do: "pc-icon-button--lg"
def get_icon_button_size_classes("xl"), do: "pc-icon-button--xl"
def get_icon_button_spinner_size_classes("xs"), do: "pc-icon-button-spinner--xs"
def get_icon_button_spinner_size_classes("sm"), do: "pc-icon-button-spinner--sm"
def get_icon_button_spinner_size_classes("md"), do: "pc-icon-button-spinner--md"
def get_icon_button_spinner_size_classes("lg"), do: "pc-icon-button-spinner--lg"
def get_icon_button_spinner_size_classes("xl"), do: "pc-icon-button-spinner--xl"
defp get_icon_button_color_classes("primary"), do: "pc-icon-button--primary"
defp get_icon_button_color_classes("secondary"),
do: "pc-icon-button--secondary"
defp get_icon_button_color_classes("gray"), do: "pc-icon-button--gray"
defp get_icon_button_color_classes("info"), do: "pc-icon-button--primary"
defp get_icon_button_color_classes("success"), do: "pc-icon-button--success"
defp get_icon_button_color_classes("warning"), do: "pc-icon-button--warning"
defp get_icon_button_color_classes("danger"), do: "pc-icon-button--danger"
defp get_icon_button_background_color_classes("primary"),
do: "pc-icon-button-bg--primary"
defp get_icon_button_background_color_classes("secondary"),
do: "pc-icon-button-bg--secondary"
defp get_icon_button_background_color_classes("gray"),
do: "pc-icon-button-bg--gray"
defp get_icon_button_background_color_classes("info"),
do: "pc-icon-button-bg--info"
defp get_icon_button_background_color_classes("success"),
do: "pc-icon-button-bg--success"
defp get_icon_button_background_color_classes("warning"),
do: "pc-icon-button-bg--warning"
defp get_icon_button_background_color_classes("danger"),
do: "pc-icon-button-bg--danger"
defp get_disabled_classes(true), do: "pc-button--disabled"
defp get_disabled_classes(false), do: ""
end