replace PDP image gallery with scroll-snap carousel
Mobile: swipeable carousel with dot indicators, no lightbox trigger. Desktop: carousel with thumbnail grid, prev/next arrows, click to open existing lightbox. Keeps all lightbox appearance and behaviour. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1134,52 +1134,136 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
||||
|
||||
def product_gallery(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<div
|
||||
class="pdp-main-image-container aspect-square bg-gray-200 mb-4 overflow-hidden relative"
|
||||
style="border-radius: var(--t-radius-image);"
|
||||
phx-click={Phoenix.LiveView.JS.exec("data-show", to: "##{@id_prefix}-lightbox")}
|
||||
>
|
||||
<img
|
||||
id={"#{@id_prefix}-main-image"}
|
||||
src={List.first(@images)}
|
||||
alt={@product_name}
|
||||
width="600"
|
||||
height="600"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<div class="absolute inset-0 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity bg-black/10">
|
||||
<div class="w-12 h-12 bg-white/90 rounded-full flex items-center justify-center shadow-lg">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7"
|
||||
/>
|
||||
</svg>
|
||||
<div class="pdp-gallery">
|
||||
<%!-- Image area (relative container for dots + desktop nav) --%>
|
||||
<div class="relative" style="border-radius: var(--t-radius-image); overflow: hidden;">
|
||||
<%!-- Scroll-snap carousel (2+ images) or single image --%>
|
||||
<%= if length(@images) > 1 do %>
|
||||
<div
|
||||
id={"#{@id_prefix}-carousel"}
|
||||
class="pdp-gallery-carousel"
|
||||
phx-hook="ProductImageScroll"
|
||||
role="region"
|
||||
aria-label="Product images"
|
||||
>
|
||||
<img
|
||||
:for={{url, idx} <- Enum.with_index(@images)}
|
||||
src={url}
|
||||
alt={"#{@product_name} — image #{idx + 1} of #{length(@images)}"}
|
||||
class="pdp-carousel-img"
|
||||
width="600"
|
||||
height="600"
|
||||
loading={if idx == 0, do: nil, else: "lazy"}
|
||||
decoding={if idx == 0, do: nil, else: "async"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<%= for {img_url, idx} <- Enum.with_index(@images) do %>
|
||||
|
||||
<%!-- Desktop: lightbox click area (transparent, above carousel) --%>
|
||||
<div
|
||||
class="pdp-lightbox-click"
|
||||
phx-click={Phoenix.LiveView.JS.exec("data-show", to: "##{@id_prefix}-lightbox")}
|
||||
/>
|
||||
|
||||
<%!-- Desktop: prev/next arrows (same chevrons as lightbox) --%>
|
||||
<button
|
||||
type="button"
|
||||
class={"aspect-square bg-gray-200 cursor-pointer overflow-hidden pdp-thumbnail#{if idx == 0, do: " pdp-thumbnail-active", else: ""}"}
|
||||
class="pdp-nav pdp-nav-prev"
|
||||
aria-label="Previous image"
|
||||
phx-click={Phoenix.LiveView.JS.dispatch("pdp:scroll-prev", to: "##{@id_prefix}-carousel")}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="pdp-nav pdp-nav-next"
|
||||
aria-label="Next image"
|
||||
phx-click={Phoenix.LiveView.JS.dispatch("pdp:scroll-next", to: "##{@id_prefix}-carousel")}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6" />
|
||||
</svg>
|
||||
</button>
|
||||
<% else %>
|
||||
<div class="pdp-gallery-single">
|
||||
<%= if @images == [] do %>
|
||||
<div
|
||||
class="w-full h-full flex items-center justify-center"
|
||||
style="color: var(--t-text-tertiary);"
|
||||
role="img"
|
||||
aria-label={@product_name}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-12 opacity-40"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0 0 22.5 18.75V5.25A2.25 2.25 0 0 0 20.25 3H3.75A2.25 2.25 0 0 0 1.5 5.25v13.5A2.25 2.25 0 0 0 3.75 21Zm16.5-13.5a1.125 1.125 0 1 1-2.25 0 1.125 1.125 0 0 1 2.25 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<% else %>
|
||||
<img
|
||||
src={List.first(@images)}
|
||||
alt={@product_name}
|
||||
width="600"
|
||||
height="600"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<div
|
||||
class="pdp-lightbox-click"
|
||||
phx-click={Phoenix.LiveView.JS.exec("data-show", to: "##{@id_prefix}-lightbox")}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%!-- Dot indicators (absolute over image, hidden on desktop) --%>
|
||||
<div
|
||||
:if={length(@images) > 1}
|
||||
class="product-image-dots"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span
|
||||
:for={{_, idx} <- Enum.with_index(@images)}
|
||||
class={["product-image-dot", idx == 0 && "product-image-dot-active"]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- Desktop: thumbnail grid --%>
|
||||
<div :if={length(@images) > 1} class="pdp-gallery-thumbs">
|
||||
<%= for {url, idx} <- Enum.with_index(@images) do %>
|
||||
<button
|
||||
type="button"
|
||||
class={"aspect-square bg-gray-200 overflow-hidden pdp-thumbnail#{if idx == 0, do: " pdp-thumbnail-active", else: ""}"}
|
||||
style="border-radius: var(--t-radius-image);"
|
||||
data-index={idx}
|
||||
aria-label={"View image #{idx + 1} of #{length(@images)}"}
|
||||
phx-click={
|
||||
Phoenix.LiveView.JS.set_attribute({"src", img_url}, to: "##{@id_prefix}-main-image")
|
||||
|> Phoenix.LiveView.JS.set_attribute({"data-current-index", to_string(idx)},
|
||||
Phoenix.LiveView.JS.dispatch("pdp:scroll-to",
|
||||
detail: %{index: idx},
|
||||
to: "##{@id_prefix}-carousel"
|
||||
)
|
||||
|> Phoenix.LiveView.JS.set_attribute(
|
||||
{"data-current-index", to_string(idx)},
|
||||
to: "##{@id_prefix}-lightbox"
|
||||
)
|
||||
|> Phoenix.LiveView.JS.remove_class("pdp-thumbnail-active", to: ".pdp-thumbnail")
|
||||
|> Phoenix.LiveView.JS.remove_class("pdp-thumbnail-active",
|
||||
to: ".pdp-thumbnail"
|
||||
)
|
||||
|> Phoenix.LiveView.JS.add_class("pdp-thumbnail-active")
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={img_url}
|
||||
alt={@product_name}
|
||||
src={url}
|
||||
alt=""
|
||||
width="150"
|
||||
height="150"
|
||||
loading="lazy"
|
||||
@@ -1189,7 +1273,12 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<.product_lightbox images={@images} product_name={@product_name} id_prefix={@id_prefix} />
|
||||
<.product_lightbox
|
||||
:if={@images != []}
|
||||
images={@images}
|
||||
product_name={@product_name}
|
||||
id_prefix={@id_prefix}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user