Files
reui-blocks/blocks/reviews9.tsx
T

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 };