293 lines
8.5 KiB
TypeScript
293 lines
8.5 KiB
TypeScript
"use client";
|
|
|
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { Rating } from "@/components/shadcnblocks/rating";
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import type { CarouselApi } from "@/components/ui/carousel";
|
|
import {
|
|
Carousel,
|
|
CarouselContent,
|
|
CarouselItem,
|
|
} from "@/components/ui/carousel";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type ReviewType = {
|
|
rate: number;
|
|
title: string;
|
|
comment?: string;
|
|
verifiedBuyer?: boolean;
|
|
author: string;
|
|
image?: string;
|
|
type?: "text" | "image";
|
|
};
|
|
|
|
interface Reviews8Props {
|
|
reviews?: ReviewType[];
|
|
title?: string;
|
|
className?: string;
|
|
}
|
|
|
|
type ReviewCardProps = ReviewType;
|
|
|
|
const customerRoleLabel = (verified?: boolean) =>
|
|
verified ? "خریدار تأییدشده" : "مشتری";
|
|
|
|
const REVIEWS: ReviewType[] = [
|
|
{
|
|
rate: 4,
|
|
title: "طراحی آگاهانه که حس میشود",
|
|
comment:
|
|
"هر قطعه ظرافت آرامی دارد. معلوم است برای ساخت و جزئیات وقت گذاشتهاند. ظریف، کاربردی و با کیفیت ساخت عالی.",
|
|
author: "لوکا مرادی",
|
|
verifiedBuyer: false,
|
|
type: "text",
|
|
},
|
|
{
|
|
rate: 5,
|
|
title: "مینیمال، ولی پر از شخصیت",
|
|
author: "ژوان احمدی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Man-in-White-Hoodie-1.png",
|
|
type: "image",
|
|
},
|
|
{
|
|
rate: 4,
|
|
title: "تعادل کامل میان سبک و کارایی",
|
|
comment:
|
|
"دوست دارم همهچیز عمدی به نظر برسد؛ نه اضافه، نه کم. این آیتمها بدون شلوغ کردن فضا، به خانهام شخصیت میدهند.",
|
|
author: "لوکا مرادی",
|
|
verifiedBuyer: false,
|
|
type: "text",
|
|
},
|
|
{
|
|
rate: 5,
|
|
title: "جاودانه و عکاسی زیبا",
|
|
author: "ژوان احمدی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Elegant-Jewelry-Portrait-1.png",
|
|
type: "image",
|
|
},
|
|
{
|
|
rate: 4,
|
|
title: "روزمره را ارتقا میدهد",
|
|
comment:
|
|
"کم پیش میآید دکوری پیدا کنید که هم هنری باشد هم کاربردی. این محصولات هم در زندگی روزمره جا میگیرند و هم توجه را جلب میکنند.",
|
|
author: "لوکا مرادی",
|
|
verifiedBuyer: false,
|
|
type: "text",
|
|
},
|
|
{
|
|
rate: 5,
|
|
title: "ظریف، مدرن و خیرهکننده",
|
|
author: "ژوان احمدی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Elegant-Gold-Earrings-1.png",
|
|
type: "image",
|
|
},
|
|
{
|
|
rate: 4,
|
|
title: "با نیت واقعی ساخته شده",
|
|
comment:
|
|
"هیچچیز حس تولید انبوه نمیدهد. بافت، وزن و پرداخت کار همه گرم و منحصربهفردند؛ چیزی که کمتر جایی میبینید.",
|
|
author: "لوکا مرادی",
|
|
verifiedBuyer: false,
|
|
type: "text",
|
|
},
|
|
{
|
|
rate: 5,
|
|
title: "زیبایی بدون تلاش",
|
|
author: "ژوان احمدی",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Elegant-Watch-on-Womans-Wrist-1.png",
|
|
type: "image",
|
|
},
|
|
];
|
|
|
|
const Reviews8 = ({
|
|
reviews = REVIEWS,
|
|
title = "صداهای واقعی، نظرات صادقانه",
|
|
className,
|
|
}: Reviews8Props) => {
|
|
const [api, setApi] = useState<CarouselApi>();
|
|
const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);
|
|
const [current, setCurrent] = useState(0);
|
|
|
|
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("overflow-hidden py-32", className)}
|
|
>
|
|
<div className="container space-y-7.5">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<h2 className="text-3xl leading-tight tracking-tight md:text-xl lg:text-2xll">
|
|
{title}
|
|
</h2>
|
|
</div>
|
|
<div className="flex gap-3 max-md:hidden">
|
|
<Button
|
|
onClick={() => api?.scrollPrev()}
|
|
variant="outline"
|
|
className="rounded-full"
|
|
size="icon-lg"
|
|
aria-label="اسلاید قبلی"
|
|
>
|
|
<ChevronRight />
|
|
</Button>
|
|
<Button
|
|
onClick={() => api?.scrollNext()}
|
|
variant="outline"
|
|
className="rounded-full"
|
|
size="icon-lg"
|
|
aria-label="اسلاید بعدی"
|
|
>
|
|
<ChevronLeft />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-8">
|
|
<Carousel
|
|
opts={{
|
|
align: "start",
|
|
direction: "rtl",
|
|
}}
|
|
setApi={setApi}
|
|
className="[&>div]:overflow-visible"
|
|
>
|
|
<CarouselContent>
|
|
{reviews.map(({ type, ...review }, index) => (
|
|
<CarouselItem
|
|
className="sm:basis-1/2 lg:basis-1/4 xl:basis-1/6"
|
|
key={index}
|
|
>
|
|
{type === "image" ? (
|
|
<ImageReviewCard {...review} />
|
|
) : (
|
|
<TextReviewCard {...review} />
|
|
)}
|
|
</CarouselItem>
|
|
))}
|
|
</CarouselContent>
|
|
</Carousel>
|
|
<div className="flex items-center justify-center gap-4 md:hidden">
|
|
{scrollSnaps.map((_, index) => (
|
|
<button
|
|
type="button"
|
|
data-state={current === index ? "active" : "inactive"}
|
|
className="h-2.5 w-2.5 rounded-full border transition-all data-[state=active]:w-5 data-[state=active]:border-primary"
|
|
key={index}
|
|
aria-label={`رفتن به اسلاید ${(index + 1).toLocaleString("fa-IR")}`}
|
|
aria-current={current === index ? "true" : undefined}
|
|
onClick={() => onButtonClick(index)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
const TextReviewCard = ({
|
|
rate,
|
|
title,
|
|
comment,
|
|
author,
|
|
verifiedBuyer,
|
|
}: ReviewCardProps) => {
|
|
return (
|
|
<Card className="h-full rounded-lg border-primary p-5">
|
|
<CardContent className="space-y-5 p-0">
|
|
<Rating className="[&_svg]:size-3 [&>div]:size-3" rate={rate} />
|
|
<CardTitle className="leading-tight">{title}</CardTitle>
|
|
{comment && (
|
|
<CardDescription className="leading-relaxed text-foreground">
|
|
{comment}
|
|
</CardDescription>
|
|
)}
|
|
<div>
|
|
<p className="text-xs">{author}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{customerRoleLabel(verifiedBuyer)}
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
const ImageReviewCard = ({
|
|
image,
|
|
title,
|
|
rate,
|
|
author,
|
|
verifiedBuyer,
|
|
}: ReviewCardProps) => {
|
|
return (
|
|
<Card className="h-full border-0 bg-background p-0 shadow-none">
|
|
<CardContent className="space-y-5 p-0">
|
|
<AspectRatio ratio={1} className="overflow-hidden rounded-lg">
|
|
<img
|
|
src={image}
|
|
alt={`تصویر نظر: ${title}`}
|
|
className="block size-full object-cover object-center"
|
|
/>
|
|
</AspectRatio>
|
|
<Rating className="[&_svg]:size-3 [&>div]:size-3" rate={rate} />
|
|
<CardTitle className="leading-tight">{title}</CardTitle>
|
|
<div>
|
|
<p className="text-xs">{author}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{customerRoleLabel(verifiedBuyer)}
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export { Reviews8 };
|