Current section

Files

Jump to
pui lib pui button.ex
Raw

lib/pui/button.ex

defmodule PUI.Button do
@moduledoc """
A versatile button component with multiple variants and sizes.
## Basic Usage
<.button>Click me</.button>
## Variants
Use the `variant` attribute to change the button style:
<.button variant="default">Default</.button>
<.button variant="secondary">Secondary</.button>
<.button variant="destructive">Destructive</.button>
<.button variant="outline">Outline</.button>
<.button variant="ghost">Ghost</.button>
<.button variant="link">Link</.button>
## Sizes
Use the `size` attribute to adjust the button size:
<.button size="sm">Small</.button>
<.button size="default">Default</.button>
<.button size="lg">Large</.button>
<.button size="icon">
<.icon name="hero-heart" />
</.button>
## With Icons
Include icons within the button content:
<.button>
<.icon name="hero-heart" class="w-4 h-4" />
Like
</.button>
## As Links
Buttons can act as links using standard Phoenix link attributes:
<.button navigate={~p"/profile"}>Navigate</.button>
<.button patch={~p"/settings"}>Patch</.button>
<.button href="/logout">Href</.button>
## Disabled State
<.button disabled>Disabled</.button>
## Unstyled Variant
Use `variant="unstyled"` for complete control over styling:
<.button variant="unstyled" class="px-4 py-2 bg-blue-500 text-white rounded">
Custom Button
</.button>
## Attributes
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `variant` | `string` | `"default"` | Button style variant |
| `size` | `string` | `"default"` | Button size: "sm", "default", "lg", "icon" |
| `class` | `string` | `""` | Additional CSS classes |
| `rest` | `global` | - | HTML attributes including `href`, `navigate`, `patch`, `disabled` |
## Slots
| Slot | Description |
|------|-------------|
| `inner_block` | Button content (required) |
"""
use Phoenix.Component
attr :class, :string, default: ""
attr :variant, :string,
values: ["default", "destructive", "outline", "secondary", "ghost", "link", "unstyled"],
default: "default"
attr :size, :string, values: ["default", "sm", "lg", "icon"], default: "default"
attr :rest, :global, include: ~w(href navigate patch method download name value disabled)
slot :inner_block, required: true
def button(%{rest: rest} = assigns) do
is_unstyled = assigns.variant == "unstyled"
variant_class =
if is_unstyled do
""
else
case assigns.variant do
"default" ->
"bg-primary text-primary-foreground hover:bg-primary/90"
"destructive" ->
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60"
"outline" ->
"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
"secondary" ->
"bg-secondary text-secondary-foreground hover:bg-secondary/80"
"ghost" ->
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50"
"link" ->
"text-primary underline-offset-4 hover:underline"
end
end
size_class =
if is_unstyled do
""
else
case assigns.size do
"default" -> "h-9 px-4 py-2 has-[>svg]:px-3"
"sm" -> "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5"
"lg" -> "h-10 rounded-md px-6 has-[>svg]:px-4"
"icon" -> "size-9"
end
end
override_class = Map.get(assigns, :class, "")
base_classes =
if is_unstyled do
[]
else
[
"inline-flex active:translate-y-px items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
]
end
assigns =
assign(assigns,
class:
(base_classes ++ [variant_class, size_class, override_class])
|> Enum.filter(&(&1 != "")),
size_class: size_class,
variant_class: variant_class
)
if rest[:href] || rest[:navigate] || rest[:patch] do
~H"""
<.link class={@class} {@rest}>
{render_slot(@inner_block)}
</.link>
"""
else
~H"""
<button class={@class} {@rest}>
{render_slot(@inner_block)}
</button>
"""
end
end
end