Skip to content

Commit

Permalink
Implement toggle group
Browse files Browse the repository at this point in the history
  • Loading branch information
bluzky committed Oct 1, 2024
1 parent 5733eab commit 06e68e6
Showing 1 changed file with 152 additions and 0 deletions.
152 changes: 152 additions & 0 deletions lib/salad_ui/toggle_group.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
defmodule SaladUI.ToggleGroup do
@moduledoc false
use SaladUI, :component

@doc """
A set of two-state buttons that can be toggled on or off.
## Example:
<.toggle_group name="style" type="single" value="bold">
<.toggle_group_item value="bold" builder={builder} aria-label="Toggle bold">
<.icon name="hero-bold" class="h-4 w-4" />
</.toggle_group_item>
<.toggle_group_item value="italic" builder={builder} aria-label="Toggle italic">
<.icon name="hero-italic" class="h-4 w-4" />
</.toggle_group_item>
<.toggle_group_item value="underline" builder={builder} aria-label="Toggle underline">
<.icon name="hero-underline" class="h-4 w-4" />
</.toggle_group_item>
</.toggle_group>
"""
attr :name, :any, required: true
attr :class, :string, default: nil
attr :variant, :string, default: "default"
attr :size, :string, default: "default"
attr :type, :string, values: ~w(single multiple), default: "single"

attr :value, :string,
default: nil,
doc: "The value of the toggle group. It's a single value for single type and a list of values for multiple type."

attr :disabled, :boolean, default: false
attr :rest, :global
slot :inner_block

def toggle_group(assigns) do
validate_value_type(assigns)

~H"""
<div class={classes(["flex items-center justify-center gap-1", @class])}>
<%= render_slot(@inner_block, assigns) %>
</div>
"""
end

defp validate_value_type(%{value: value, type: type} = _assigns) do
cond do
type == "multiple" and not is_list(value) ->
raise ArgumentError, "The value of the toggle group must be a list for multiple type."

type == "single" and not (is_nil(value) or is_binary(value)) ->
raise ArgumentError, "The value of the toggle group must be a single value for single type."

true ->
nil
end
end

attr :class, :string, default: nil
attr :disabled, :boolean, default: false
attr :value, :string, default: nil
attr :builder, :map, required: true, doc: "The builder context of toggle group."
attr :rest, :global
slot :inner_block

def toggle_group_item(%{builder: %{type: "single"}} = assigns) do
assigns =
assigns
|> assign(:variant_class, variant(assigns.builder))
|> assign(:checked, assigns.value == assigns.builder.value)

~H"""
<button
onclick="this.querySelector('.toggle-input').click()"
disabled={@disabled || @builder.disabled}
class={
classes([
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 has-[:checked]:bg-accent has-[:checked]:text-accent-foreground",
@variant_class,
@class
])
}
>
<input
type="radio"
class="toggle-input hidden"
name={@builder.name}
value={@value}
checked={@checked}
{@rest}
/>
<%= render_slot(@inner_block) %>
</button>
"""
end

def toggle_group_item(%{builder: %{type: "multiple"}} = assigns) do
assigns =
assigns
|> assign(:variant_class, variant(assigns.builder))
|> assign(:checked, assigns.value in assigns.builder.value)

~H"""
<button
onclick="this.querySelector('.toggle-input').click()"
disabled={@disabled || @builder.disabled}
class={
classes([
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 has-[:checked]:bg-accent has-[:checked]:text-accent-foreground",
@variant_class,
@class
])
}
>
<input
type="checkbox"
class="toggle-input hidden"
name={@builder.name <> "[]"}
value={@value}
checked={@checked}
{@rest}
/>
<%= render_slot(@inner_block) %>
</button>
"""
end

@variants %{
variant: %{
"default" => "bg-transparent",
"outline" => "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground"
},
size: %{
"default" => "h-10 px-3",
"sm" => "h-9 px-2.5",
"lg" => "h-11 px-5"
}
}

@default_variants %{
variant: "default",
size: "default"
}

defp variant(props) do
variants = Map.take(props, ~w(variant size)a)
variants = Map.merge(@default_variants, variants)

Enum.map_join(variants, " ", fn {key, value} -> @variants[key][value] end)
end
end

0 comments on commit 06e68e6

Please sign in to comment.