Skip to content

Commit 35d93f2

Browse files
laugharnclaude
andauthored
PDP: aspect ratio prop on gallery; suppress OOS card hover slideshow (#248)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2080755 commit 35d93f2

6 files changed

Lines changed: 313 additions & 64 deletions

File tree

apps/template/components/product-card/components.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ function ProductCardImage({
101101
{fallbackTitle}
102102
</div>
103103
)}
104-
{hasSlideshow && <ProductCardSlideshow images={images} sizes={sizes} />}
104+
{hasSlideshow && !outOfStock && <ProductCardSlideshow images={images} sizes={sizes} />}
105105
{outOfStock && (
106106
<div className="absolute inset-0 bg-black/60 flex items-center justify-center">
107107
<span className="text-destructive-foreground font-medium text-xs px-2 py-1 bg-destructive rounded">
@@ -216,6 +216,7 @@ function ProductCardSkeleton({
216216
}
217217

218218
export {
219+
aspectRatioClasses,
219220
ProductCard,
220221
type ProductCardAspectRatio,
221222
ProductCardBadge,

apps/template/components/product-detail/product-detail-page.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Suspense } from "react";
22

3+
import {
4+
aspectRatioClasses,
5+
type ProductCardAspectRatio,
6+
} from "@/components/product-card/components";
37
import { ProductSchema } from "@/components/product-detail/schema";
48
import { RelatedProductsSection } from "@/components/product/related-products-section";
59
import { BreadcrumbSchema } from "@/components/schema/breadcrumb-schema";
@@ -10,6 +14,7 @@ import { Skeleton } from "@/components/ui/skeleton";
1014
import { siteConfig } from "@/lib/config";
1115
import type { Locale } from "@/lib/i18n";
1216
import type { ProductDetails } from "@/lib/types";
17+
import { cn } from "@/lib/utils";
1318

1419
import { ProductDetailSection } from "./product-detail-section";
1520

@@ -24,22 +29,21 @@ function ProductBreadcrumbSchema({ title, handle }: { title: string; handle: str
2429
);
2530
}
2631

27-
function ProductPageFallback() {
32+
function ProductPageFallback({ aspectRatio }: { aspectRatio: ProductCardAspectRatio }) {
33+
const tile = cn("w-full rounded-none", aspectRatioClasses);
2834
return (
2935
<Sections>
3036
<div className="grid gap-10 lg:grid-cols-10 lg:items-start lg:gap-5">
3137
<div className="lg:col-span-6">
32-
{/* Mobile: single full-bleed square + pagination space */}
3338
<div className="grid gap-5 lg:hidden -mx-5">
34-
<Skeleton className="aspect-square w-full rounded-none" />
39+
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
3540
<div className="h-1.5" />
3641
</div>
37-
{/* Desktop: 2×2 grid */}
3842
<div className="hidden lg:grid grid-cols-2 gap-2.5">
39-
<Skeleton className="aspect-square w-full rounded-none" />
40-
<Skeleton className="aspect-square w-full rounded-none" />
41-
<Skeleton className="aspect-square w-full rounded-none" />
42-
<Skeleton className="aspect-square w-full rounded-none" />
43+
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
44+
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
45+
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
46+
<Skeleton data-aspect-ratio={aspectRatio} className={tile} />
4347
</div>
4448
</div>
4549
<div className="grid gap-10 lg:sticky lg:top-20 lg:col-span-4">
@@ -55,10 +59,12 @@ async function ProductContent({
5559
productPromise,
5660
locale,
5761
variantIdPromise,
62+
aspectRatio,
5863
}: {
5964
productPromise: Promise<ProductDetails>;
6065
locale: Locale;
6166
variantIdPromise: Promise<string | undefined>;
67+
aspectRatio: ProductCardAspectRatio;
6268
}) {
6369
const product = await productPromise;
6470
const { handle, title } = product;
@@ -86,6 +92,7 @@ async function ProductContent({
8692
product={product}
8793
locale={locale}
8894
variantIdPromise={variantIdPromise}
95+
aspectRatio={aspectRatio}
8996
/>
9097
<RelatedProductsSection handle={handle} locale={locale} />
9198
</Sections>
@@ -97,19 +104,22 @@ export async function ProductDetailPage({
97104
productPromise,
98105
locale,
99106
variantIdPromise,
107+
aspectRatio = "square",
100108
}: {
101109
productPromise: Promise<ProductDetails>;
102110
locale: Locale;
103111
variantIdPromise: Promise<string | undefined>;
112+
aspectRatio?: ProductCardAspectRatio;
104113
}) {
105114
return (
106115
<Page className="pt-0">
107116
<Container className="bg-background">
108-
<Suspense fallback={<ProductPageFallback />}>
117+
<Suspense fallback={<ProductPageFallback aspectRatio={aspectRatio} />}>
109118
<ProductContent
110119
productPromise={productPromise}
111120
locale={locale}
112121
variantIdPromise={variantIdPromise}
122+
aspectRatio={aspectRatio}
113123
/>
114124
</Suspense>
115125
</Container>

apps/template/components/product-detail/product-detail-section.tsx

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { getTranslations } from "next-intl/server";
22
import { Suspense } from "react";
33

4+
import {
5+
aspectRatioClasses,
6+
type ProductCardAspectRatio,
7+
} from "@/components/product-card/components";
48
import { BuyButtons } from "@/components/product-detail/buy-buttons";
59
import {
610
ProductInfoDescription,
@@ -36,10 +40,12 @@ export async function ProductDetailSection({
3640
product,
3741
locale,
3842
variantIdPromise,
43+
aspectRatio = "square",
3944
}: {
4045
product: ProductDetails;
4146
locale: Locale;
4247
variantIdPromise: Promise<string | undefined>;
48+
aspectRatio?: ProductCardAspectRatio;
4349
}) {
4450
const { handle, title, featuredImage, images, videos, variants, options } = product;
4551

@@ -66,15 +72,28 @@ export async function ProductDetailSection({
6672
otherImages={getSharedImages(images, options, variants)}
6773
videos={videos}
6874
title={title}
75+
aspectRatio={aspectRatio}
6976
className="lg:col-span-6"
7077
desktopSlot={
7178
<Suspense
7279
fallback={
7380
<>
74-
<Skeleton className="aspect-square w-full rounded-none" />
75-
<Skeleton className="aspect-square w-full rounded-none" />
76-
<Skeleton className="aspect-square w-full rounded-none" />
77-
<Skeleton className="aspect-square w-full rounded-none" />
81+
<Skeleton
82+
data-aspect-ratio={aspectRatio}
83+
className={cn("w-full rounded-none", aspectRatioClasses)}
84+
/>
85+
<Skeleton
86+
data-aspect-ratio={aspectRatio}
87+
className={cn("w-full rounded-none", aspectRatioClasses)}
88+
/>
89+
<Skeleton
90+
data-aspect-ratio={aspectRatio}
91+
className={cn("w-full rounded-none", aspectRatioClasses)}
92+
/>
93+
<Skeleton
94+
data-aspect-ratio={aspectRatio}
95+
className={cn("w-full rounded-none", aspectRatioClasses)}
96+
/>
7897
</>
7998
}
8099
>
@@ -83,14 +102,21 @@ export async function ProductDetailSection({
83102
options={options}
84103
variants={variants}
85104
title={title}
105+
aspectRatio={aspectRatio}
86106
variantIdPromise={variantIdPromise}
87107
/>
88108
</Suspense>
89109
}
90110
mobileSlot={
91111
<Suspense
92112
fallback={
93-
<div className="relative shrink-0 w-full aspect-square snap-start snap-always overflow-hidden">
113+
<div
114+
data-aspect-ratio={aspectRatio}
115+
className={cn(
116+
"relative shrink-0 w-full snap-start snap-always overflow-hidden",
117+
aspectRatioClasses,
118+
)}
119+
>
94120
<Skeleton className="size-full rounded-none" />
95121
</div>
96122
}
@@ -100,6 +126,7 @@ export async function ProductDetailSection({
100126
options={options}
101127
variants={variants}
102128
title={title}
129+
aspectRatio={aspectRatio}
103130
variantIdPromise={variantIdPromise}
104131
/>
105132
</Suspense>
@@ -110,6 +137,7 @@ export async function ProductDetailSection({
110137
otherImages={images}
111138
videos={videos}
112139
title={title}
140+
aspectRatio={aspectRatio}
113141
className="lg:col-span-6"
114142
/>
115143
)}
@@ -297,12 +325,14 @@ async function ResolvedColorImages({
297325
options,
298326
variants,
299327
title,
328+
aspectRatio,
300329
variantIdPromise,
301330
}: {
302331
images: ImageType[];
303332
options: ProductOption[];
304333
variants: ProductVariant[];
305334
title: string;
335+
aspectRatio: ProductCardAspectRatio;
306336
variantIdPromise: Promise<string | undefined>;
307337
}) {
308338
const variantId = await variantIdPromise;
@@ -316,20 +346,22 @@ async function ResolvedColorImages({
316346

317347
if (colorImages.length === 0) return null;
318348

319-
return <ColorImageGrid images={colorImages} title={title} />;
349+
return <ColorImageGrid images={colorImages} title={title} aspectRatio={aspectRatio} />;
320350
}
321351

322352
async function ResolvedColorCarouselImages({
323353
images,
324354
options,
325355
variants,
326356
title,
357+
aspectRatio,
327358
variantIdPromise,
328359
}: {
329360
images: ImageType[];
330361
options: ProductOption[];
331362
variants: ProductVariant[];
332363
title: string;
364+
aspectRatio: ProductCardAspectRatio;
333365
variantIdPromise: Promise<string | undefined>;
334366
}) {
335367
const variantId = await variantIdPromise;
@@ -343,5 +375,5 @@ async function ResolvedColorCarouselImages({
343375

344376
if (colorImages.length === 0) return null;
345377

346-
return <ColorImageCarouselItems images={colorImages} title={title} />;
378+
return <ColorImageCarouselItems images={colorImages} title={title} aspectRatio={aspectRatio} />;
347379
}

0 commit comments

Comments
 (0)