Corex. Combobox
(Corex v0.1.0-rc.1)
View Source
Phoenix implementation of Zag.js Combobox.
Pass options with Corex.List.new/1. With redirect, use per-item :to, :redirect (:href | :patch | :navigate | false), and :new_tab; Zag runs single-select when redirect is true.
Anatomy
Minimal
<.combobox
class="combobox"
translation={%Corex.Combobox.Translation{placeholder: "Select a country", empty: "No results"}}
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"}
])}
>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
</.combobox>Grouped
<.combobox
class="combobox"
translation={%Corex.Combobox.Translation{placeholder: "Select a country", empty: "No results"}}
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" />
</:trigger>
</.combobox>Extended
This example requires the installation of Flagpack to display the use of custom item rendering.
<.combobox
class="combobox"
translation={%Corex.Combobox.Translation{placeholder: "Select a country", empty: "No results"}}
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"}
])}
>
<:item :let={item}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
<:clear_trigger>
<.heroicon name="hero-backspace" />
</:clear_trigger>
<:item_indicator>
<.heroicon name="hero-check" />
</:item_indicator>
</.combobox>Extended Grouped
This example requires the installation of Flagpack to display the use of custom item rendering.
<.combobox
class="combobox"
translation={%Corex.Combobox.Translation{placeholder: "Select a country", empty: "No results"}}
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}>
<Flagpack.flag name={String.to_atom(to_string(item.value))} />
{item.label}
</:item>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
<:clear_trigger>
<.heroicon name="hero-backspace" />
</:clear_trigger>
<:item_indicator>
<.heroicon name="hero-check" />
</:item_indicator>
</.combobox>API
Requires a stable id on <.combobox>.
| Function | Action | Returns |
|---|---|---|
set_value/2 | Set selection (client) | %Phoenix.LiveView.JS{} |
set_value/3 | Set selection (server) | socket |
<.action phx-click={Corex.Combobox.set_value("combobox-api", ["fra"])} class="button button--sm">France</.action>Events
Server events
| Event | When | Payload |
|---|---|---|
on_value_change="combobox_value_changed" | Selection changes | %{"id" => id, "value" => values} |
on_open_change="combobox_open_changed" | Menu open state changes | %{"id" => id, "open" => open} |
on_input_value_change="combobox_search" | Input text changes (server filter) | %{"id" => id, "value" => string, "reason" => reason} |
Client events
| Event | When | event.detail |
|---|---|---|
on_value_change_client="combobox-value-changed" | Selection changes | id, value, items |
on_open_change_client="combobox-open-changed" | Menu open state changes | id, open |
Patterns
Server-side filtering
Disable client filtering with filter={false} and use on_input_value_change to filter on the server. This example uses a local list; replace with a database query for real apps.
defmodule MyAppWeb.CountryCombobox do
use MyAppWeb, :live_view
@items [
%{value: "fra", label: "France"},
%{value: "bel", label: "Belgium"},
%{value: "deu", label: "Germany"},
%{value: "usa", label: "USA"},
%{value: "jpn", label: "Japan"}
]
def mount(_params, _session, socket) do
{:ok, assign(socket, items: [])}
end
def handle_event("search", %{"value" => value, "reason" => "input-change"}, socket) do
filtered =
if byte_size(value) < 1 do
[]
else
term = String.downcase(value)
Enum.filter(@items, fn item ->
String.contains?(String.downcase(item.label), term)
end)
end
{:noreply, assign(socket, items: filtered)}
end
def render(assigns) do
~H"""
<.combobox
items={@items}
filter={false}
on_input_value_change="search"
>
<:trigger><.heroicon name="hero-chevron-down" /></:trigger>
</.combobox>
"""
end
endStyle
Target parts with data-scope and data-part:
[data-scope="combobox"][data-part="root"] {}
[data-scope="combobox"][data-part="control"] {}
[data-scope="combobox"][data-part="input"] {}
[data-scope="combobox"][data-part="trigger"] {}
[data-scope="combobox"][data-part="clear-trigger"] {}
[data-scope="combobox"][data-part="content"] {}
[data-scope="combobox"][data-part="empty"] {}
[data-scope="combobox"][data-part="item-group"] {}
[data-scope="combobox"][data-part="item-group-label"] {}
[data-scope="combobox"][data-part="item"] {}
[data-scope="combobox"][data-part="item-text"] {}
[data-scope="combobox"][data-part="item-indicator"] {}If you wish to use the default Corex styling, you can use the class combobox 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/combobox.css";You can then use modifiers
<.combobox class="combobox combobox--accent combobox--lg" items={Corex.List.new([])}>
<:empty>No results</:empty>
<:trigger>
<.heroicon name="hero-chevron-down" />
</:trigger>
</.combobox>Form
Use field={f[:key]} with a form built from an Ecto changeset. Set the form id in to_form/2 and use <.form for={@form}>. See Select Form for full controller and LiveView examples.
For cross-cutting invalid styling and error presentation, see the Forms guide. Pass invalid={Corex.FormField.invalid?(@form[:field])} when you want alert borders after validation.
Localization
Pass translation={%Corex.Combobox.Translation{}} for partial overrides. See Corex.Combobox.Translation for defaults.
Summary
Components
Renders a combobox component.
API
Set selected value(s) from a control (phx-click). Pass a list, comma-separated string, or single value (normalized like the component).
Set selection from handle_event. Pushes combobox_set_value.
Components
Renders a combobox component.
Attributes
id(:string) - The id of the combobox, useful for API to identify the combobox.items(:list) - Items fromCorex.List.new/1(or maps with :label and optional :value). Defaults to[].value(:any) - Initial selected item values (list of strings or a single string); not updated by LiveView after mount. Defaults tonil.on_open_change(:string) - The server event name to trigger on open change. Defaults tonil.on_open_change_client(:string) - The client event name to trigger on open change. Defaults tonil.disabled(:boolean) - Whether the combobox is disabled. Defaults tofalse.translation(Corex.Combobox.Translation) - Override translatable strings. Defaults tonil.always_submit_on_enter(:boolean) - Whether to always submit on enter. Defaults tofalse.auto_focus(:boolean) - Whether to auto focus the combobox. Defaults tofalse.close_on_select(:boolean) - Whether to close the combobox on select. Defaults totrue.dir(:string) - The direction of the combobox. When nil, derived from document (html lang + config :rtl_locales). Defaults tonil. Must be one ofnil,"ltr", or"rtl".orientation(:string) - Layout orientation for the combobox. Defaults to"vertical". Must be one of"horizontal", or"vertical".input_behavior(:string) - The input behavior of the combobox. Defaults to"autohighlight".loop_focus(:boolean) - Whether to loop focus the combobox. Defaults tofalse.multiple(:boolean) - Whether to allow multiple selection. Defaults tofalse.invalid(:boolean) - Whether the combobox is invalid. Defaults tofalse.name(:string) - The name of the combobox.form(:string) - The id of the form of the combobox.read_only(:boolean) - Whether the combobox is read only. Defaults tofalse.required(:boolean) - Whether the combobox is required. Defaults tofalse.filter(:boolean) - When true, filter options client-side by input value. Set to false when using on_input_value_change for server-side filtering. Defaults totrue.on_input_value_change(:string) - The server event name to trigger on input value change. Defaults tonil.on_input_value_change_client(:string) - The client event name to trigger on input value change. Defaults tonil.on_value_change(:string) - The server event name to trigger on value change. Defaults tonil.on_value_change_client(:string) - The client event name to trigger on value change. Defaults tonil.redirect(:boolean) - When true, selecting a value triggers redirect-on-select. Each item picks the navigation kind via:redirect(:href(default) |:patch|:navigate|false). Items may also set:to(overrides the destination) and:new_tab(opens in a new tab). When true, the client runs single-select in Zag even ifmultipleis set on this component.Defaults to
false.positioning(:map) - The positioning of the combobox. 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: true, offset: nil}.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)
empty- Content when there are no results. When omitted, translation.empty is used. Accepts attributes:class(:string)
trigger(required) - The trigger button content. Accepts attributes:class(:string)
clear_trigger- The clear 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)
API
Set selected value(s) from a control (phx-click). Pass a list, comma-separated string, or single value (normalized like the component).
<.action phx-click={Corex.Combobox.set_value("my-combobox", "bel")}>Belgium</.action>
<.combobox
id="my-combobox"
class="combobox"
translation={%Corex.Combobox.Translation{placeholder: "Country", empty: "None"}}
items={Corex.List.new([
%{label: "Belgium", value: "bel"},
%{label: "Germany", value: "deu"}
])}
>
<:trigger><.heroicon name="hero-chevron-down" /></:trigger>
</.combobox>document.getElementById("my-combobox")?.dispatchEvent(
new CustomEvent("corex:combobox:set-value", {
bubbles: false,
detail: { value: ["bel"] },
})
);
Set selection from handle_event. Pushes combobox_set_value.
<.action phx-click="pick_bel" phx-value-value="bel">Belgium</.action>
<.combobox
id="my-combobox"
class="combobox"
translation={%Corex.Combobox.Translation{placeholder: "Country", empty: "None"}}
items={Corex.List.new([
%{label: "Belgium", value: "bel"},
%{label: "Germany", value: "deu"}
])}
>
<:trigger><.heroicon name="hero-chevron-down" /></:trigger>
</.combobox>def handle_event("pick_bel", %{"value" => v}, socket) do
{:noreply, Corex.Combobox.set_value(socket, "my-combobox", v)}
end