2026-07-11 21:21:29 +03:30
"use client" ;
import { ArrowLeft , ArrowRight } from "lucide-react" ;
import { useEffect , useState } from "react" ;
import { Button } from "@/components/ui/button" ;
import type { CarouselApi } from "@/components/ui/carousel" ;
import {
Carousel ,
CarouselContent ,
CarouselItem ,
} from "@/components/ui/carousel" ;
import { cn } from "@/lib/utils" ;
export interface Gallery4Item {
id : string ;
title : string ;
description : string ;
href : string ;
image : string ;
}
export interface Gallery4Props {
title? : string ;
description? : string ;
items? : Gallery4Item [];
className? : string ;
}
const data : Gallery4Item [] = [
{
id : "shadcn-ui" ,
title : "ری یو آی: ساخت کتابخانه مدرن کامپوننت" ,
description :
"ببینید ری یو آی چگونه کتابخانههای کامپوننت رابط کاربری را با رویکردی یکتا در توزیع و سفارشیسازی متحول کرد و ساخت اپلیکیشنهای زیبا و در دسترس را برای توسعهدهندگان سادهتر کرد." ,
href : "https://ui.shadcn.com" ,
image :
"/block-media/external/unsplash/2.jpg" ,
},
{
id : "tailwind" ,
title : "استایلدهی مدرن: انقلاب utility-first" ,
description :
"کشف کنید استایلدهی مدرن چگونه روش استایلدهی توسعهدهندگان را تغییر داد؛ رویکرد utility-first که سرعت توسعه را بالا میبرد و انعطاف طراحی را حفظ میکند." ,
href : "https://tailwindcss.com" ,
image :
"/block-media/external/unsplash/photo-1551250928-e4a05afaed1e.jpg" ,
},
{
id : "astro" ,
title : "Astro: فریمورک همهکاره وب" ,
description :
"بیاموزید معماری Islands و رویکرد zero-JS-by-default در Astro چگونه به توسعهدهندگان کمک میکند وبسایتهای سریعتر بسازند و در عین حال تعامل غنی داشته باشند." ,
href : "https://astro.build" ,
image :
"/block-media/external/unsplash/photo-1536735561749-fc87494598cb.jpg" ,
},
{
id : "react" ,
title : "رابط کاربری: پیشگام رابط کاربری مبتنی بر کامپوننت" ,
description :
"ببینید رابط کاربری با معماری مبتنی بر کامپوننت چگونه همچنان توسعه وب مدرن را شکل میدهد و ساخت رابطهای پیچیده با کد قابل استفاده مجدد را ممکن میکند." ,
href : "https://react.dev" ,
image :
"/block-media/external/unsplash/4.jpg" ,
},
{
id : "nextjs" ,
title : "فریمورک وب: فریمورک رابط کاربری برای پروداکشن" ,
description :
"کشف کنید فریمورک وب چگونه به فریمورک اصلی ساخت اپلیکیشنهای full-stack رابط کاربری تبدیل شده؛ با server component، مسیریابی فایلمحور و بهینهسازی خودکار." ,
href : "https://nextjs.org" ,
image :
"/block-media/external/unsplash/photo-1550070881-a5d71eda5800.jpg" ,
},
];
const Gallery4 = ({
title = "مطالعات موردی" ,
2026-07-21 19:25:31 +03:30
description = "ببینید شرکتها چگونه با پلتفرم سامانه مدیریت ارتباط با مشتری و بازاریابی ما درآمد و نرخ تبدیل خود را افزایش دادهاند. این مطالعات موردی کاربردهای واقعی و نتایج قابل اندازهگیری را نشان میدهند." ,
2026-07-11 21:21:29 +03:30
items = data ,
className ,
} : Gallery4Props ) => {
const [ carouselApi , setCarouselApi ] = useState < CarouselApi >();
const [ canScrollPrev , setCanScrollPrev ] = useState ( false );
const [ canScrollNext , setCanScrollNext ] = useState ( false );
const [ currentSlide , setCurrentSlide ] = useState ( 0 );
useEffect (() => {
if ( ! carouselApi ) {
return ;
}
const updateSelection = () => {
setCanScrollPrev ( carouselApi . canScrollPrev ());
setCanScrollNext ( carouselApi . canScrollNext ());
setCurrentSlide ( carouselApi . selectedScrollSnap ());
};
updateSelection ();
carouselApi . on ( "select" , updateSelection );
return () => {
carouselApi . off ( "select" , updateSelection );
};
}, [ carouselApi ]);
useEffect (() => {
if ( ! carouselApi ) {
return ;
}
carouselApi . reInit ();
}, [ carouselApi ]);
return (
< section dir = "rtl" lang = "fa" className = { cn ( "py-32" , className )}>
< div className = "container" >
< div className = "mb-8 flex items-end justify-between md:mb-14 lg:mb-16" >
< div className = "flex flex-col gap-4 text-start" >
< h2 className = "text-3xl font-medium md:text-xl lg:text-2xll" >
{ title }
</ h2 >
< p className = "max-w-xl text-muted-foreground" >{ description }</ p >
</ div >
< div className = "hidden shrink-0 gap-2 md:flex" >
< Button
size = "icon"
variant = "ghost"
onClick = {() => {
carouselApi ? . scrollPrev ();
}}
disabled = { ! canScrollPrev }
className = "disabled:pointer-events-auto"
>
< ArrowRight className = "size-5" />
< span className = "sr-only" > اسلاید قبلی </ span >
</ Button >
< Button
size = "icon"
variant = "ghost"
onClick = {() => {
carouselApi ? . scrollNext ();
}}
disabled = { ! canScrollNext }
className = "disabled:pointer-events-auto"
>
< ArrowLeft className = "size-5" />
< span className = "sr-only" > اسلاید بعدی </ span >
</ Button >
</ div >
</ div >
< Carousel
dir = "rtl"
setApi = { setCarouselApi }
opts = {{
align : "start" ,
direction : "rtl" ,
breakpoints : {
"(max-width: 768px)" : {
dragFree : true ,
},
},
}}
>
< CarouselContent className = "-ms-4 me-0" >
{ items . map (( item ) => (
< CarouselItem
key = { item . id }
className = "max-w-[320px] basis-auto ps-4 pe-0 lg:max-w-[360px]"
>
< a href = { item . href } className = "group rounded-xl" >
< div className = "group relative h-full min-h-[27rem] max-w-full overflow-hidden rounded-xl md:aspect-5/4 lg:aspect-16/9" >
< img
src = { item . image }
alt = { item . title }
className = "absolute h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-105"
/>
< div className = "absolute inset-0 h-full bg-[linear-gradient(transparent_20%,black_100%)] mix-blend-multiply" />
< div className = "absolute inset-x-0 bottom-0 flex flex-col items-start p-6 text-start text-white md:p-8" >
< div className = "mb-2 pt-4 text-xl font-semibold md:mb-3 md:pt-4 lg:pt-4" >
{ item . title }
</ div >
< div className = "mb-8 line-clamp-2 md:mb-12 lg:mb-9" >
{ item . description }
</ div >
< div className = "flex items-center text-sm" >
بیشتر بخوانید { " " }
< ArrowRight className = "ms-2 size-5 transition-transform group-hover:translate-x-1 rtl:rotate-180 rtl:group-hover:-translate-x-1" />
</ div >
</ div >
</ div >
</ a >
</ CarouselItem >
))}
</ CarouselContent >
</ Carousel >
< div className = "mt-8 flex justify-center gap-2" >
{ items . map (( _ , index ) => (
< button
key = { index }
type = "button"
className = { `h-2 w-2 rounded-full transition-colors ${
currentSlide === index ? "bg-primary" : "bg-primary/20"
} ` }
onClick = {() => carouselApi ? . scrollTo ( index )}
aria-label = { `رفتن به اسلاید ${ ( index + 1 ). toLocaleString ( "fa-IR" ) } ` }
/>
))}
</ div >
</ div >
</ section >
);
};
export { Gallery4 };