PhoenixKitCatalogue.Web.Components.ItemPicker (PhoenixKitCatalogue v0.1.14)

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 selection

API

<.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. nil or [] means "all categories + uncategorized" (matches Catalogue.search_items/2).
  • :catalogue_uuids — scope search to these catalogues. Composes with :category_uuids (AND).
  • :include_descendants — when true (default), :category_uuids is expanded through the V103 tree; pass false for literal set semantics.
  • :only:uncategorized_only restricts results to items without a category; :categorized_only restricts to items in some category; nil (default) is unrestricted. Forwards to Catalogue.search_items/2's :only opt.
  • :selected_item — the %Item{} currently chosen (or nil). Drives the input text and the aria-selected / primary-border styling in the dropdown.
  • :excluded_uuids — items in this list are rendered dim + aria-disabled and cannot be clicked. Use for "already picked in another row" state.
  • :locale (required) — locale string for translated display names ("en", "es", etc.). Resolved via Catalogue.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 to 10.
  • :page_size — max results fetched per query. Defaults to 20. 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 :catalogue preloaded — the search always does this) and returning a display string or nil. Defaults to a Decimal stringifier of item_pricing(item).final_price. Return nil to omit the price column entirely.

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 select event 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).