PhoenixKitCatalogue.Web.Components.Browse (PhoenixKitCatalogue v0.19.1)

Copy Markdown View Source

Embeddable, selection-agnostic building blocks for browsing catalogue items — the pieces ItemSelectorModal is assembled from, exposed so a host LiveView can compose its own browse surface (a storefront section, a picker, a read-only category wall) without copying markup.

Everything here is a pure function component: state in, events out. Each interactive component takes a target (phx-target) so it works inside a LiveComponent as well as straight in a LiveView — leave it nil and events go to the host LV. Event names are fixed (documented per component) so one handle_event/3 vocabulary serves every embedding.

The data these render is a presented item — a plain map produced by present_items/2, which resolves translations and the featured-photo URL once per fetch rather than on every render:

items
|> Browse.present_items(locale)
# => [%{uuid: "…", name: "…", sku: "…", price: %Decimal{}|nil,
#       unit: "piece", photo_url: "/…/medium/…"|nil,
#       manufacturer: "…"|nil, default_qty: %Decimal{1}}]

Pair them with PhoenixKitCatalogue.Catalogue.BrowseState for the fetch/paging state machine; the moduledoc there shows the loop.

Summary

Functions

Horizontally scrollable category filter chips: "All" plus one per category. Dispatches browse_category with phx-value-uuid ("" for All).

Signed URL for an item's featured photo (medium variant), or nil. Signing is pure computation — no Storage roundtrip — so this is safe per item; it lives here so every surface resolves photos one way.

Formats a Decimal price for the card/tray. Bare number, no currency symbol — the same convention as the module's item table (format_price in Web.Components): which currency a price is in is host business the catalogue has never decided.

Placeholder cards with the exact geometry of item_card/1, so the grid does not reflow when real items arrive.

One product card: photo-forward (square, object-cover, lazy), then name / sku / price. Selection chrome is NOT built in — the picker layers it through the :footer slot and the selected ring, so a plain browse embedding renders the same card with neither.

The responsive card grid. Cards come in through the default slot so the caller decides what a card is — this component owns only the layout.

Denormalizes schema items into presented maps: translated name, signed featured-photo URL, selling price (Catalogue.item_pricing/1's final_price, matching ItemPicker), and a starting quantity of 1.

Quantity stepper: minus / text input / plus. The input commits on blur or Enter (qty_commit with %{"uuid" =>, "value" =>}) — never on keystroke, so typing "2." on the way to "2.5" is not fought. The buttons dispatch qty_dec / qty_inc immediately.

Functions

category_chips(assigns)

Horizontally scrollable category filter chips: "All" plus one per category. Dispatches browse_category with phx-value-uuid ("" for All).

Attributes

  • id (:string) (required)
  • categories (:list) (required) - [%{uuid:, name:}].
  • active_uuid (:string) - Defaults to nil.
  • target (:any) - Defaults to nil.

format_price(d)

Formats a Decimal price for the card/tray. Bare number, no currency symbol — the same convention as the module's item table (format_price in Web.Components): which currency a price is in is host business the catalogue has never decided.

grid_skeleton(assigns)

Placeholder cards with the exact geometry of item_card/1, so the grid does not reflow when real items arrive.

Attributes

  • id (:string) (required)
  • count (:integer) - Defaults to 8.

item_card(assigns)

One product card: photo-forward (square, object-cover, lazy), then name / sku / price. Selection chrome is NOT built in — the picker layers it through the :footer slot and the selected ring, so a plain browse embedding renders the same card with neither.

Dispatches card_click with phx-value-uuid when clickable.

Attributes

  • id (:string) (required)
  • item (:map) (required) - a presented item (see present_items/2).
  • selected (:boolean) - Defaults to false.
  • clickable (:boolean) - Defaults to true.
  • show_price (:boolean) - Defaults to true.
  • show_sku (:boolean) - Defaults to true.
  • target (:any) - Defaults to nil.

Slots

  • footer

item_grid(assigns)

The responsive card grid. Cards come in through the default slot so the caller decides what a card is — this component owns only the layout.

Attributes

  • id (:string) (required)
  • class (:string) - Defaults to nil.

Slots

  • inner_block (required)

present_items(items, locale)

Denormalizes schema items into presented maps: translated name, signed featured-photo URL, selling price (Catalogue.item_pricing/1's final_price, matching ItemPicker), and a starting quantity of 1.

Item.default_value is the smart-catalogue fee fallback (percent/flat), not a pick quantity — do not use it as a stepper default.

Runs once per fetched page — never call translation or URL helpers from a template; a quantity keystroke re-renders every card.

qty_stepper(assigns)

Quantity stepper: minus / text input / plus. The input commits on blur or Enter (qty_commit with %{"uuid" =>, "value" =>}) — never on keystroke, so typing "2." on the way to "2.5" is not fought. The buttons dispatch qty_dec / qty_inc immediately.

Integer mode is precision: 0 (the default); a decimal item is the same component with precision > 0 and a unit suffix — no redesign, which is the point. All limits are re-enforced server-side; these attrs only shape the keyboard.

Attributes

  • id (:string) (required)
  • uuid (:string) (required)
  • qty (:string) (required) - display string, already formatted.
  • unit (:string) - Defaults to nil.
  • precision (:integer) - Defaults to 0.
  • target (:any) - Defaults to nil.
  • size (:string) - Defaults to "sm". Must be one of "xs", or "sm".