feat(carousel): add clickable carousel indicators

This commit is contained in:
shafin-r
2025-12-08 18:59:29 +06:00
parent 7ce4a6e4f8
commit ce071622a6
3 changed files with 218 additions and 7 deletions

View File

@ -1,3 +1,4 @@
import ServiceCarousel from "@/components/ServiceCarousel";
import { import {
Carousel, Carousel,
CarouselContent, CarouselContent,
@ -347,13 +348,7 @@ export default function Home() {
Strategic excellence across every dimension of modern PR Strategic excellence across every dimension of modern PR
</h5> </h5>
<section className="flex flex-col items-start space-y-7"> <section className="flex flex-col items-start space-y-7">
<Carousel className="w-full h-fit "> <ServiceCarousel />
<CarouselContent>
{serviceCarousel.map(({ id, content }) => (
<CarouselItem key={id}>{content}</CarouselItem>
))}
</CarouselContent>
</Carousel>
</section> </section>
</section> </section>
{/*Approach*/} {/*Approach*/}

View File

@ -0,0 +1,139 @@
"use client";
import { useState } from "react";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import CurrentSlide from "@/hooks/CurrentSlide";
import Image from "next/image";
export default function CarouselWrapper() {
const serviceCarousel = [
{
id: 0,
content: (
<>
<section className="lg:flex lg:flex-row-reverse lg:justify-between space-y-6 lg:items-center">
<div className="lg:w-1/2">
<Image
src={"/images/campaign-strategy.png"}
width={2464}
height={1672}
alt="campaign-strategy"
/>
</div>
<div className="space-y-4 lg:w-1/3 lg:space-y-12">
<p className="px-6 py-2 lg:px-9 lg:py-4 lg:text-xl text-[#7B2E45] font-lato border-2 border-[#7B2E45] rounded-full w-fit">
01
</p>
<h2 className="text-4xl lg:text-7xl text-white font-lato">
Campaign Strategy
</h2>
<p className="text-lg lg:text-2xl font-lato text-white/60">
Comprehensive planning from research to execution, crafted to
win hearts and minds.
</p>
<div className="flex gap-4 w-full">
<div className="bg-[#7B2E45]/80 rounded-full w-1/4 h-1 lg:w-[60px]" />
<div className="bg-[#7B2E45]/65 rounded-full w-1/5 h-1 lg:w-[50px]" />
<div className="bg-[#7B2E45]/50 rounded-full w-1/6 h-1 lg:w-[40px]" />
<div className="bg-[#7B2E45]/35 rounded-full w-1/7 h-1 lg:w-[30px]" />
<div className="bg-[#7B2E45]/20 rounded-full w-1/8 h-1 lg:w-[20px]" />
</div>
</div>
</section>
</>
),
},
{
id: 1,
content: (
<>
<section className="lg:flex lg:flex-row-reverse lg:justify-between space-y-6 lg:items-center">
<div className="lg:w-1/2">
<Image
src={"/images/media-production.png"}
width={2464}
height={1672}
alt="media-production"
/>
</div>
<div className="space-y-4 lg:w-1/3 lg:space-y-12">
<p className="px-6 py-2 lg:px-9 lg:py-4 lg:text-xl text-[#7B2E45] font-lato border-2 border-[#7B2E45] rounded-full w-fit">
02
</p>
<h2 className="text-4xl lg:text-7xl text-white font-lato">
Media Production
</h2>
<p className="text-lg font-lato text-white/60">
Cinematic storytelling through video, photography, and
multimedia content that captivates.
</p>
<div className="flex gap-4 w-full">
<div className="bg-[#7B2E45]/80 rounded-full w-1/4 h-1 lg:w-[60px]" />
<div className="bg-[#7B2E45]/65 rounded-full w-1/5 h-1 lg:w-[50px]" />
<div className="bg-[#7B2E45]/50 rounded-full w-1/6 h-1 lg:w-[40px]" />
<div className="bg-[#7B2E45]/35 rounded-full w-1/7 h-1 lg:w-[30px]" />
<div className="bg-[#7B2E45]/20 rounded-full w-1/8 h-1 lg:w-[20px]" />
</div>
</div>
</section>
</>
),
},
{
id: 2,
content: (
<>
<section className="lg:flex lg:flex-row-reverse lg:justify-between space-y-6 lg:items-center">
<div className="lg:w-1/2">
<Image
src={"/images/research.png"}
width={2464}
height={1672}
alt="research"
/>
</div>
<div className="space-y-4 lg:w-1/3 lg:space-y-12">
<p className="px-6 py-2 lg:px-9 lg:py-4 lg:text-xl text-[#7B2E45] font-lato border-2 border-[#7B2E45] rounded-full w-fit">
03
</p>
<h2 className="text-4xl lg:text-7xl text-white font-lato">
Research & Analytics
</h2>
<p className="text-lg font-lato text-white/60">
Data-driven insights that inform every decision and optimize
every outcome
</p>
<div className="flex gap-4 w-full">
<div className="bg-[#7B2E45]/80 rounded-full w-1/4 h-1 lg:w-[60px]" />
<div className="bg-[#7B2E45]/65 rounded-full w-1/5 h-1 lg:w-[50px]" />
<div className="bg-[#7B2E45]/50 rounded-full w-1/6 h-1 lg:w-[40px]" />
<div className="bg-[#7B2E45]/35 rounded-full w-1/7 h-1 lg:w-[30px]" />
<div className="bg-[#7B2E45]/20 rounded-full w-1/8 h-1 lg:w-[20px]" />
</div>
</div>
</section>
</>
),
},
];
const [api, setApi] = useState<any>();
return (
<>
<Carousel setApi={setApi} className="w-full h-fit">
<CarouselContent>
{serviceCarousel.map(({ id, content }) => (
<CarouselItem key={id}>{content}</CarouselItem>
))}
</CarouselContent>
</Carousel>
{/* All slide reading logic happens inside this component */}
<CurrentSlide api={api} />
</>
);
}

77
hooks/CurrentSlide.tsx Normal file
View File

@ -0,0 +1,77 @@
"use client";
import { useEffect, useState } from "react";
import type { CarouselApi } from "@/components/ui/carousel";
export default function CurrentSlide({
api,
total = 3,
}: {
api?: CarouselApi;
total?: number;
}) {
const [current, setCurrent] = useState(0);
useEffect(() => {
if (!api) return;
// Set initial slide
setCurrent(api.selectedScrollSnap());
const handler = () => setCurrent(api.selectedScrollSnap());
api.on("select", handler);
return () => api.off("select", handler);
}, [api]);
const handleClick = (index: number) => {
if (!api) return;
api.scrollTo(index); // embla API command to jump to slide
};
return (
<div className="flex items-center justify-center w-full gap-8 py-6">
{Array.from({ length: total }).map((_, i) => {
const isActive = i === current;
return (
<button
key={i}
onClick={() => handleClick(i)}
className="flex flex-col items-center gap-2 cursor-pointer group"
>
{/* Outer ring */}
<div
className={`
relative w-10 h-10 rounded-full border transition-all duration-300
${isActive ? "border-[#C74C65]" : "border-[#C74C65]/40"}
group-hover:border-[#C74C65]
`}
>
{/* Inner dot (only active) */}
{isActive && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-3 h-3 rounded-full bg-[#C74C65]" />
</div>
)}
</div>
{/* Slide number */}
<span
className={`
font-lato text-md tracking-widest transition-colors duration-300
${
isActive
? "text-[#C74C65]"
: "text-[#C74C65]/40 group-hover:text-[#C74C65]"
}
`}
>
{String(i + 1).padStart(2, "0")}
</span>
</button>
);
})}
</div>
);
}