feat: refine storefront product and sale sections

This commit is contained in:
amirhosein.ashourloo
2026-09-23 13:43:18 +03:30
parent 69c54d3b17
commit 3af4e04bdd
13 changed files with 263 additions and 122 deletions
+20 -4
View File
@@ -1,6 +1,6 @@
"use client";
import { Children, useRef } from "react";
import { Children, useRef, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { FreeMode } from "swiper/modules";
import type { Swiper as SwiperClass } from "swiper";
@@ -10,6 +10,7 @@ import "swiper/css/free-mode";
type CarouselProps = {
children: React.ReactNode;
ariaLabel: string;
slideClassName?: string;
};
function ChevronLeftIcon() {
@@ -40,11 +41,12 @@ function ChevronRightIcon() {
);
}
export function Carousel({ children, ariaLabel }: CarouselProps) {
export function Carousel({ children, ariaLabel, slideClassName }: CarouselProps) {
const swiperRef = useRef<SwiperClass | null>(null);
const [isLocked, setIsLocked] = useState(false);
const btnClass =
"absolute top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border border-white/40 bg-white/40 text-red shadow-sm backdrop-blur-md transition-colors hover:border-red/40 hover:bg-white/60 hover:text-red-dark";
"absolute top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border border-white/40 bg-white/40 text-red shadow-sm backdrop-blur-md transition-colors hover:border-red/40 hover:bg-white/60 hover:text-red-dark disabled:pointer-events-none disabled:opacity-0";
return (
<div className="relative">
@@ -52,6 +54,7 @@ export function Carousel({ children, ariaLabel }: CarouselProps) {
type="button"
onClick={() => swiperRef.current?.slidePrev()}
aria-label="قبلی"
disabled={isLocked}
className={`${btnClass} right-1`}
>
<ChevronRightIcon />
@@ -60,6 +63,7 @@ export function Carousel({ children, ariaLabel }: CarouselProps) {
type="button"
onClick={() => swiperRef.current?.slideNext()}
aria-label="بعدی"
disabled={isLocked}
className={`${btnClass} left-1`}
>
<ChevronLeftIcon />
@@ -68,10 +72,17 @@ export function Carousel({ children, ariaLabel }: CarouselProps) {
modules={[FreeMode]}
onSwiper={(swiper) => {
swiperRef.current = swiper;
setIsLocked(swiper.isLocked);
}}
onLock={(swiper) => {
swiper.slideTo(0, 0);
setIsLocked(true);
}}
onUnlock={() => setIsLocked(false)}
dir="rtl"
slidesPerView="auto"
spaceBetween={16}
watchOverflow
freeMode={{ enabled: true, momentum: true, sticky: true }}
grabCursor
role="region"
@@ -83,7 +94,12 @@ export function Carousel({ children, ariaLabel }: CarouselProps) {
// wrapper height — resolves per-slide instead of stretching to match
// the tallest sibling. height:auto (inline, so it wins) lets normal
// flex stretch equalize card heights across a row.
<SwiperSlide style={{ width: "auto", height: "auto" }}>{child}</SwiperSlide>
<SwiperSlide
className={slideClassName}
style={slideClassName ? { height: "auto" } : { width: "auto", height: "auto" }}
>
{child}
</SwiperSlide>
))}
</Swiper>
</div>