220 lines
7.5 KiB
TypeScript
220 lines
7.5 KiB
TypeScript
"use client";
|
|
|
|
import { AnimatePresence, motion } from "framer-motion";
|
|
import React, { useState } from "react";
|
|
|
|
import type { CarouselApi } from "@/components/ui/carousel";
|
|
import {
|
|
Carousel,
|
|
CarouselContent,
|
|
CarouselItem,
|
|
CarouselNext,
|
|
CarouselPrevious,
|
|
} from "@/components/ui/carousel";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const SLIDES = [
|
|
{
|
|
name: "کاوه مرادی",
|
|
content:
|
|
"Laravel نحوه ساخت اپلیکیشنهای وب ما را کاملاً متحول کرد. نحو زیبا، قابلیتهای قدرتمند و مستندات عالی، توسعه را لذتبخش کرده است.",
|
|
logo: "/block-media/shadcnblocks/block/logos/laravel-wordmark.svg",
|
|
logoAlt: "لوگوی Laravel",
|
|
logoClass: "h-10 md:h-12",
|
|
imageAlt: "نمونه کار توسعه وب با Laravel",
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/fabian-centeno-njeVb6E3XB8-unsplash.jpg",
|
|
position: 2,
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/israel-andrade-YI_9SivVt_s-unsplash.jpg",
|
|
position: 4,
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/studio-kealaula-IVZ_XAoZF3s-unsplash.jpg",
|
|
position: 7,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "مهسا نوری",
|
|
content:
|
|
"همکاری با این تیم کسبوکار ما را کاملاً متحول کرد. توجه به جزئیات و راهکارهای نوآورانه آنها باعث شد در تنها شش ماه، درآمدمان ۴۰٪ افزایش یابد.",
|
|
logo: "/block-media/shadcnblocks/block/logos/tailwind-wordmark.svg",
|
|
logoAlt: "لوگوی Tailwind CSS",
|
|
logoClass: "h-6 md:h-8",
|
|
imageAlt: "نمونه کار طراحی رابط کاربری",
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/annie-spratt-AkftcHujUmk-unsplash.jpg",
|
|
position: 1,
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/nastuh-abootalebi-yWwob8kwOCk-unsplash.jpg",
|
|
position: 3,
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/alvin-engler-bIhpiQA009k-unsplash.jpg",
|
|
position: 5,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "امیر محمدی",
|
|
content:
|
|
"این محصول فراتر از تمام انتظارات ما بود. شهودی، قدرتمند و دقیقاً همان چیزی که برای سادهسازی فرآیندهایمان نیاز داشتیم. برای هر کسبوکاری توصیه میشود.",
|
|
logo: "/block-media/shadcnblocks/block/logos/vercel-wordmark.svg",
|
|
logoAlt: "لوگوی Vercel",
|
|
logoClass: "h-6 md:h-8",
|
|
imageAlt: "نمونه کار استقرار و زیرساخت ابری",
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/redd-f-5U_28ojjgms-unsplash.jpg",
|
|
position: 0,
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/nubelson-fernandes-tAJYoec13xk-unsplash.jpg",
|
|
position: 4,
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/photos/nubelson-fernandes-xxa7qYObMrI-unsplash.jpg",
|
|
position: 7,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
interface CaseStudies5Props {
|
|
className?: string;
|
|
}
|
|
|
|
const CaseStudies5 = ({ className }: CaseStudies5Props) => {
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
const [carouselApi, setCarouselApi] = useState<CarouselApi>();
|
|
|
|
React.useEffect(() => {
|
|
if (!carouselApi) {
|
|
return;
|
|
}
|
|
|
|
const onSelect = () => {
|
|
setCurrentIndex(carouselApi.selectedScrollSnap());
|
|
};
|
|
|
|
carouselApi.on("select", onSelect);
|
|
return () => {
|
|
carouselApi.off("select", onSelect);
|
|
};
|
|
}, [carouselApi]);
|
|
|
|
const renderGrid = (
|
|
images: (typeof SLIDES)[0]["images"],
|
|
imageAlt: string,
|
|
) => {
|
|
return (
|
|
<div className="grid h-full w-full grid-cols-3 grid-rows-3 gap-3 p-3">
|
|
{Array.from({ length: 9 }).map((_, index) => {
|
|
const image = images.find((img) => img.position === index);
|
|
const isImage = Boolean(image);
|
|
|
|
return (
|
|
<div
|
|
key={index}
|
|
className={cn(
|
|
"w-full overflow-hidden rounded-lg",
|
|
!isImage && "bg-muted",
|
|
)}
|
|
>
|
|
{isImage && image?.src && (
|
|
<img
|
|
src={image.src}
|
|
alt={imageAlt}
|
|
className="aspect-square h-full w-full object-cover md:aspect-video"
|
|
loading={index === 0 ? "eager" : "lazy"}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("bg-background py-32", className)}
|
|
>
|
|
<div className="relative container">
|
|
<div className="mx-auto w-full overflow-hidden rounded-lg md:border">
|
|
<Carousel setApi={setCarouselApi}>
|
|
<div className="grid grid-cols-1 md:grid-cols-2">
|
|
<div className="relative">
|
|
<CarouselContent>
|
|
{SLIDES.map((testimonial, index) => (
|
|
<CarouselItem key={index}>
|
|
<div className="relative flex items-center justify-center overflow-hidden md:h-[500px]">
|
|
{renderGrid(testimonial.images, testimonial.imageAlt)}
|
|
</div>
|
|
</CarouselItem>
|
|
))}
|
|
</CarouselContent>
|
|
</div>
|
|
|
|
<div className="z-2 flex h-full items-center p-4 md:p-10">
|
|
<div className="flex flex-col gap-y-6 md:gap-y-8">
|
|
<AnimatePresence mode="wait">
|
|
<motion.div
|
|
key={currentIndex}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
transition={{
|
|
duration: 0.4,
|
|
ease: "easeOut",
|
|
}}
|
|
className="flex h-12 items-center gap-3"
|
|
>
|
|
<img
|
|
src={SLIDES[currentIndex].logo}
|
|
alt={SLIDES[currentIndex].logoAlt}
|
|
className={SLIDES[currentIndex].logoClass}
|
|
/>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
|
|
<AnimatePresence mode="wait">
|
|
<motion.p
|
|
key={currentIndex}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
transition={{
|
|
duration: 0.4,
|
|
ease: "easeOut",
|
|
delay: 0.15,
|
|
}}
|
|
className="min-h-[100px] text-start leading-snug tracking-tight text-muted-foreground sm:text-xl xl:ms-8"
|
|
>
|
|
{SLIDES[currentIndex].content}
|
|
</motion.p>
|
|
</AnimatePresence>
|
|
|
|
<div className="flex h-12 gap-4">
|
|
<CarouselPrevious className="static translate-y-0 rtl:rotate-180" />
|
|
<CarouselNext className="static translate-y-0 rtl:rotate-180" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Carousel>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { CaseStudies5 };
|