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.x] Fix layout shift on thumbnails & quantity #710

Merged
merged 14 commits into from
Jan 29, 2025
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
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"
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-if="images.length && zoomed" class="fixed inset-0 bg-white cursor-zoom-out flex z-popup" v-cloak>
<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>
27 changes: 16 additions & 11 deletions resources/views/product/partials/gallery/slider.blade.php
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
<div class="relative" v-if="images.length">
<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 should always be above v-bind:src --}}
src="/storage/{{ config('rapidez.store') }}/resizes/400/magento/catalog/product/{{ Arr::first($product->images) }}.webp"
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-on:click="change(active-1)" class="z-10 top-1/2 left-3 -translate-y-1/2 absolute" aria-label="@lang('Prev')" v-cloak>
<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="z-10 top-1/2 right-3 -translate-y-1/2 absolute" 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 class="h-96 rounded" v-cloak />
81 changes: 44 additions & 37 deletions resources/views/product/partials/gallery/thumbnails.blade.php
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
@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'] }}
{{--
This components initializes with data from PHP to avoid layout shifts.
When Vue is loaded it takes over and all `v-cloak`'s show up.
With `$breakpoints` you can control the amount of images.
--}}

@php($breakpoints = ['xl' => 7, 'lg' => 5, 'md' => 7, 'sm' => 5, 'xs' => 3])

<div class="mt-3 flex gap-2">
@for ($imageId = 0; $imageId < max($breakpoints); $imageId++)
<button
@attributes([
'v-cloak' => !($imageId < count($product->images) && count($product->images) > 1),
'v-if' => $imageId . ' < images.length && images.length > 1',
])
@class([
'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',
'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'],
])
v-bind:class="{
'outline outline-1 border-primary': active === {{ $imageId }},
'!outline-transparent !outline-0 !border-border': active !== {{ $imageId }},
}"
v-on:click="change({{ $imageId }})"
>
<span class="size-9 flex items-center justify-center rounded-full shadow-lg shadow-default bg-white text-sm font-bold text">
+@{{ images.length - (imageId + 1) }}
</span>
</span>
</button>
<img
{{-- src should always be above v-bind:src --}}
@if ($imageId < count($product->images))
src="/storage/{{ config('rapidez.store') }}/resizes/80x80/magento/catalog/product/{{ $product->images[$imageId] }}.webp"
@endif
v-bind:src="'/storage/{{ config('rapidez.store') }}/resizes/80x80/magento/catalog/product/' + images[{{ $imageId }}] + '.webp'"
alt="{{ $product->name }}"
class="block max-h-full w-auto object-contain"
width="80"
height="80"
/>

{{-- Only include the showMore if it's actually possible to be visible --}}
@includeWhen(in_array($imageId + 1, $breakpoints), 'rapidez::product.partials.gallery.thumbnails.show-more')
</button>
Jade-GG marked this conversation as resolved.
Show resolved Hide resolved
@endfor
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{{-- This component shows an overlay on the last shown thumbnail if there are more thumbnails after it. --}}

<div @attributes([
'v-cloak' => !(count($product->images) - $imageId - 1),
'v-if' => 'images.length - ' . $imageId . ' - 1',
])>
<span
@class([
'absolute inset-0 hidden items-center justify-center bg-black/20',
'xl:flex' => $imageId + 1 === $breakpoints['xl'],
'lg:max-xl:flex' => $imageId + 1 === $breakpoints['lg'],
'md:max-lg:flex' => $imageId + 1 === $breakpoints['md'],
'sm:max-md:flex' => $imageId + 1 === $breakpoints['sm'],
'max-sm:flex' => $imageId + 1 === $breakpoints['xs'],
])
v-on:click="toggleZoom"
>
<span
class="size-9 flex items-center justify-center rounded-full bg-white text-sm font-bold text shadow-lg"
v-text="'+' + (images.length - {{ $imageId }} - 1)"
>
+{{ count($product->images) - $imageId - 1 }}
</span>
</span>
</div>
17 changes: 2 additions & 15 deletions resources/views/product/partials/images.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,8 @@
@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')
Expand All @@ -28,4 +15,4 @@ class="max-h-full w-full object-contain"
@include('rapidez::product.partials.gallery.popup')
</div>
</images>
</div>
</div>
Loading