Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3] Fix layout shift on thumbnails, quantity #675

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions resources/views/components/quantity.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<quantity-select v-slot="qtySelect" {{ $attributes }}>
<div class="flex items-center justify-center border rounded bg-white h-12 self-start">
<button
disabled
Roene-JustBetter marked this conversation as resolved.
Show resolved Hide resolved
v-on:click.prevent="qtySelect.decrease"
v-bind:disabled="!qtySelect.decreasable"
aria-label="@lang('Decrease')"
Expand All @@ -17,6 +18,7 @@ class="shrink-0 pl-2.5 text disabled:cursor-not-allowed disabled:opacity-50"
name="qty"
type="number"
dusk="qty"
value="1"
Roene-JustBetter marked this conversation as resolved.
Show resolved Hide resolved
class="outline-0 ring-0 border-none w-12 bg-transparent font-medium text-center px-0 sm:text-base focus:ring-transparent"
aria-label="@lang('Quantity')"
{{ $attributes }}
Expand Down
4 changes: 2 additions & 2 deletions resources/views/product/partials/gallery/popup.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div v-if="images.length && zoomed" class="fixed inset-0 bg-white cursor-zoom-out flex z-popup">
<div v-cloak v-if="images.length && zoomed" class="fixed inset-0 bg-white cursor-zoom-out flex z-popup">
<div class="flex flex-1 items-center justify-center" v-on:click.prevent="toggleZoom">
<img
:src="config.media_url + '/catalog/product' + images[active]"
Expand All @@ -16,4 +16,4 @@ class="object-contain max-h-full mx-auto block"
<button class="z-popup-actions top-1/2 right-3 -translate-y-1/2 absolute" v-if="active != images.length-1" v-on:click="change(active+1)" aria-label="@lang('Next')">
<x-heroicon-o-chevron-right class="size-8 text-muted" />
</button>
</div>
</div>
28 changes: 16 additions & 12 deletions resources/views/product/partials/gallery/slider.blade.php
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
<div class="relative" v-if="images.length">
<div v-if="images.length" class="relative">
<a
:href="config.media_url + '/catalog/product' + images[active]"
class="flex items-center justify-center border rounded p-5 h-[440px] cursor-zoom-in"
v-on:click.prevent="toggleZoom"
v-bind:href="config.media_url + '/catalog/product' + images[active]"
class="flex h-[440px] cursor-zoom-in items-center justify-center rounded border p-5"
>
<img
:src="'/storage/{{ config('rapidez.store') }}/resizes/400/magento/catalog/product' + images[active] + '.webp'"
src="/storage/{{ config('rapidez.store') }}/resizes/400/magento/catalog/product/{{ Arr::first($product->images) }}.webp"
{{-- src should always be above v-bind:src --}}
v-bind:src="'/storage/{{ config('rapidez.store') }}/resizes/400/magento/catalog/product' + images[active] + '.webp'"
alt="{{ $product->name }}"
class="object-contain max-h-full"
class="max-h-full object-contain"
width="400"
height="400"
/>
</a>
<button class="z-10 top-1/2 left-3 -translate-y-1/2 absolute" v-if="active" v-on:click="change(active-1)" aria-label="@lang('Prev')">
<x-heroicon-o-chevron-left class="size-8 text-inactive" />
</button>
<button class="z-10 top-1/2 right-3 -translate-y-1/2 absolute" v-if="active != images.length-1" v-on:click="change(active+1)" aria-label="@lang('Next')">
<x-heroicon-o-chevron-right class="size-8 text-inactive" />
</button>
@if (count($product->images) > 1)
<button v-if="active" v-cloak v-on:click="change(active-1)" class="absolute left-3 top-1/2 z-10 -translate-y-1/2" aria-label="@lang('Prev')">
<x-heroicon-o-chevron-left class="size-8 text-inactive" />
</button>
<button v-if="active != images.length-1" v-on:click="change(active+1)" class="absolute right-3 top-1/2 z-10 -translate-y-1/2" aria-label="@lang('Next')">
<x-heroicon-o-chevron-right class="size-8 text-inactive" />
</button>
@endif
</div>

<x-rapidez::no-image v-else class="h-96 rounded" />
<x-rapidez::no-image v-else v-cloak class="h-96 rounded" />
124 changes: 87 additions & 37 deletions resources/views/product/partials/gallery/thumbnails.blade.php
Copy link
Member

@Roene-JustBetter Roene-JustBetter Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer one version instead of 2 versions if that is possible.

Original file line number Diff line number Diff line change
@@ -1,40 +1,90 @@
@php($breakpoints = ['xl' => 7, 'lg' => 5, 'md' => 4, 'sm' => 3, 'xs' => 4])
<div v-if="images.length > 1" class="flex mt-3 gap-2">
<button
v-for="(image, imageId) in images.slice(0, {{ max($breakpoints) }})"
class="flex items-center justify-center bg-white border rounded p-1.5 aspect-square max-w-24 flex-1 transition-all outline-transparent overflow-hidden relative"
:class="{
'outline outline-1 !outline-primary border-primary': active == imageId,
'xl:hidden': imageId > {{ $breakpoints['xl'] }},
'lg:max-xl:hidden': imageId > {{ $breakpoints['lg'] }},
'md:max-lg:hidden': imageId > {{ $breakpoints['md'] }},
'sm:max-md:hidden': imageId > {{ $breakpoints['sm'] }},
'max-sm:hidden': imageId > {{ $breakpoints['xs'] }}
}"
@click="change(imageId)"
>
<img
:src="'/storage/{{ config('rapidez.store') }}/resizes/80x80/magento/catalog/product' + image + '.webp'"
alt="{{ $product->name }}"
class="object-contain block w-auto max-h-full"
width="80"
height="80"
/>
<span
v-if="(imageId + 1) < images.length"
v-on:click="toggleZoom()"
class="absolute inset-0 items-center justify-center bg-black/20 hidden"
:class="{
'xl:flex': imageId === {{ $breakpoints['xl'] }},
'lg:max-xl:flex': imageId === {{ $breakpoints['lg'] }},
'md:max-lg:flex': imageId === {{ $breakpoints['md'] }},
'sm:max-md:flex': imageId === {{ $breakpoints['sm'] }},
'max-sm:flex': imageId === {{ $breakpoints['xs'] }}
{{--
We have two versions of the thumbnails:
1. PHP version with v-if="false": Shows instantly during page load but is hidden when Vue loads.
2. Vue version with v-cloak: Takes over when Vue is loaded.

This prevents layout shifting.
--}}

@php
$breakpoints = ['xl' => 7, 'lg' => 5, 'md' => 4, 'sm' => 3, 'xs' => 4];
$baseClasses = 'max-w-24 relative flex aspect-square flex-1 items-center justify-center overflow-hidden rounded border bg-white p-1.5 outline-primary transition-all';
@endphp

<div class="mt-3 flex gap-2">
@foreach ($product->images as $imageId => $image)
@if ($imageId < max($breakpoints))
<button v-if="false" @class([
$baseClasses,
'outline outline-1 border-primary' => $imageId == 0,
'xl:hidden' => $imageId > $breakpoints['xl'],
'lg:max-xl:hidden' => $imageId > $breakpoints['lg'],
'md:max-lg:hidden' => $imageId > $breakpoints['md'],
'sm:max-md:hidden' => $imageId > $breakpoints['sm'],
'max-sm:hidden' => $imageId > $breakpoints['xs'],
])>
<img
src="/storage/{{ config('rapidez.store') }}/resizes/80x80/magento/catalog/product/{{ $image }}.webp"
alt="{{ $product->name }}"
class="block max-h-full w-auto object-contain"
width="80"
height="80"
/>
@if ($imageId < count($product->images) - 1)
<span @class([
'absolute inset-0 hidden items-center justify-center bg-black/20',
'xl:flex' => $imageId === $breakpoints['xl'],
'lg:max-xl:flex' => $imageId === $breakpoints['lg'],
'md:max-lg:flex' => $imageId === $breakpoints['md'],
'sm:max-md:flex' => $imageId === $breakpoints['sm'],
'max-sm:flex' => $imageId === $breakpoints['xs'],
])>
<span class="size-9 flex items-center justify-center rounded-full bg-white text-sm font-bold text shadow-lg">
+{{ count($product->images) - $imageId - 1 }}
</span>
</span>
@endif
</button>
@endif
@endforeach

<template v-for="(image, imageId) in images.slice(0, {{ max($breakpoints) }})" v-cloak>
<button
v-on:click="change(imageId)"
v-bind:class="{
'outline outline-1 border-primary': active == imageId,
'xl:hidden': imageId > {{ $breakpoints['xl'] }},
'lg:max-xl:hidden': imageId > {{ $breakpoints['lg'] }},
'md:max-lg:hidden': imageId > {{ $breakpoints['md'] }},
'sm:max-md:hidden': imageId > {{ $breakpoints['sm'] }},
'max-sm:hidden': imageId > {{ $breakpoints['xs'] }},
}"
class="{{ $baseClasses }}"
>
<span class="size-9 flex items-center justify-center rounded-full shadow-lg bg-white text-sm font-bold text">
+@{{ images.length - (imageId + 1) }}
</span>
</span>
</button>
<img
v-bind:src="'/storage/{{ config('rapidez.store') }}/resizes/80x80/magento/catalog/product' + image + '.webp'"
alt="{{ $product->name }}"
class="block max-h-full w-auto object-contain"
width="80"
height="80"
/>
<template v-if="imageId < images.length - 1">
<span
v-on:click="toggleZoom()"
v-bind:class="{
'xl:flex': imageId === {{ $breakpoints['xl'] }},
'lg:max-xl:flex': imageId === {{ $breakpoints['lg'] }},
'md:max-lg:flex': imageId === {{ $breakpoints['md'] }},
'sm:max-md:flex': imageId === {{ $breakpoints['sm'] }},
'max-sm:flex': imageId === {{ $breakpoints['xs'] }}
}"
class="absolute inset-0 hidden items-center justify-center bg-black/20"
>
<span class="size-9 flex items-center justify-center rounded-full bg-white text-sm font-bold text shadow-lg">
+@{{ images.length - imageId - 1 }}
</span>
</span>
</template>
</button>
</template>
</div>
20 changes: 3 additions & 17 deletions resources/views/product/partials/images.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,14 @@
@include('rapidez::wishlist.button')
</div>
@endif
@if (count($product->images))
<div class="absolute inset-0 flex">
<div class="h-[440px] items-center justify-center rounded p-5 border sticky top-5 w-full">
<img
class="max-h-full w-full object-contain"
src="/storage/{{ config('rapidez.store') }}/resizes/400/magento/catalog/product{{ $product->images[0] }}.webp"
alt="{{ $product->name }}"
width="400"
height="400"
/>
</div>
</div>
@endif

<images v-cloak>
<images>
<div class="flex-1" slot-scope="{ images, active, zoomed, toggleZoom, change }">
<div class="sticky top-5 bg-white">
@include('rapidez::product.partials.gallery.slider')
@include('rapidez::product.partials.gallery.thumbnails')
@includeWhen(count($product->images) > 1, 'rapidez::product.partials.gallery.thumbnails')
</div>

@include('rapidez::product.partials.gallery.popup')
</div>
</images>
</div>
</div>
Loading