Corex.Select
(Corex v0.1.0-alpha.33)
View Source
Phoenix implementation of Zag.js Select.
Examples
The placeholder text comes from the Translation struct. Use translation={%Select.Translation{ placeholder: gettext("Select an option") }} to customize.
Minimal
<.select
id="my-select"
class="select"
items={[
%{label: "France", id: "fra", disabled: true},
%{label: "Belgium", id: "bel"},
%{label: "Germany", id: "deu"},
%{label: "Netherlands", id: "nld"},
%{label: "Switzerland", id: "che"},
%{label: "Austria", id: "aut"}
]}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
</.select>Grouped
<.select
class="select"
items={[
%{label: "France", id: "fra", group: "Europe"},
%{label: "Belgium", id: "bel", group: "Europe"},
%{label: "Germany", id: "deu", group: "Europe"},
%{label: "Netherlands", id: "nld", group: "Europe"},
%{label: "Switzerland", id: "che", group: "Europe"},
%{label: "Austria", id: "aut", group: "Europe"},
%{label: "Japan", id: "jpn", group: "Asia"},
%{label: "China", id: "chn", group: "Asia"},
%{label: "South Korea", id: "kor", group: "Asia"},
%{label: "Thailand", id: "tha", group: "Asia"},
%{label: "USA", id: "usa", group: "North America"},
%{label: "Canada", id: "can", group: "North America"},
%{label: "Mexico", id: "mex", group: "North America"}
]}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
</.select>Custom
This example requires the installation of Flagpack to display the use of custom item rendering.
<.select
class="select"
items={[
%{label: "France", id: "fra"},
%{label: "Belgium", id: "bel"},
%{label: "Germany", id: "deu"},
%{label: "Netherlands", id: "nld"},
%{label: "Switzerland", id: "che"},
%{label: "Austria", id: "aut"}
]}
>
<:label>
Country of residence
</:label>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(item.id)} />
{item.label}
</:item>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
<:item_indicator>
<.heroicon name="hero-check" />
</:item_indicator>
</.select>Custom Grouped
This example requires the installation of Flagpack to display the use of custom item rendering.
<.select
class="select"
items={[
%{label: "France", id: "fra", group: "Europe"},
%{label: "Belgium", id: "bel", group: "Europe"},
%{label: "Germany", id: "deu", group: "Europe"},
%{label: "Japan", id: "jpn", group: "Asia"},
%{label: "China", id: "chn", group: "Asia"},
%{label: "South Korea", id: "kor", group: "Asia"}
]}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(item.id)} />
{item.label}
</:item>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
<:item_indicator>
<.heroicon name="hero-check" />
</:item_indicator>
</.select>Use as Navigation
Set redirect so the first selected value is used as the destination URL. Per item: redirect: false disables redirect; new_tab: true opens in a new tab.
Controller
When not connected to LiveView, the hook automatically performs a full page redirect via window.location.
<.select
id="nav-select"
class="select"
redirect
translation={%Corex.Select.Translation{placeholder: "Go to"}}
items={[
%{label: "Account", id: ~p"/account"},
%{label: "Settings", id: ~p"/settings"}
]}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
</.select>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.
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={[
%{label: "Account", id: ~p"/account"},
%{label: "Settings", id: ~p"/settings"}
]}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
</.select>
"""
end
endPhoenix Form Integration
When using with Phoenix forms, you must add an id to the form using the Corex.Form.get_form_id/1 function.
Controller
Build the form from an Ecto changeset:
def form_page(conn, _params) do
form =
%MyApp.Form.SelectForm{}
|> MyApp.Form.SelectForm.changeset(%{})
|> Phoenix.Component.to_form(as: :select_form, id: "select-form")
render(conn, :form_page, form: form)
end<.form :let={f} for={@form} id={Corex.Form.get_form_id(@form)} action={@action} method="post">
<.select
field={f[:country]}
class="select"
translation={%Corex.Select.Translation{placeholder: "Select a country"}}
items={[
%{label: "France", id: "fra", disabled: true},
%{label: "Belgium", id: "bel"},
%{label: "Germany", id: "deu"},
%{label: "Netherlands", id: "nld"},
%{label: "Switzerland", id: "che"},
%{label: "Austria", id: "aut"}
]}
>
<:label>Your country of residence</:label>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.select>
<button type="submit">Submit</button>
</.form>Live View
When using in a Live view you must add controlled mode. Prefer building the form from an Ecto changeset (see "With Ecto changeset" below).
With Ecto changeset
When using Ecto changeset for validation and inside a Live view you must enable the controlled mode.
This allows the Live View to be the source of truth and the component to be in sync accordingly.
First create your schema and changeset:
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :country, :string
timestamps(type: :utc_datetime)
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :country])
|> validate_required([:name, :country])
end
enddefmodule MyAppWeb.UserLive do
use MyAppWeb, :live_view
alias MyApp.Accounts.User
def mount(_params, _session, socket) do
{:ok, assign(socket, :form, to_form(User.changeset(%User{}, %{})))}
end
def handle_event("validate", %{"user" => user_params}, socket) do
changeset = User.changeset(%User{}, user_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def render(assigns) do
~H"""
<.form for={@form} id={get_form_id(@form)} phx-change="validate">
<.select
field={@form[:country]}
class="select"
controlled
translation={%Corex.Select.Translation{placeholder: "Select a country"}}
items={[
%{label: "France", id: "fra"},
%{label: "Belgium", id: "bel"},
%{label: "Germany", id: "deu"}
]}
>
<:label>Your country of residence</:label>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.select>
</.form>
"""
end
endAPI Control
# Client-side
<button phx-click={Corex.Select.set_value("my-select", "fra")}>
Check
</button>
<button phx-click={Corex.Select.toggle_value("my-select")}>
Toggle
</button>
# Server-side
def handle_event("set_value", _, socket) do
{:noreply, Corex.Select.set_value(socket, "my-select", "fra")}
endStyling
Use data attributes to target elements:
[data-scope="select"][data-part="root"] {}
[data-scope="select"][data-part="control"] {}
[data-scope="select"][data-part="label"] {}
[data-scope="select"][data-part="input"] {}
[data-scope="select"][data-part="error"] {}
[data-scope="select"][data-part="trigger"] {}
[data-scope="select"][data-part="item-group"] {}
[data-scope="select"][data-part="item-group-label"] {}
[data-scope="select"][data-part="item"] {}
[data-scope="select"][data-part="item-text"] {}
[data-scope="select"][data-part="item-indicator"] {}If you wish to use the default Corex styling, you can use the class select on the component.
This requires to install Mix.Tasks.Corex.Design first and import the component css file.
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/select.css";You can then use modifiers
<.select class="select select--accent select--lg">Learn more about modifiers and Corex Design
Summary
Functions
Attributes
id(:string) - The id of the select component.items(:list) - List of items (maps with :id and :label, or Corex.List.Item). Defaults to[].controlled(:boolean) - Whether the select is controlled. Defaults tofalse.value(:list) - The value of the select. Defaults to[].disabled(:boolean) - Whether the select is disabled. Defaults tofalse.close_on_select(:boolean) - Whether to close the select on select. Defaults totrue.dir(:string) - The direction of the select. When nil, derived from document (html lang + config :rtl_locales). Defaults tonil.orientation(:string) - Layout orientation for CSS (vertical or horizontal). Defaults to"vertical". Must be one of"vertical", or"horizontal".loop_focus(:boolean) - Whether to loop focus the select. Defaults tofalse.multiple(:boolean) - Whether to allow multiple selection. Defaults tofalse.invalid(:boolean) - Whether the select is invalid. Defaults tofalse.name(:string) - The name of the select.form(:string) - The id of the form of the select.read_only(:boolean) - Whether the select is read only. Defaults tofalse.required(:boolean) - Whether the select is required. Defaults tofalse.prompt(:string) - the prompt for select inputs. Defaults tonil.on_value_change(:string) - Server event name to push on value change. Payload includesvalue(list),path(current path without locale),id,items. UseEnum.at(value, 0)for the first selected value. Defaults tonil.on_value_change_client(:any) - Client-side only: either a string (CustomEvent name to dispatch) or aPhoenix.LiveView.JScommand. For JS commands, placeholders are replaced at run time:__VALUE__(selected value(s) as JSON array),__VALUE_0__(first value). For redirect-on-select useredirectinstead (no placeholders).Defaults to
nil.redirect(:boolean) - When true, the first selected value is used as the destination URL. When not connected the hook sets window.location; when connected use on_value_change and redirect(socket, to: Enum.at(value, 0)) in your handler. Same approach as menu's redirect. Per item: set redirect: false on an item to disable redirect for that item; set new_tab: true to open that item's URL in a new tab. Defaults tofalse.positioning(Corex.Positioning) - Positioning options for the dropdown. Defaults to%Corex.Positioning{hide_when_detached: true, strategy: "fixed", placement: "bottom", gutter: 8, shift: 0, overflow_padding: 0, arrow_padding: 4, flip: true, slide: true, overlap: false, same_width: true, fit_viewport: false}.translation(Corex.Select.Translation) - Override translatable strings. Defaults tonil.field(Phoenix.HTML.FormField) - A form field struct retrieved from the form, for example: @form[:country]. Automatically sets id, name, value, and errors from the form field.errors(:list) - List of error messages to display. Defaults to[].Global attributes are accepted.
Slots
label- The label content. Accepts attributes:class(:string)
trigger(required) - The trigger button content. Accepts attributes:class(:string)
item_indicator- Optional indicator for selected items. Accepts attributes:class(:string)
error- Accepts attributes:class(:string)
item- Custom content for each item. Receives the item as :let binding. Accepts attributes:class(:string)