PhoenixKitCatalogue.Web.Components.CatalogueBrowse (PhoenixKitCatalogue v0.24.0)

Copy Markdown View Source

Embeddable catalogue browse surface: search, category chips, and the item grid or admin-look table as one drop-in LiveComponent — the same BrowseState + Browse.* stack ItemSelectorModal runs on, minus the selection chrome. Put a catalogue (or a scoped slice of one) on any logged-in page:

<.live_component
  module={PhoenixKitCatalogue.Web.Components.CatalogueBrowse}
  id="showroom"
  scope={%{catalogue_uuids: [@catalogue.uuid], statuses: ["active"]}}
/>

Views and columns (2026-08-30)

Two presentations over the same fetch, exactly like the modal: view: "card" (the default here — existing embeds keep their grid) and view: "table", the admin-look list. A toggle beside the search box switches them; the attr only sets the starting view.

columns is the same host contract the modal enforces (Browse.table_columns/0, unknown entries raise) and it is a GRANT: the cards read it too, so a column the host didn't grant can't reappear by flipping the view. Omitted, the default set applies minus what show_sku/show_prices opt out of, and minus :qty — selection chrome this surface doesn't have (a host that explicitly asks for :qty gets an empty cell).

The TABLE additionally starts without the modal's default-hidden pair (:sku, :breadcrumb): without the modal's Columns dropdown they'd render outright, and :breadcrumb beside the :category column shows the category twice per row. That is visibility, not grant — the cards keep their SKU line, which is what show_sku has always controlled. An explicit columns list is taken verbatim on both surfaces.

Messages to the host

One generic message, so a host writes a single clause and switches on the event:

handle_info({:catalogue_browse, %{id: id, event: :item_clicked, item: item}}, socket)

item is a presented map (Browse.present_items/2): uuid, translated name, sku, price, unit, photo_url. Cards/rows are clickable only when the host opts in with on_item_click: true — a purely decorative embedding never receives (or needs to handle) anything.

Scope

Identical semantics to ItemSelectorModal: fixed at mount, every fetch re-derived from it, category narrowing only within it — including the subtree expansion a parent-category scope needs (Browse.expand_scope/1, shared with the modal since 2026-08-30; this surface previously hid descendant chips and rejected narrowing to them). See PhoenixKitCatalogue.Catalogue.BrowseState.