"use client";
import { Button } from "@base-ui/react/button";
import { AnimatePresence, motion } from "framer-motion";
import { ChevronLeft, ChevronRight, Quote, Star } from "lucide-react";
import { useState } from "react";
const testimonials = [
{
id: 1,
name: "Sarah Johnson",
role: "CEO, TechCorp",
content:
"This library has transformed how we build user interfaces. The animations are smooth, professional, and easy to implement.",
rating: 5,
},
{
id: 2,
name: "Michael Chen",
role: "Lead Developer, StartupXYZ",
content:
"Best animation library we've used. The components are production-ready and the documentation is excellent.",
rating: 5,
},
{
id: 3,
name: "Emily Rodriguez",
role: "Designer, Creative Studio",
content:
"Beautiful animations that bring our designs to life. Highly recommended for any modern web project.",
rating: 5,
},
];
export function TestimonialSectionBaseui() {
const [currentIndex, setCurrentIndex] = useState(0);
const nextTestimonial = () => {
setCurrentIndex((prev) => (prev + 1) % testimonials.length);
};
const prevTestimonial = () => {
setCurrentIndex(
(prev) => (prev - 1 + testimonials.length) % testimonials.length
);
};
return (
<div className="w-full px-4 py-16">
<div className="mx-auto max-w-4xl">
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="mb-12 text-center"
>
<h2 className="mb-4 text-3xl font-bold sm:text-4xl">
What Our Users Say
</h2>
<p className="text-sm text-foreground/70 sm:text-base md:text-lg">
Don't just take our word for it - hear from our community
</p>
</motion.div>
<div className="relative">
<AnimatePresence mode="wait">
<motion.div
key={currentIndex}
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -50 }}
transition={{ duration: 0.3 }}
>
<div className="rounded-lg border border-border bg-card text-card-foreground shadow-sm">
<div className="flex flex-col space-y-1.5 p-6">
<div className="mb-4 flex items-center gap-2">
{[...Array(testimonials[currentIndex].rating)].map(
(_, i) => (
<motion.div
key={i}
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ delay: i * 0.1, type: "spring" }}
>
<Star className="h-5 w-5 fill-yellow-400 text-yellow-400" />
</motion.div>
)
)}
</div>
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, type: "spring" }}
className="mb-2 inline-flex"
>
<Quote className="h-6 w-6 text-muted-foreground" />
</motion.div>
</div>
<div className="p-6 pt-0">
<p className="mb-6 text-base leading-relaxed text-muted-foreground sm:text-lg">
{testimonials[currentIndex].content}
</p>
<div className="flex items-center justify-between">
<div>
<p className="font-semibold">
{testimonials[currentIndex].name}
</p>
<p className="text-sm text-foreground/60">
{testimonials[currentIndex].role}
</p>
</div>
</div>
</div>
</div>
</motion.div>
</AnimatePresence>
<div className="mt-8 flex items-center justify-center gap-4">
<Button
onClick={prevTestimonial}
className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-input bg-background transition-colors hover:bg-accent hover:text-accent-foreground"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<div className="flex gap-2">
{testimonials.map((_, index) => (
<motion.button
key={index}
onClick={() => setCurrentIndex(index)}
className="h-2 rounded-full"
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
initial={{ width: 8 }}
animate={{ width: index === currentIndex ? 24 : 8 }}
style={{
backgroundColor:
index === currentIndex
? "hsl(var(--accent))"
: "hsl(var(--border))",
}}
/>
))}
</div>
<Button
onClick={nextTestimonial}
className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-input bg-background transition-colors hover:bg-accent hover:text-accent-foreground"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
);
}
npx shadcn@latest add @uitripled/testimonial-section-baseuiUsage varies by registry entry. Refer to the registry docs or source files below for details.