defmodule Shadix.Components.Toggle do
@moduledoc """
Toggle component translated from shadcn/ui (new-york-v4).
A two-state toggle button. Unlike a native checkbox, the pressed state lives in
ARIA (`aria-pressed`) and a `data-state` of `"on"`/`"off"` that the `cva` styles
key off of (the on-state gets `bg-accent`). It renders a plain
`<button type="button">` and is *uncontrolled*: `@pressed` only seeds the initial
markup. The small `ShadixToggle` hook (assets/ts/toggle.ts) flips both
`aria-pressed` and `data-state` on click by reading the current DOM value, so it
stays independent of the server-rendered initial state.
Caller-supplied `class` is appended last; Tailwind cascade layers ensure it wins
over the defaults.
"""
use Phoenix.Component
import Shadix.Cn
@base "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
@variants %{
"default" => "bg-transparent",
"outline" =>
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
}
@sizes %{
"default" => "h-9 min-w-9 px-2",
"sm" => "h-8 min-w-8 px-1.5",
"lg" => "h-10 min-w-10 px-2.5"
}
@doc """
Renders an uncontrolled two-state toggle button.
`:pressed` seeds the initial `aria-pressed` / `data-state`; thereafter the
`ShadixToggle` hook owns the state and flips it on click.
"""
attr(:variant, :string, default: "default", values: ~w(default outline))
attr(:size, :string, default: "default", values: ~w(default sm lg))
attr(:pressed, :boolean, default: false)
attr(:class, :string, default: nil)
attr(:rest, :global)
slot(:inner_block, required: true)
def toggle(assigns) do
assigns =
assign(
assigns,
:computed_class,
cn([@base, @variants[assigns.variant], @sizes[assigns.size], assigns.class])
)
~H"""
<button
type="button"
data-slot="toggle"
aria-pressed={to_string(@pressed)}
data-state={if @pressed, do: "on", else: "off"}
phx-hook="ShadixToggle"
class={@computed_class}
{@rest}
>
{render_slot(@inner_block)}
</button>
"""
end
end