A read-only preview card for any resource that has photos/files and a handful of display fields — the catalogue product card, generalised. Opened from a picker thumbnail or a list's featured-image thumb, and potentially shown to a CLIENT, not just admins, so it has to stand on its own.
preview_card/1 renders a <.modal> whose media area is ONE continuous
swipeable carousel: images first, then attached files (a PDF renders
inline, any other file as a tile with an Open action) — swipe through the
images and just keep going into the files. Below it: the resource's
filled scalar fields and a compact file list for saving. Slide switching
is entirely client-side (scroll-snap); the only server event left is the
close.
The component is pure render: every DB-backed value (images, files,
fields, title) is resolved by the caller, so it stays testable
without a database. preview_card_body/1 is the same content without
the modal shell (the "notpopup" form).
Usage
The catalogue item card:
<PreviewCard.preview_card
id={@id}
show={@card_open}
title={@card_name}
images={ProductCard.resolve_images(@item)}
files={ProductCard.resolve_files(@item)}
fields={ProductCard.build_fields(@item, @locale)}
target={@myself}
on_close="card_close"
/>An Andi sub-order's featured-image card:
<PreviewCard.preview_card
id="sub-order-card"
show={true}
title={SubOrderCard.build(@sub_order, @order).name}
images={@sub_order_card.images}
files={@sub_order_card.files}
fields={@sub_order_card.fields}
target={nil}
on_close="close_sub_order_card"
/>
Summary
Functions
Renders the preview card modal. Pure: every DB-backed value (images,
files, fields, title) is resolved by the caller and passed in.
The card's content without the modal shell — the "notpopup" form, for
embedding the same preview inline (a detail pane, a future product
page). Same attrs as preview_card/1 minus the modal ones — and
minus :target: the body renders no event of its own (slide
switching is client-side, the close button lives in the modal's
action row), so an inline embedder has nothing to point at.
Functions
Renders the preview card modal. Pure: every DB-backed value (images,
files, fields, title) is resolved by the caller and passed in.
Attrs:
:id(required) — used to derive the modal's DOM id.:show(required) — whether the modal is open.:target(required,nilallowed) — the@myselfof the LiveComponent that handles the close event, ornilwhen the host is a LiveView itself.:title— card title; falls back to gettext "Preview".:images— ordered list of%{uuid, name}(main image first).:fields— list of{label, value}for the already-filtered, non-empty fields.:files— ordered list of%{uuid, name, size, pdf?}.:on_close— event pushed to@targeton close (default"preview_card_close").:max_width— modal width class, forwarded to<.modal>(default"3xl").
Attributes
id(:string) (required)show(:boolean) (required)target(:any) (required)title(:string) - Defaults tonil.images(:list) - Defaults to[].fields(:list) - Defaults to[].files(:list) - Defaults to[].on_close(:string) - Defaults to"preview_card_close".max_width(:string) - Defaults to"3xl".
Slots
extra_actions- rendered in the modal's action row before Close — a host puts a mode-aware Add/quantity control or similar here.
The card's content without the modal shell — the "notpopup" form, for
embedding the same preview inline (a detail pane, a future product
page). Same attrs as preview_card/1 minus the modal ones — and
minus :target: the body renders no event of its own (slide
switching is client-side, the close button lives in the modal's
action row), so an inline embedder has nothing to point at.
The media area is ONE continuous swipeable carousel: images first, then
the attached files (a PDF renders inline, any other file as a tile) — the
client swipes through the images and just keeps going into the files.
Scroll-snap (daisyUI carousel) drives it entirely client-side: native
swipe on touch, arrow buttons on desktop, no server round-trip per slide.
Attributes
title(:string) - Defaults tonil.images(:list) - Defaults to[].fields(:list) - Defaults to[].files(:list) - Defaults to[].