233 lines
7.1 KiB
TypeScript
233 lines
7.1 KiB
TypeScript
"use client";
|
|
|
|
import { Minus } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { Rating } from "@/components/shadcnblocks/rating";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import type { CarouselApi } from "@/components/ui/carousel";
|
|
import {
|
|
Carousel,
|
|
CarouselContent,
|
|
CarouselItem,
|
|
CarouselNext,
|
|
CarouselPrevious,
|
|
} from "@/components/ui/carousel";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type ReviewType = {
|
|
rate: number;
|
|
comment: string;
|
|
author: string;
|
|
date: string;
|
|
image: {
|
|
src: string;
|
|
alt: string;
|
|
};
|
|
};
|
|
|
|
interface Reviews9Props {
|
|
reviews?: ReviewType[];
|
|
title?: string;
|
|
className?: string;
|
|
}
|
|
|
|
type ReviewCardProps = ReviewType;
|
|
|
|
const REVIEWS: ReviewType[] = [
|
|
{
|
|
rate: 5,
|
|
comment:
|
|
"این آباژور مجسمهای به فضا گرما و ظرافت میبخشد و در عین هنری بودن، کاربردی است.",
|
|
author: "آوا تهرانی",
|
|
date: "۲۰ آذر ۱۴۰۳",
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Artistic-Lamp-1.png",
|
|
alt: "آباژور هنری مدرن در فضای داخلی",
|
|
},
|
|
},
|
|
{
|
|
rate: 5,
|
|
comment:
|
|
"مبل مینیمال لوکس است، راحت و دقیقاً همان چیزی که برای استراحت میخواستم.",
|
|
author: "آوا تهرانی",
|
|
date: "۱۸ آذر ۱۴۰۳",
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Minimalist-Sofa-1.png",
|
|
alt: "مبل مینیمال مدرن",
|
|
},
|
|
},
|
|
{
|
|
rate: 5,
|
|
comment:
|
|
"کابینت چوبی زیبا فضای من را گرمتر و کاربردیتر کرده؛ کیفیت ساخت عالی است.",
|
|
author: "آوا تهرانی",
|
|
date: "۱۵ آذر ۱۴۰۳",
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/furniture/Modern-Wooden-Cabinet-1.png",
|
|
alt: "کابینت چوبی مدرن",
|
|
},
|
|
},
|
|
{
|
|
rate: 5,
|
|
comment:
|
|
"این مجسمه مدرن با حضور هنری ظریف، دکور داخلی را یک پله ارتقا داده است.",
|
|
author: "آوا تهرانی",
|
|
date: "۱۲ آذر ۱۴۰۳",
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/furniture/Minimalist-Interior-with-Sculptural-Element-1.png",
|
|
alt: "مجسمه در فضای داخلی مینیمال",
|
|
},
|
|
},
|
|
{
|
|
rate: 5,
|
|
comment:
|
|
"میز پذیرایی مینیمال هماهنگ با فضاست و در عین سادگی، ظاهری چشمگیر دارد.",
|
|
author: "آوا تهرانی",
|
|
date: "۸ آذر ۱۴۰۳",
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/furniture/Minimalist-Wooden-Sideboard-1.png",
|
|
alt: "میز پذیرایی چوبی مینیمال",
|
|
},
|
|
},
|
|
];
|
|
|
|
const Reviews9 = ({
|
|
reviews = REVIEWS,
|
|
title = "نظرات مشتریان",
|
|
className,
|
|
}: Reviews9Props) => {
|
|
const [api, setApi] = useState<CarouselApi>();
|
|
const [current, setCurrent] = useState(0);
|
|
const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);
|
|
|
|
const onButtonClick = (index: number) => {
|
|
if (!api) return;
|
|
api.scrollTo(index);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!api) return;
|
|
|
|
const updateCurrent = (carouselApi: CarouselApi) => {
|
|
setCurrent(carouselApi?.selectedScrollSnap() ?? 0);
|
|
};
|
|
|
|
const updateScrollSnaps = (carouselApi: CarouselApi) => {
|
|
setScrollSnaps(carouselApi?.scrollSnapList() ?? []);
|
|
};
|
|
|
|
updateCurrent(api);
|
|
updateScrollSnaps(api);
|
|
|
|
api
|
|
.on("reInit", updateCurrent)
|
|
.on("select", updateCurrent)
|
|
.on("resize", updateScrollSnaps);
|
|
|
|
return () => {
|
|
api
|
|
.off("reInit", updateCurrent)
|
|
.off("select", updateCurrent)
|
|
.off("resize", updateScrollSnaps);
|
|
};
|
|
}, [api]);
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("py-32", className)}
|
|
>
|
|
<div className="container space-y-10">
|
|
<h2 className="text-xl font-medium tracking-tighter xl:text-2xll">
|
|
{title}
|
|
</h2>
|
|
<Carousel setApi={setApi} opts={{ direction: "rtl", align: "start" }}>
|
|
<CarouselContent>
|
|
{reviews.map((review, index) => (
|
|
<CarouselItem className="sm:basis-1/2 lg:basis-1/3" key={index}>
|
|
<ReviewCard {...review} />
|
|
</CarouselItem>
|
|
))}
|
|
</CarouselContent>
|
|
<div className="max-xl:hidden">
|
|
<CarouselNext
|
|
className="end-0 size-16 translate-x-1/2 bg-background/40 backdrop-blur-lg [&>svg]:!size-6"
|
|
aria-label="اسلاید بعدی"
|
|
/>
|
|
<CarouselPrevious
|
|
className="start-0 size-16 -translate-x-1/2 bg-background/40 backdrop-blur-lg [&>svg]:!size-6"
|
|
aria-label="اسلاید قبلی"
|
|
/>
|
|
</div>
|
|
</Carousel>
|
|
<div className="-mt-5 flex items-center justify-center xl:hidden">
|
|
{scrollSnaps.map((_, index) => (
|
|
<button
|
|
type="button"
|
|
data-state={current === index ? "active" : "inactive"}
|
|
className="group flex size-4"
|
|
key={index}
|
|
aria-label={`رفتن به اسلاید ${(index + 1).toLocaleString("fa-IR")}`}
|
|
aria-current={current === index ? "true" : undefined}
|
|
onClick={() => onButtonClick(index)}
|
|
>
|
|
<span className="m-auto block size-2 rounded-full bg-primary/20 group-data-[state=active]:bg-primary" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
const ReviewCard = ({
|
|
rate,
|
|
comment,
|
|
author,
|
|
date,
|
|
image,
|
|
}: ReviewCardProps) => {
|
|
const formattedRate = rate.toLocaleString("fa-IR", {
|
|
minimumFractionDigits: 1,
|
|
maximumFractionDigits: 1,
|
|
});
|
|
|
|
return (
|
|
<Card className="h-full overflow-hidden bg-primary/5 p-0">
|
|
<CardContent className="flex h-full p-0 max-xl:flex-col-reverse">
|
|
<div className="flex-1 space-y-6 p-8">
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-2 max-xl:justify-center">
|
|
<Rating className="[&_svg]:size-4 [&>div]:size-4" rate={rate} />
|
|
<p className="font-bold">{formattedRate}</p>
|
|
</div>
|
|
<p className="text-lg font-medium max-xl:text-center">{comment}</p>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2 max-xl:justify-center">
|
|
<p className="text-sm font-medium text-muted-foreground">
|
|
{author}
|
|
</p>
|
|
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
|
<Minus className="size-3.5" aria-hidden />
|
|
<p>{date}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="h-full shrink-0 basis-37.5">
|
|
<div className="h-full overflow-hidden rounded-xl max-xl:aspect-[0.8]">
|
|
<img
|
|
src={image.src}
|
|
alt={image.alt}
|
|
className="block size-full object-cover object-center"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export { Reviews9 };
|