defmodule Corex.RadioGroup do @moduledoc ~S''' Phoenix implementation of [Zag.js Radio Group](https://zagjs.com/components/react/radio-group). ## Examples ### Basic (without indicator) ```heex <.radio_group id="rg" name="choice" items={[["1", "Option A"], ["2", "Option B"]]} class="radio-group"> <:label>Choose one ``` ### With indicator ```heex <.radio_group id="rg" name="choice" items={[["1", "Option A"], ["2", "Option B"]]} class="radio-group"> <:label>Choose one <:item_control><.icon name="hero-check" class="data-checked" /> ``` Items can be a list of `{value, label}` tuples or a list of maps with `:value`, `:label`, and optional `:disabled`, `:invalid`. Optional `:item_control` slot renders the check indicator for each item; when omitted, no indicator is shown. ## Styling Use data attributes to target elements: ```css [data-scope="radio-group"][data-part="root"] {} [data-scope="radio-group"][data-part="label"] {} [data-scope="radio-group"][data-part="indicator"] {} [data-scope="radio-group"][data-part="item"] {} [data-scope="radio-group"][data-part="item-text"] {} [data-scope="radio-group"][data-part="item-control"] {} [data-scope="radio-group"][data-part="item-hidden-input"] {} ``` If you wish to use the default Corex styling, you can use the class `radio-group` on the component. This requires to install `Mix.Tasks.Corex.Design` first and import the component css file. ```css @import "../corex/main.css"; @import "../corex/tokens/themes/neo/light.css"; @import "../corex/components/radio-group.css"; ``` You can then use modifiers ```heex <.radio_group class="radio-group radio-group--accent radio-group--lg" items={[]}> ``` Learn more about modifiers and [Corex Design](https://corex-ui.com/components/radio-group#modifiers) ''' @doc type: :component use Phoenix.Component alias Corex.RadioGroup.Anatomy.{ Props, Root, Label, Indicator, Item, ItemText, ItemControl, ItemHiddenInput } alias Corex.RadioGroup.Connect attr(:id, :string, required: false) attr(:value, :string, default: nil) attr(:default_value, :string, default: nil) attr(:controlled, :boolean, default: false) attr(:name, :string, default: nil) attr(:form, :string, default: nil) attr(:disabled, :boolean, default: false) attr(:invalid, :boolean, default: false) attr(:required, :boolean, default: false) attr(:read_only, :boolean, default: false) attr(:dir, :string, default: nil, values: [nil, "ltr", "rtl"]) attr(:orientation, :string, default: "vertical", values: ["horizontal", "vertical"]) attr(:on_value_change, :string, default: nil) attr(:on_value_change_client, :string, default: nil) attr(:items, :list, required: true, doc: "List of [value, label] or %{value: ..., label: ..., disabled: ..., invalid: ...}" ) attr(:rest, :global) slot(:label, required: false) slot(:item_control, required: false) slot(:item, required: false) def radio_group(assigns) do assigns = assigns |> assign_new(:id, fn -> "radio-group-#{System.unique_integer([:positive])}" end) |> assign_new(:dir, fn -> "ltr" end) |> assign(:items, normalize_items(assigns.items)) ~H"""
{render_slot(@label)}
""" end defp normalize_items(items) when is_list(items) do Enum.map(items, fn {value, label} -> %{value: to_string(value), label: to_string(label), disabled: false, invalid: false} [value, label] -> %{value: to_string(value), label: to_string(label), disabled: false, invalid: false} %{value: v, label: l} = m -> %{ value: to_string(v), label: to_string(l), disabled: !!Map.get(m, :disabled), invalid: !!Map.get(m, :invalid) } other -> raise ArgumentError, "radio_group items must be {value, label}, [value, label], or %{value: ..., label: ...}, got: #{inspect(other)}" end) end end