defmodule Corex.Select do
@moduledoc ~S'''
Phoenix implementation of [Zag.js Select](https://zagjs.com/components/react/select).
## Anatomy
The placeholder text comes from the `translation` attribute (default English `"Select"` is passed through the host Phoenix gettext backend at render time when unchanged). Pass `translation={%Select.Translation{placeholder: …}}` to customize.
### Minimal
```heex
<.select
class="select"
items={Corex.List.new([
%{label: "France", value: "fra", disabled: true},
%{label: "Belgium", value: "bel"},
%{label: "Germany", value: "deu"},
%{label: "Netherlands", value: "nld"},
%{label: "Switzerland", value: "che"},
%{label: "Austria", value: "aut"}
])}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
```
### Grouped
```heex
<.select
class="select"
items={Corex.List.new([
%{label: "France", value: "fra", group: "Europe"},
%{label: "Belgium", value: "bel", group: "Europe"},
%{label: "Germany", value: "deu", group: "Europe"},
%{label: "Netherlands", value: "nld", group: "Europe"},
%{label: "Switzerland", value: "che", group: "Europe"},
%{label: "Austria", value: "aut", group: "Europe"},
%{label: "Japan", value: "jpn", group: "Asia"},
%{label: "China", value: "chn", group: "Asia"},
%{label: "South Korea", value: "kor", group: "Asia"},
%{label: "Thailand", value: "tha", group: "Asia"},
%{label: "USA", value: "usa", group: "North America"},
%{label: "Canada", value: "can", group: "North America"},
%{label: "Mexico", value: "mex", group: "North America"}
])}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
```
### Custom
This example requires the installation of [Flagpack](https://hex.pm/packages/flagpack) to display the use of custom item rendering.
```heex
<.select
class="select"
items={Corex.List.new([
%{label: "France", value: "fra"},
%{label: "Belgium", value: "bel"},
%{label: "Germany", value: "deu"},
%{label: "Netherlands", value: "nld"},
%{label: "Switzerland", value: "che"},
%{label: "Austria", value: "aut"}
])}
>
<:label>
Country of residence
<:item :let={item}>
{item.label}
<:trigger>
<.heroicon name="hero-chevron-down" />
<:item_indicator>
<.heroicon name="hero-check" />
```
### Custom Grouped
This example requires the installation of [Flagpack](https://hex.pm/packages/flagpack) to display the use of custom item rendering.
```heex
<.select
class="select"
items={Corex.List.new([
%{label: "France", value: "fra", group: "Europe"},
%{label: "Belgium", value: "bel", group: "Europe"},
%{label: "Germany", value: "deu", group: "Europe"},
%{label: "Japan", value: "jpn", group: "Asia"},
%{label: "China", value: "chn", group: "Asia"},
%{label: "South Korea", value: "kor", group: "Asia"}
])}
>
<:item :let={item}>
{item.label}
<:trigger>
<.heroicon name="hero-chevron-down" />
<:item_indicator>
<.heroicon name="hero-check" />
```
## Patterns
### Navigation
Set `redirect` on the component so the first selected value is used as the destination URL.
Per item, choose the navigation kind explicitly via the item's `:redirect` field:
* `:href` (default) - full page redirect via `window.location` (safe everywhere)
* `:patch` - LiveView `js().patch(url)` (caller asserts: same LV mount + matching live route)
* `:navigate` - LiveView `js().navigate(url)` (caller asserts: another LV in the same `live_session`)
* `false` - disable redirect for this item (e.g. let your `on_value_change` server handler decide)
Set `new_tab: true` on an item to open its destination in a new tab via `window.open`.
An item may also set `:to` to override the destination (defaults to the item id).
Build items with `Corex.List.new/1`. When `redirect` is true, the client runs **single-select in Zag** even if `multiple` is set on the component.
### Controller
When not connected to LiveView, the hook always performs a full page redirect via `window.location`.
```heex
<.select
class="select"
redirect
translation={%Corex.Select.Translation{placeholder: "Go to"}}
items={Corex.List.new([
%{label: "Account", id: ~p"/account"},
%{label: "Settings", id: ~p"/settings"}
])}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
```
### LiveView
When connected to LiveView, use `on_value_change` and redirect in the callback. The payload includes `value` (list); use `Enum.at(value, 0)` for the destination.
```elixir
defmodule MyAppWeb.NavLive do
use MyAppWeb, :live_view
def handle_event("nav_change", %{"value" => value}, socket) do
path = Enum.at(value, 0) || ~p"/"
{:noreply, push_navigate(socket, to: path)}
end
def render(assigns) do
~H"""
<.select
id="nav-select"
class="select"
redirect
on_value_change="nav_change"
translation={%Corex.Select.Translation{placeholder: "Go to"}}
items={Corex.List.new([
%{label: "Account", id: ~p"/account"},
%{label: "Settings", id: ~p"/settings"}
])}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
"""
end
end
```
### Stream
Use `Phoenix.LiveView.stream/3` to add or remove options at runtime. Keep `@items_list` in sync and pass `Corex.List.new(@items_list)` as `items`. Configure `dom_id` as `select:stream-select:item:#{value}`.
```heex
<.select class="select" items={Corex.List.new(@items_list)}>
<:label>Country
<:trigger>
<.heroicon name="hero-chevron-down" class="icon" />
```
## Form
When using with Phoenix forms, set the form `id` in `to_form/2` (for example `to_form(changeset, as: :name, id: "my-form")`) and use `<.form for={@form}>`.
For cross-cutting invalid styling and error presentation, see the [Forms](forms.html) guide. Pass `invalid={Corex.FormField.invalid?(@form[:field])}` when you want alert borders after validation.
### Multiple selection and `{:array, :string}` fields
With `multiple` and `field={f[:tags]}`, the hidden native `