A select with a panel of our own, for when the native DevactionUI.Select is not enough: options with a
marker (an icon, an avatar, a swatch) and a line of description, groups, a search at the top of the panel,
several choices at once, a button that empties the choice and a row that creates what the search did not
find. For a short list with none of that, the native select is still the right one.
<Listbox.listbox
field={@form[:category_id]}
label="Category"
placeholder="Choose a category"
options={[
{"Work", [%{value: "dev", label: "Development", description: "Code, review and deploy"}]},
%{value: "fin", label: "Finance", keywords: "money invoice"}
]}
/>Options are maps %{value, label} with the optional description (a quiet line under the label),
keywords (more words the search finds it by, not shown) and disabled; a {label, options} tuple is a
group with a heading. The option_marker slot draws something before each label and gets the option as it
was given, so it may carry anything else (an icon, an avatar).
searchable puts a field at the top of the panel that filters while you type, ignoring case and accents,
by the label and the keywords; empty is what shows when nothing is left, with %{term} for the text
typed. With create ("Create “%{term}”") the last row offers to create the text typed when no option is
called exactly that; choosing it sends on_create to the LiveView (phx-target points it at a component)
with %{"id" => id, "term" => text}, and the LiveView adds the option and sets the value; create_hint
is a small text at the end of that row (the key that chooses it). search_clear_label names a button in the
search field that empties what was typed.
multiple keeps the panel open while choosing and sends a list (name[], an empty list when nothing is
chosen). The button says count_text ("%{count} chosen") or, without it, the labels joined by commas,
so it never changes height. indicator is :check (a mark at the end of the row, the default) or
:checkbox (a box at the start). prefix is a fixed text before the choice on the button (Compare to).
The panel is position: fixed, put by the hook under the button (position={:top} for above, align for
the side), so an ancestor with overflow: hidden does not cut it; it opens on the other side when the wanted
one has no room and shortens its list when neither has.
The keyboard is the ARIA pattern: ↓ ↑ on the button open it, ↑ ↓ Home End move, Enter (and Space without a
search) chooses, Esc closes and returns to the button, Tab leaves; without a search, typing letters jumps to
the option that starts with them. The value is in hidden inputs and the hook fires input on them, so the
form's phx-change hears every choice. The chosen value is the caller's, as in any LiveView field: a form
that re-renders should hold value (with phx-change).
field={@form[:category_id]} fills the id, the name, the value and the error (see
DevactionUI.FormField). The library knows no language: every word is an attribute.
Summary
Functions
A select whose panel the library draws: groups, descriptions, search, several choices, create.
Functions
@spec listbox(map()) :: Phoenix.LiveView.Rendered.t()
A select whose panel the library draws: groups, descriptions, search, several choices, create.
Attributes
id(:string) - required unless afieldgives it. Defaults tonil.name(:string) - required unless afieldgives it;[]is added whenmultiple. Defaults tonil.field(Phoenix.HTML.FormField) - a form field (@form[:category_id]): gives theid, thename, the value and the error. Defaults tonil.options(:list) (required) -%{value, label}maps (withdescription,keywordsanddisabledoptional) and{label, options}groups.value(:any) - the chosen value, or a list of them whenmultiple. Defaults tonil.label(:string) (required) - what is being chosen, for assistive technology.placeholder(:string) (required) - shown on the button when nothing is chosen.multiple(:boolean) - several choices; the panel stays open while choosing. Defaults tofalse.indicator(:atom) - how a chosen option is marked: a check at the end or a box at the start. Defaults to:check. Must be one of:check, or:checkbox.count_text(:string) - the button text of a multiple choice, with%{count}; the labels joined when nil. Defaults tonil.prefix(:string) - a fixed text on the button before the choice. Defaults tonil.searchable(:boolean) - a search field at the top of the panel. Defaults tofalse.search_placeholder(:string) - Defaults tonil.empty(:string) - shown when the search finds nothing;%{term}is the text typed. Defaults tonil.create(:string) - the text of the row that creates what was typed, with%{term}; needson_create. Defaults tonil.on_create(:string) - the event sent with%{"id" => id, "term" => text}when the create row is chosen. Defaults tonil.create_hint(:string) - a small text at the end of the create row, such as the key that chooses it (Enter). Defaults tonil.clear_label(:string) - the name of the button that empties the choice; none when nil. Defaults tonil.search_clear_label(:string) - the name of the button in the search field that empties what was typed; none when nil, drawn withclear_icon. Defaults tonil.size(:atom) - Defaults to:md. Must be one of:sm,:md, or:lg.position(:atom) - where the panel opens. Defaults to:bottom. Must be one of:bottom, or:top.align(:atom) - the side the panel lines up with. Defaults to:start. Must be one of:start, or:end.panel_class(:any) - classes of the panel, such as a width (w-72). Defaults tonil.disabled(:boolean) - Defaults tofalse.required(:boolean) - Defaults tofalse.show_label(:boolean) - drawslabelabove the field instead of leaving it for assistive technology only. Defaults tofalse.help(:string) - a line of explanation under the field. Defaults tonil.error(:string) - the message under the field; also marks it invalid. Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted. Supports all globals plus:
["phx-target"].
Slots
icon- an icon at the start of the button.chevron_icon- the arrow at the end of the button; turns over when open.search_icon- the magnifier in the search field.clear_icon- the icon of the clear button.check_icon- the mark of a chosen option (indicator={:check}) and in its box.create_icon- drawn before the text of the create row.option_marker- drawn before the label of each option; gets the option as given.