Files
mr_decor/components/sections/feature/feature-carousel.tsx
T

171 lines
7.3 KiB
TypeScript

"use client";
import { ChevronUp } from "lucide-react";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { useContent } from "@/components/layout/content-provider";
import type { CarouselApi } from "@/components/ui/carousel";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
const MOBILE_BREAKPOINT = 768;
const AUTOPLAY_DELAY_MS = 3200;
const FeatureCarousel = () => {
const content = useContent();
const { featureCarousel } = content.pages.home;
const carouselItems = featureCarousel.items;
const [openCardIndex, setOpenCardIndex] = useState<number | null>(null);
const [isMobile, setIsMobile] = useState(false);
const [carouselApi, setCarouselApi] = useState<CarouselApi | null>(null);
useEffect(() => {
const mq = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const handler = () => setIsMobile(mq.matches);
handler();
mq.addEventListener("change", handler);
return () => mq.removeEventListener("change", handler);
}, []);
useEffect(() => {
if (!isMobile || !carouselApi) return;
const timer = setInterval(() => {
carouselApi.scrollNext();
}, AUTOPLAY_DELAY_MS);
return () => clearInterval(timer);
}, [isMobile, carouselApi]);
const handleCardClick = (index: number) => {
if (!isMobile) return;
setOpenCardIndex((prev) => (prev === index ? null : index));
};
return (
<section id="rahavarha" className="py-32">
<div className="relative container mx-auto max-w-7xl px-6">
<h2 className="text-heading-2 z-10 mb-10 text-center font-semibold tracking-tight text-foreground">
{featureCarousel.heading}
</h2>
<Carousel
opts={{
align: "center",
loop: true,
duration: 20,
}}
setApi={setCarouselApi}
className="w-full"
dir="ltr"
>
<CarouselContent className="py-5">
{carouselItems.map((item, index) => (
<CarouselItem
key={index}
className="text-background select-none md:basis-1/2 lg:basis-1/3 dark:text-foreground"
>
<DottedDiv className="group h-150 w-full mx-auto p-4">
<div
className={cn(
"group/card relative h-full w-full bg-muted/50 p-4 transition-colors duration-300 ease-out group-hover:bg-muted",
isMobile && "cursor-pointer"
)}
data-open={openCardIndex === index}
onClick={() => handleCardClick(index)}
role={isMobile ? "button" : undefined}
aria-expanded={isMobile ? openCardIndex === index : undefined}
>
<div className="relative h-full w-full overflow-hidden rounded-3xl">
<img
src={item.imageSrc}
alt={item.title}
className="h-full w-full object-cover transition-transform duration-500 ease-out group-hover:scale-105 group-active:scale-105 md:group-hover:scale-105 md:group-active:scale-105"
/>
{/* Overlay: hover on desktop, open state on mobile */}
<div
className={cn(
"absolute inset-0 bg-black/0 backdrop-blur-none transition-all duration-300 ease-out",
"md:group-hover:bg-black/50 md:group-hover:backdrop-blur-md md:group-active:bg-black/50 md:group-active:backdrop-blur-md",
"max-md:group-data-[open=true]/card:bg-black/50 max-md:group-data-[open=true]/card:backdrop-blur-md"
)}
aria-hidden
/>
{/* Text overlay */}
<div
className={cn(
"absolute inset-4 -ml-4 flex flex-col items-center justify-between p-6 sm:p-8 opacity-0 translate-y-4 transition-all duration-300 ease-out",
"md:group-hover:opacity-100 md:group-hover:translate-y-0 md:group-active:opacity-100 md:group-active:translate-y-0",
"max-md:group-data-[open=true]/card:opacity-100 max-md:group-data-[open=true]/card:translate-y-0"
)}
>
<div className="flex flex-1 flex-col items-center justify-center pointer-events-none">
<h3 className="text-heading-3 text-center font-semibold tracking-tight text-background dark:text-foreground">
{item.title}
</h3>
<div className="mx-auto mt-2 h-1 w-6 rounded-full bg-background/90 dark:bg-foreground/90" />
<p className="text-body-sm mt-6 max-w-sm px-1 text-center leading-relaxed tracking-tight text-background/90 dark:text-foreground/90">
{item.description}
</p>
</div>
<Link
href={item.link}
onClick={(e) => e.stopPropagation()}
className="pointer-events-auto group/cta mb-2 flex cursor-pointer flex-col items-center justify-center transition-transform duration-300 ease-out group-hover/cta:-translate-y-1"
>
<ChevronUp
size={28}
className="text-background dark:text-foreground"
/>
<p className="text-body mt-1 font-medium tracking-tight text-background dark:text-foreground">
{featureCarousel.viewDetails}
</p>
</Link>
</div>
</div>
</div>
</DottedDiv>
</CarouselItem>
))}
</CarouselContent>
<div className="mt-5 flex justify-end gap-3">
<CarouselPrevious className="static size-12" />
<CarouselNext variant="default" className="static size-12" />
</div>
</Carousel>
</div>
</section>
);
};
export { FeatureCarousel };
const DottedDiv = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => (
<div className={cn("relative", className)}>
<div className="absolute top-4 -left-[28px] h-[1.5px] w-[100%] bg-muted" />
<div className="absolute bottom-4 -left-[28px] h-[1.5px] w-[100%] bg-muted" />
<div className="absolute -top-25 left-4 h-[130%] w-[1.5px] bg-muted" />
<div className="absolute -top-25 right-4 h-[130%] w-[1.5px] bg-muted" />
<div className="absolute top-[12.5px] left-[12.5px] z-10 size-2 rounded-full bg-foreground" />
<div className="absolute top-[12.5px] right-[12.5px] z-10 size-2 rounded-full bg-foreground" />
<div className="absolute bottom-[12.5px] left-[12.5px] z-10 size-2 rounded-full bg-foreground" />
<div className="absolute right-[12.5px] bottom-[12.5px] z-10 size-2 rounded-full bg-foreground" />
{children}
</div>
);