Packages
petal_components
0.1.0
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/button.ex
defmodule PetalComponents.Button do
use Phoenix.Component
def button(assigns) do
assigns = get_extra_attributes(assigns)
~H"""
<button class={button_classes(assigns)} disabled={assigns[:disabled]} {@extra_attributes}>
<%= if assigns[:loading] do %>
<svg
class="w-5 h-5 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<% end %>
<%= if assigns[:inner_block] do %>
<%= render_slot(@inner_block) %>
<% else %>
<%= @label %>
<% end %>
</button>
"""
end
def a(assigns) do
assigns = get_extra_attributes(assigns)
~H"""
<a href={@href} class={button_classes(assigns)} onclick={if assigns[:disabled] || assigns[:loading], do: "return false;", else: nil} {@extra_attributes}>
<%= if assigns[:loading] do %>
<svg
class="w-5 h-5 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<% end %>
<%= if assigns[:inner_block] do %>
<%= render_slot(@inner_block) %>
<% else %>
<%= @label %>
<% end %>
</a>
"""
end
def patch(assigns) do
assigns = get_extra_attributes(assigns)
~H"""
<%= live_patch [
to: @href,
class: button_classes(assigns),
onclick: (if assigns[:disabled] || assigns[:loading], do: "return false;", else: nil)
] ++ Map.to_list(assigns.extra_attributes) do %>
<%= if assigns[:loading] do %>
<svg
class="w-5 h-5 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<% end %>
<%= if assigns[:inner_block] do %>
<%= render_slot(@inner_block) %>
<% else %>
<%= @label %>
<% end %>
<% end %>
"""
end
def redirect(assigns) do
assigns = get_extra_attributes(assigns)
~H"""
<%= live_redirect [
to: @href,
class: button_classes(assigns),
onclick: (if assigns[:disabled] || assigns[:loading], do: "return false;", else: nil)
] ++ Map.to_list(assigns.extra_attributes) do %>
<%= if assigns[:loading] do %>
<svg
class="w-5 h-5 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<% end %>
<%= if assigns[:inner_block] do %>
<%= render_slot(@inner_block) %>
<% else %>
<%= @label %>
<% end %>
<% end %>
"""
end
def 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,
icon: opts[:icon] || false
}
color_css = get_color_classes(opts)
size_css =
case opts[:size] do
"xs" -> "text-xs leading-4 px-2.5 py-1.5"
"sm" -> "text-sm leading-4 px-3 py-2"
"md" -> "text-sm leading-5 px-4 py-2"
"lg" -> "text-base leading-6 px-4 py-2"
"xl" -> "text-base leading-6 px-6 py-3"
end
loading_css =
if opts[:loading] do
"flex gap-2 items-center whitespace-nowrap disabled cursor-not-allowed"
else
""
end
disabled_css =
if opts[:disabled] do
"disabled cursor-not-allowed opacity-50"
else
""
end
icon_css =
if opts[:icon] do
"flex gap-2 items-center whitespace-nowrap"
else
""
end
"""
#{color_css}
#{size_css}
#{loading_css}
#{disabled_css}
#{icon_css}
font-medium
shadow-sm
rounded-md
inline-flex items-center justify-center
border
focus:outline-none
transition duration-150 ease-in-out
"""
end
def get_extra_attributes(assigns) do
assign_new(assigns, :extra_attributes, fn ->
Map.drop(assigns, [
:loading,
:disabled,
:inner_block,
:size,
:variant,
:color,
:icon
])
end)
end
def get_color_classes(%{color: "primary", variant: variant}) do
case variant do
"outline" ->
"border-primary-400 hover:border-primary-600 text-primary-600 hover:text-primary-700 active:bg-primary-200 hover:bg-primary-50 focus:border-primary-700 focus:shadow-outline-primary"
_ ->
"border-transparent text-white bg-primary-600 active:bg-primary-700 hover:bg-primary-700 focus:bg-primary-700 active:bg-primary-800 focus:shadow-outline-primary"
end
end
def get_color_classes(%{color: "secondary", variant: variant}) do
case variant do
"outline" ->
"border-secondary-400 hover:border-secondary-600 text-secondary-600 hover:text-secondary-700 active:bg-secondary-200 hover:bg-secondary-50 focus:border-secondary-700 focus:shadow-outline-secondary"
_ ->
"border-transparent text-white bg-secondary-600 active:bg-secondary-700 hover:bg-secondary-700 focus:bg-secondary-700 active:bg-secondary-800 focus:shadow-outline-secondary"
end
end
def get_color_classes(%{color: "white", variant: variant}) do
case variant do
"outline" ->
"border-gray-400 hover:border-gray-500 text-gray-600 hover:text-gray-700 active:bg-gray-100 hover:bg-gray-50 focus:bg-gray-50 focus:border-gray-500 active:border-gray-600"
_ ->
"text-gray-700 border-gray-300 hover:text-gray-900 hover:text-gray-900 hover:border-gray-400 hover:bg-gray-50 focus:outline-none focus:border-gray-400 focus:bg-gray-100 focus:text-gray-900 active:border-gray-400 active:bg-gray-200 active:text-black"
end
end
def get_color_classes(%{color: "success", variant: variant}) do
case variant do
"outline" ->
"border-green-400 hover:border-green-500 text-green-500 hover:text-green-600 active:border-green-600 focus:text-green-600 active:text-green-700 active:bg-green-100 hover:bg-green-50 focus:border-green-500"
_ ->
"border-transparent text-white bg-green-500 active:bg-green-700 hover:bg-green-600 active:bg-green-700 focus:bg-green-600"
end
end
def get_color_classes(%{color: "danger", variant: variant}) do
case variant do
"outline" ->
"border-red-400 hover:border-red-600 text-red-600 hover:text-red-700 active:bg-red-200 active:border-red-700 hover:bg-red-50 focus:border-red-600"
_ ->
"border-transparent text-white bg-red-500 active:bg-red-700 hover:bg-red-600 active:bg-green-700 focus:bg-red-600"
end
end
end