PhoenixKitCatalogue. Web. Components. ItemPicker
(PhoenixKitCatalogue v0.7.0)
Copy Markdown
View Source
Combobox LiveComponent for picking a single item from the catalogue via server-side search.
Drop one into any LiveView — typically many, one per row in a picker table. Each instance owns its own search state; the parent LV only reacts to two messages:
{:item_picker_select, id, %Item{}} # user chose an item
{:item_picker_clear, id} # user cleared the selectionAPI
<.item_picker
id="row-42-picker"
category_uuids={[category.uuid]}
selected_item={@chosen_item}
excluded_uuids={@already_used_uuids}
locale="en"
/>Attrs:
:id(required) — unique DOM/component id. The:item_picker_*messages echo this back so a parent with N pickers knows which fired.:category_uuids— scope search to these categories.nilor[]means "all categories + uncategorized" (matchesCatalogue.search_items/2).:catalogue_uuids— scope search to these catalogues. Composes with:category_uuids(AND).:include_descendants— whentrue(default),:category_uuidsis expanded through the V103 tree; passfalsefor literal set semantics.:only—:uncategorized_onlyrestricts results to items without a category;:categorized_onlyrestricts to items in some category;nil(default) is unrestricted. Forwards toCatalogue.search_items/2's:onlyopt.:selected_item— the%Item{}currently chosen (ornil). Drives the input text and thearia-selected/ primary-border styling in the dropdown.:excluded_uuids— items in this list are rendered dim +aria-disabledand cannot be clicked. Use for "already picked in another row" state.:locale(required) — locale string for translated display names ("en","es", etc.). Resolved viaCatalogue.get_translation/2.:placeholder— input placeholder. Defaults to "Search items…".:empty_query_limit— how many items to show when the query is empty (the "just focused" state). Defaults to10.:page_size— max results fetched per query. Defaults to20. When the unbounded count exceeds this the dropdown shows a "Type to refine…" sentinel row so the user knows there's more.:disabled— disables the input and hides the clear button.:format_price— 1-arity function taking an%Item{}(with:cataloguepreloaded — the search always does this) and returning a display string ornil. Defaults to a Decimal stringifier ofitem_pricing(item).final_price. Returnnilto omit the price column entirely.:show_unit— whentrue, renders the item's measurement unit (via:format_unit) as a small muted label next to the price in each dropdown row. Defaults tofalse(no unit) so existing consumers are unaffected.:format_unit— 1-arity function taking the item's unit string and returning a display label (""to omit). Only used when:show_unitistrue. Defaults to a built-in mapping of common abbreviations (piece→pc,set→set,pair→pair,sheet→sheet,m2→m²,running_meter→rm; unknown strings pass through). Supply your own to use a different unit vocabulary.:highlight_selected— whentrue(default), the input gets theinput-primaryborder while an item is selected. Passfalseto suppress that highlight. Default preserves existing behaviour.:initial_query— optional seed string for the search input. When provided (and nothing is selected and the user hasn't typed), the input is prefilled with this string and the dropdown opens with matching results on first render. Fires once; subsequent updates leave the query alone. Defaults tonil(no seeding).
Keyboard / a11y
Handled client-side by the colocated ItemPicker hook:
- ArrowDown / ArrowUp cycle through enabled options (announced via
aria-activedescendant; DOM focus stays on the input). - Home / End jump to first / last enabled option.
- Enter activates the focused option (simulates a click so the
normal
selectevent fires). - Escape closes the dropdown and keeps focus on the input.
- Clicking outside the picker closes it (
phx-click-away).
The dropdown is absolutely positioned and elevated with z-50; the
parent container must allow overflow (overflow: visible or just
don't set overflow: hidden on an ancestor that clips it).