# `PhoenixKitCatalogue.Web.Components.Browse`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/v0.19.1/lib/phoenix_kit_catalogue/web/components/browse.ex#L1)

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.

# `category_chips`

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`.

# `featured_photo_url`

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.

# `format_price`

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`

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`

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`

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`

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`

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"`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
