Packages
petal_components
2.5.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/button_group.ex
defmodule PetalComponents.ButtonGroup do
use Phoenix.Component
alias PetalComponents.Helpers
@doc """
Renders a button group. Group buttons are configured by defining multiple `:button` slots.
Note: Phoenix LiveView >= 0.20.17 is required to prevent arbitrary attributes like phx-*
given to `:button` slots emitting development warnings.
## Examples
### Buttons
<.button_group aria_label="My actions" size="md">
<:button phx-click="beep" phx-value-boop="bop">Action 1</:button>
<:button phx-click="boop" phx-value-bop="beep">Action 2</:button>
<:button label="Action 3" phx-click="bop" phx-value-beep="boop" disabled={@action_disabled?} />
</.button_group>
### Links
<.button_group aria_label="My links" size="md">
<:button kind="link" patch={~p"/path-one"}>Link 1</:button>
<:button kind="link" patch={~p"/path-two"}>Link 2</:button>
<:button label="Link 3" kind="link" navigate={~p"/other"} />
</.button_group>
### Custom Styles
<.button_group
aria_label="Custom styled buttons"
button_bg_class="bg-blue-500 hover:bg-blue-600 dark:bg-blue-700 dark:hover:bg-blue-800 text-white"
button_border_class="border border-blue-300 dark:border-blue-600"
>
<:button>Custom Button 1</:button>
<:button>Custom Button 2</:button>
</.button_group>
"""
attr :id, :string, default: nil
attr :aria_label, :string, required: true, doc: "the ARIA label for the button group"
attr :size, :string, default: "md", values: ["xs", "sm", "md", "lg", "xl"]
attr :container_class, :string,
default: "pc-button-group",
doc: "class to apply to the group container"
attr :font_weight_class, :string,
default: "font-medium",
doc: "the font weight class to apply to all buttons - defaults to font-medium"
# New attributes for customizing button styles
attr :button_bg_class, :string,
default: nil,
doc: "class to customize the button background colors"
attr :button_border_class, :string,
default: nil,
doc: "class to customize the button border styles"
# We mark validate_attrs false so passing phx-click etc. does not emit warnings
slot :button, required: true, validate_attrs: false do
attr :class, :string, doc: "classes in addition to those already configured"
attr :label, :string, doc: "a button label, rendered if you don't provide an inner block"
attr :kind, :string,
values: ["button", "link"],
doc: "determines whether we render a button or a <.link />"
attr :disabled, :boolean,
doc: "disables the button - will turn an <a> into a <button> (<a> tags can't be disabled)"
end
def button_group(assigns) do
~H"""
<div
aria-label={@aria_label}
role="group"
id={@id || Helpers.uniq_id("button-group")}
class={@container_class}
>
<.group_button
:for={{group_btn_assigns, idx} <- Enum.with_index(@button)}
idx={idx}
last_idx={length(@button) - 1}
size={@size}
font_weight_class={@font_weight_class}
button_bg_class={@button_bg_class}
button_border_class={@button_border_class}
{group_btn_assigns}
>
<%= if is_function(group_btn_assigns.inner_block) do %>
<%= render_slot(group_btn_assigns) %>
<% else %>
<%= group_btn_assigns.label %>
<% end %>
</.group_button>
</div>
"""
end
attr :class, :string, default: ""
attr :label, :string
attr :kind, :string, default: "button", values: ["button", "link"]
attr :disabled, :boolean, default: false
attr :font_weight_class, :string, required: true
attr :size, :string, required: true, values: ["xs", "sm", "md", "lg", "xl"]
attr :idx, :integer, required: true
attr :last_idx, :integer, required: true
attr :button_bg_class, :string, default: nil
attr :button_border_class, :string, default: nil
attr :rest, :global,
include:
~w(patch navigate method download hreflang ping referrerpolicy rel target type value name form)
slot :inner_block, doc: "The inner block of the button.", required: true
defp group_button(%{kind: "button"} = assigns) do
~H"""
<button
disabled={@disabled}
aria-disabled={@disabled}
class={[@class | group_btn_class(assigns)]}
{@rest}
>
<%= render_slot(@inner_block) %>
</button>
"""
end
# Anchor tags can't be disabled - renders a disabled button
defp group_button(%{kind: "link", disabled: true} = assigns) do
assigns = update_in(assigns.rest, &Map.drop(&1, [:"phx-click"]))
~H"""
<button disabled aria-disabled class={[@class | group_btn_class(assigns)]} {@rest}>
<%= render_slot(@inner_block) %>
</button>
"""
end
defp group_button(%{kind: "link"} = assigns) do
~H"""
<.link class={[@class | group_btn_class(assigns)]} {@rest}>
<%= render_slot(@inner_block) %>
</.link>
"""
end
defp group_btn_class(assigns) do
base_classes = [
"pc-button-group__button",
size_class(assigns.size),
font_weight_class(assigns.font_weight_class),
"pc-button-group__button--default-styles",
background_class(nil),
border_class(nil)
]
custom_classes = [
background_class(assigns.button_bg_class),
border_class(assigns.button_border_class)
]
position_class = position_class(assigns.idx, assigns.last_idx)
# Assemble classes in order: base, custom, position
base_classes ++ custom_classes ++ [position_class]
end
defp position_class(idx, last_idx) do
cond do
idx == 0 and idx == last_idx -> "pc-button-group__button--rounded"
idx == 0 -> "pc-button-group__button--rounded-r-none"
idx == last_idx -> "pc-button-group__button--rounded-l-none"
true -> "pc-button-group__button--rounded-none"
end
end
defp size_class(size), do: "pc-button-group__button--#{size}"
defp font_weight_class("font-normal"), do: "pc-button-group__button--font-normal"
defp font_weight_class("font-medium"), do: "pc-button-group__button--font-medium"
defp font_weight_class("font-semibold"), do: "pc-button-group__button--font-semibold"
defp font_weight_class("font-bold"), do: "pc-button-group__button--font-bold"
defp font_weight_class(custom_class), do: custom_class
defp background_class(nil), do: "pc-button-group__button--bg-default"
defp background_class(custom_class), do: custom_class
defp border_class(nil), do: "pc-button-group__button--border-default"
defp border_class(custom_class), do: custom_class
end