Fix sale-banner slot, mobile width, and product card height
- Pull the special-discount carousel's banner out of the swiper into a fixed sidebar (no longer scrolls with the product slides) and drop carousel looping - Wire it to a new sale-banner static-image slot - Fix an invalid xs: breakpoint that was silently keeping the banner at its smallest width on all phone sizes - Tighten ProductCard's title height to match 2 lines of text instead of reserving extra space, and drop a redundant top margin on the price row that was inflating card height
This commit is contained in:
@@ -52,6 +52,15 @@ export async function GET() {
|
||||
itemCount: 3,
|
||||
recommendedWidth: 520,
|
||||
},
|
||||
{
|
||||
key: "sale-banner",
|
||||
label: "بنر ثابت کروسل تخفیفهای ویژه",
|
||||
kind: "single",
|
||||
pageKey: "home",
|
||||
aspectRatio: "4:5",
|
||||
itemCount: 1,
|
||||
recommendedWidth: 480,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ export default async function Home() {
|
||||
return (
|
||||
<main>
|
||||
<HeroSlider slot={findSlot(slots, "slider")} />
|
||||
<SpecialSaleCarousel items={specialItems} />
|
||||
<SpecialSaleCarousel items={specialItems} bannerSlot={findSlot(slots, "sale-banner")} />
|
||||
<ThreeBanners slot={findSlot(slots, "three-banner-row")} />
|
||||
<TwoBanners slot={findSlot(slots, "two-banner-row")} />
|
||||
<TopSellingCarousel items={topSellingItems} />
|
||||
|
||||
@@ -4,13 +4,27 @@ import Link from "next/link";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Autoplay, Navigation } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
import type { SpecialCarouselItem } from "@/lib/types";
|
||||
import type { SpecialCarouselItem, StaticImageSlot } from "@/lib/types";
|
||||
import { toCssAspectRatio } from "@/lib/format";
|
||||
import ProductCard from "./ui/ProductCard";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "./icons";
|
||||
|
||||
export default function SpecialSaleCarousel({ items }: { items: SpecialCarouselItem[] }) {
|
||||
export default function SpecialSaleCarousel({
|
||||
items,
|
||||
bannerSlot,
|
||||
}: {
|
||||
items: SpecialCarouselItem[];
|
||||
bannerSlot?: StaticImageSlot;
|
||||
}) {
|
||||
if (items.length === 0) return null;
|
||||
|
||||
const bannerImage = bannerSlot?.images[0];
|
||||
const cssAspectRatio = toCssAspectRatio(bannerSlot?.aspectRatio);
|
||||
const bannerAspectStyle = cssAspectRatio ? { aspectRatio: cssAspectRatio } : undefined;
|
||||
const bannerSrc = bannerImage?.url ?? "/images/special-sale/banner.svg";
|
||||
const bannerHref = bannerImage?.linkUrl;
|
||||
const BannerWrapper = bannerHref ? "a" : "div";
|
||||
|
||||
return (
|
||||
<section id="special-sale" className="boxed py-10 md:py-14">
|
||||
<div className="flex items-center justify-between mb-6 md:mb-8">
|
||||
@@ -32,51 +46,58 @@ export default function SpecialSaleCarousel({ items }: { items: SpecialCarouselI
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="relative bg-brand rounded-2xl md:rounded-3xl p-4 md:p-8">
|
||||
<Swiper
|
||||
modules={[Autoplay, Navigation]}
|
||||
autoplay={{ delay: 3200, disableOnInteraction: false }}
|
||||
navigation={{
|
||||
prevEl: ".sale-prev",
|
||||
nextEl: ".sale-next",
|
||||
}}
|
||||
loop={items.length > 5}
|
||||
spaceBetween={14}
|
||||
slidesPerView={1.8}
|
||||
breakpoints={{
|
||||
480: { slidesPerView: 2.2 },
|
||||
640: { slidesPerView: 3 },
|
||||
1024: { slidesPerView: 4.5, spaceBetween: 20 },
|
||||
}}
|
||||
<div className="bg-brand rounded-2xl md:rounded-3xl p-4 md:p-8 flex gap-4 md:gap-6">
|
||||
{/* Fixed on the right — stays put while the products scroll beside it. */}
|
||||
<BannerWrapper
|
||||
{...(bannerHref ? { href: bannerHref } : {})}
|
||||
className="shrink-0 block w-32 sm:w-40 md:w-56 overflow-hidden rounded-lg"
|
||||
>
|
||||
<SwiperSlide>
|
||||
<img
|
||||
src="/images/special-sale/banner.svg"
|
||||
alt=""
|
||||
className="w-full h-full aspect-[4/5] object-cover rounded-lg"
|
||||
/>
|
||||
</SwiperSlide>
|
||||
{items.map(({ product, discount }) => (
|
||||
<SwiperSlide key={product.id}>
|
||||
<ProductCard product={product} discount={discount} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<img
|
||||
src={bannerSrc}
|
||||
alt=""
|
||||
style={bannerAspectStyle}
|
||||
className={`w-full h-full object-cover ${bannerAspectStyle ? "" : "aspect-[4/5]"}`}
|
||||
/>
|
||||
</BannerWrapper>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="اسلاید قبلی"
|
||||
className="sale-prev hidden sm:flex absolute top-1/2 -translate-y-1/2 start-0 z-10 translate-x-1/2 items-center justify-center size-10 md:size-11 rounded-full bg-white text-brand shadow-md hover:bg-gold hover:text-ink transition-colors"
|
||||
>
|
||||
<ChevronRightIcon className="size-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="اسلاید بعدی"
|
||||
className="sale-next hidden sm:flex absolute top-1/2 -translate-y-1/2 end-0 z-10 -translate-x-1/2 items-center justify-center size-10 md:size-11 rounded-full bg-white text-brand shadow-md hover:bg-gold hover:text-ink transition-colors"
|
||||
>
|
||||
<ChevronLeftIcon className="size-5" />
|
||||
</button>
|
||||
<div className="relative flex-1 min-w-0">
|
||||
<Swiper
|
||||
modules={[Autoplay, Navigation]}
|
||||
autoplay={{ delay: 3200, disableOnInteraction: false }}
|
||||
navigation={{
|
||||
prevEl: ".sale-prev",
|
||||
nextEl: ".sale-next",
|
||||
}}
|
||||
spaceBetween={14}
|
||||
slidesPerView={1.4}
|
||||
breakpoints={{
|
||||
480: { slidesPerView: 1.8 },
|
||||
640: { slidesPerView: 2.4 },
|
||||
1024: { slidesPerView: 3.5, spaceBetween: 20 },
|
||||
}}
|
||||
>
|
||||
{items.map(({ product, discount }) => (
|
||||
<SwiperSlide key={product.id}>
|
||||
<ProductCard product={product} discount={discount} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="اسلاید قبلی"
|
||||
className="sale-prev hidden sm:flex absolute top-1/2 -translate-y-1/2 start-0 z-10 translate-x-1/2 items-center justify-center size-10 md:size-11 rounded-full bg-white text-brand shadow-md hover:bg-gold hover:text-ink transition-colors"
|
||||
>
|
||||
<ChevronRightIcon className="size-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="اسلاید بعدی"
|
||||
className="sale-next hidden sm:flex absolute top-1/2 -translate-y-1/2 end-0 z-10 -translate-x-1/2 items-center justify-center size-10 md:size-11 rounded-full bg-white text-brand shadow-md hover:bg-gold hover:text-ink transition-colors"
|
||||
>
|
||||
<ChevronLeftIcon className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ProductCard({
|
||||
<div className="flex flex-1 flex-col p-3.5">
|
||||
{/* Fixed height regardless of 1 or 2 lines, so card heights stay
|
||||
consistent across a row of mixed-length titles. */}
|
||||
<h3 className="h-10 md:h-11 leading-tight text-[13px] md:text-sm text-ink text-right line-clamp-2">
|
||||
<h3 className="h-9 md:h-10 leading-tight text-[13px] md:text-sm text-ink text-right line-clamp-2">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function ProductCard({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="font-bold text-sm md:text-[15px] text-brand truncate">
|
||||
{price != null ? formatToman(price) : "تماس بگیرید"}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user