add mobile swipe for product card images and fix dev asset caching
Product cards now use CSS scroll-snap on touch devices (mobile) for swiping between images, with dot indicators and a JS hook for active state. Desktop keeps the existing hover crossfade via @media (hover: hover). Dots use size differentiation (WCAG 2.2 AA compliant) with outline rings for contrast on any background. Also fixes: no-image placeholder (SVG icon instead of broken img), unnecessary wrapper div for single-image cards, and dev static asset caching (was immutable for all envs, now only prod). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -123,25 +123,51 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
||||
attr :show_delivery_text, :boolean, required: true
|
||||
|
||||
defp product_card_inner(assigns) do
|
||||
assigns =
|
||||
assign(
|
||||
assigns,
|
||||
:has_hover_image,
|
||||
assigns.theme_settings.hover_image && assigns.product[:hover_image_url]
|
||||
)
|
||||
|
||||
~H"""
|
||||
<div class={image_container_classes(@variant)}>
|
||||
<%= if @show_badges do %>
|
||||
<.product_badge product={@product} />
|
||||
<% end %>
|
||||
<.product_card_image
|
||||
product={@product}
|
||||
variant={@variant}
|
||||
priority={@priority}
|
||||
class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
|
||||
/>
|
||||
<%= if @theme_settings.hover_image && @product[:hover_image_url] do %>
|
||||
<%= if @has_hover_image do %>
|
||||
<div
|
||||
id={"product-image-scroll-#{@product[:id] || @product.name}"}
|
||||
class="product-image-scroll"
|
||||
phx-hook="ProductImageScroll"
|
||||
>
|
||||
<.product_card_image
|
||||
product={@product}
|
||||
variant={@variant}
|
||||
priority={@priority}
|
||||
class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
|
||||
/>
|
||||
<.product_card_image
|
||||
product={@product}
|
||||
variant={@variant}
|
||||
image_key={:hover}
|
||||
class="product-image-hover w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<% else %>
|
||||
<.product_card_image
|
||||
product={@product}
|
||||
variant={@variant}
|
||||
image_key={:hover}
|
||||
class="product-image-hover w-full h-full object-cover"
|
||||
priority={@priority}
|
||||
class="product-image-primary w-full h-full object-cover"
|
||||
/>
|
||||
<% end %>
|
||||
<%= if @has_hover_image do %>
|
||||
<div class="product-image-dots" aria-hidden="true">
|
||||
<span class="product-image-dot product-image-dot-active"></span>
|
||||
<span class="product-image-dot"></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class={content_padding_class(@variant)}>
|
||||
<%= if @show_category && @product[:category] do %>
|
||||
@@ -201,27 +227,50 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
||||
|> assign(:source_width, assigns.product[source_width_field])
|
||||
|
||||
~H"""
|
||||
<%= if @source_width do %>
|
||||
<.responsive_image
|
||||
src={@src}
|
||||
alt={@product.name}
|
||||
source_width={@source_width}
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
|
||||
class={@class}
|
||||
width={600}
|
||||
height={600}
|
||||
priority={@priority}
|
||||
/>
|
||||
<% else %>
|
||||
<img
|
||||
src={@src}
|
||||
alt={@product.name}
|
||||
width="600"
|
||||
height="600"
|
||||
loading={if @priority, do: nil, else: "lazy"}
|
||||
decoding={if @priority, do: nil, else: "async"}
|
||||
class={@class}
|
||||
/>
|
||||
<%= cond do %>
|
||||
<% is_nil(@src) -> %>
|
||||
<div
|
||||
class={[@class, "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>
|
||||
<% @source_width -> %>
|
||||
<.responsive_image
|
||||
src={@src}
|
||||
alt={@product.name}
|
||||
source_width={@source_width}
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
|
||||
class={@class}
|
||||
width={600}
|
||||
height={600}
|
||||
priority={@priority}
|
||||
/>
|
||||
<% true -> %>
|
||||
<img
|
||||
src={@src}
|
||||
alt={@product.name}
|
||||
width="600"
|
||||
height="600"
|
||||
loading={if @priority, do: nil, else: "lazy"}
|
||||
decoding={if @priority, do: nil, else: "async"}
|
||||
class={@class}
|
||||
/>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user