Open the leather banner to the full viewport on scroll and align homepage frames.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f44058bd6e
commit
beac5f3b73
@@ -3,7 +3,9 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import type { Product } from "@/data/home";
|
||||
import BilingualTitle from "@/components/ui/BilingualTitle";
|
||||
import ProductCard from "./ProductCard";
|
||||
import OverlayProductCard from "./OverlayProductCard";
|
||||
import PromoCard, { type PromoSlotOverride } from "./PromoCard";
|
||||
|
||||
type ProductRowProps = {
|
||||
@@ -13,10 +15,23 @@ type ProductRowProps = {
|
||||
/** Stagger fade-in for promo + product cards when scrolled into view */
|
||||
stagger?: boolean;
|
||||
promoSlot?: PromoSlotOverride | null;
|
||||
/**
|
||||
* `carousel` — promo + horizontal product scroller (default)
|
||||
* `overlay-grid` — 2×4 image-only cards; black hover overlay with name + desc
|
||||
*/
|
||||
layout?: "carousel" | "overlay-grid";
|
||||
};
|
||||
|
||||
const CARD_W =
|
||||
"w-[260px] shrink-0 sm:w-[280px] lg:w-[300px]";
|
||||
const OVERLAY_GRID_TITLES: Record<
|
||||
ProductRowProps["promo"],
|
||||
{ titleFa: string; titleEn: string }
|
||||
> = {
|
||||
sale: { titleFa: "فروش ویژه", titleEn: "Sale" },
|
||||
discount: { titleFa: "پیشنهادهای ویژه", titleEn: "Special offers" },
|
||||
autumn: { titleFa: "کالکشن پاییزه", titleEn: "Autumn collection" },
|
||||
};
|
||||
|
||||
const CARD_W = "w-[260px] shrink-0 sm:w-[280px] lg:w-[300px]";
|
||||
|
||||
export default function ProductRow({
|
||||
products,
|
||||
@@ -24,6 +39,7 @@ export default function ProductRow({
|
||||
className = "",
|
||||
stagger = false,
|
||||
promoSlot,
|
||||
layout = "carousel",
|
||||
}: ProductRowProps) {
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const trackRef = useRef<HTMLDivElement>(null);
|
||||
@@ -66,6 +82,7 @@ export default function ProductRow({
|
||||
}, [applyOffset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (layout !== "carousel") return;
|
||||
measure();
|
||||
const viewport = viewportRef.current;
|
||||
if (!viewport) return;
|
||||
@@ -79,7 +96,7 @@ export default function ProductRow({
|
||||
window.removeEventListener("resize", measure);
|
||||
window.clearTimeout(t);
|
||||
};
|
||||
}, [measure, products, promo]);
|
||||
}, [measure, products, promo, layout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!stagger) return;
|
||||
@@ -108,13 +125,13 @@ export default function ProductRow({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (layout !== "carousel") return;
|
||||
const viewport = viewportRef.current;
|
||||
if (!viewport) return;
|
||||
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
if (e.button !== 0) return;
|
||||
const target = e.target as HTMLElement;
|
||||
// Never steal pointer from links/buttons — cards are full <a> links
|
||||
if (target.closest("a, button")) return;
|
||||
|
||||
dragRef.current = {
|
||||
@@ -131,7 +148,6 @@ export default function ProductRow({
|
||||
|
||||
const dx = drag.startX - e.clientX;
|
||||
if (!drag.dragging) {
|
||||
// Wait for a real drag before capturing — plain clicks still hit <a>
|
||||
if (Math.abs(e.clientX - drag.startX) < 8) return;
|
||||
drag.dragging = true;
|
||||
viewport.setPointerCapture(e.pointerId);
|
||||
@@ -179,7 +195,49 @@ export default function ProductRow({
|
||||
viewport.removeEventListener("pointerup", onPointerUp);
|
||||
viewport.removeEventListener("pointercancel", onPointerUp);
|
||||
};
|
||||
}, [applyOffset]);
|
||||
}, [applyOffset, layout]);
|
||||
|
||||
if (layout === "overlay-grid") {
|
||||
const gridProducts = products.slice(0, 8);
|
||||
const title = OVERLAY_GRID_TITLES[promo];
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={sectionRef}
|
||||
dir="rtl"
|
||||
className={`mx-auto w-full max-w-[1600px] px-4 sm:px-6 lg:px-10 ${className}`}
|
||||
>
|
||||
<BilingualTitle
|
||||
titleFa={promoSlot?.nameFa?.trim() || title.titleFa}
|
||||
titleEn={promoSlot?.nameEn?.trim() || title.titleEn}
|
||||
align="center"
|
||||
size="lg"
|
||||
className="mb-6 sm:mb-8"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-2 sm:gap-2.5 md:grid-cols-4 lg:gap-3">
|
||||
{gridProducts.map((product, i) => (
|
||||
<div
|
||||
key={`${promo}-overlay-${product.id}-${i}`}
|
||||
className="seq-fade-item"
|
||||
style={
|
||||
stagger
|
||||
? {
|
||||
transitionDelay: visible ? `${i * 70}ms` : "0ms",
|
||||
opacity: visible ? 1 : 0,
|
||||
transform: visible
|
||||
? "translate3d(0,0,0)"
|
||||
: "translate3d(0,28px,0)",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<OverlayProductCard product={product} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
|
||||
Reference in New Issue
Block a user