Testimonials Slider
Rendered Component
Loading component...
Packages
npm install framer-motion
npm install @fortawesome/react-fontawesome
npm install @fortawesome/free-solid-svg-icons
npm install @fortawesome/free-brands-svg-icons
yarn add framer-motion
yarn add @fortawesome/react-fontawesome
yarn add @fortawesome/free-solid-svg-icons
yarn add @fortawesome/free-brands-svg-icons
Code
'use client';
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronLeft, ChevronRight, Star } from 'lucide-react';
const testimonials = [
{
id: 1,
name: 'Alice Johnson',
role: 'UX Designer',
company: 'TechCorp',
content:
"This product has transformed our design process. It\'s intuitive and powerful!",
rating: 5,
image:
'https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 2,
name: 'Bob Smith',
role: 'Senior Developer',
company: 'WebSolutions',
content:
"I\'ve never seen a tool that boosts productivity like this. Absolutely game-changing.",
rating: 4,
image:
'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 3,
name: 'Carol White',
role: 'Product Manager',
company: 'InnovateCo',
content:
"From ideation to launch, this product has streamlined our entire workflow. The impact on our team\'s productivity has been nothing short of remarkable.",
rating: 5,
image:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
];
const TestimonialCard = ({ testimonial, isActive, onClick }) => {
return (
<motion.div
className={`bg-gradient-to-br from-purple-700 to-pink-500 p-[2px] rounded-xl shadow-lg h-full ${
!isActive ? 'cursor-pointer' : ''
}`}
whileHover={{ scale: isActive ? 1.05 : 1.02 }}
onClick={onClick}
>
<div className='bg-zinc-900 p-4 sm:p-6 rounded-xl h-full flex flex-col relative overflow-hidden'>
<motion.div
className='absolute inset-0 bg-gradient-to-br from-purple-600/20 to-pink-500/20'
initial={{ opacity: 0 }}
animate={{ opacity: isActive ? 1 : 0 }}
transition={{ duration: 0.3 }}
/>
<div className='flex items-center mb-4 relative z-10'>
<motion.img
src={testimonial.image}
alt={testimonial.name}
className='w-12 h-12 sm:w-16 sm:h-16 rounded-full mr-3 sm:mr-4 object-cover'
whileHover={{ scale: 1.1 }}
/>
<div>
<motion.h3
className='font-bold text-white text-sm sm:text-base'
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.1 }}
>
{testimonial.name}
</motion.h3>
<motion.p
className='text-xs sm:text-sm text-zinc-400'
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
{testimonial.role} at {testimonial.company}
</motion.p>
</div>
</div>
<div className='custom-scrollbar flex-grow overflow-y-auto mb-4 relative z-10'>
<motion.p
className='text-zinc-300 text-sm sm:text-base'
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.3 }}
>
{testimonial.content}
</motion.p>
</div>
<div className='flex relative z-10'>
{[...Array(5)].map((_, i) => (
<motion.div
key={i}
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.4 + i * 0.1 }}
>
<Star
className={`w-4 h-4 sm:w-5 sm:h-5 ${
i < testimonial.rating
? 'text-yellow-400'
: 'text-zinc-600'
}`}
fill={i < testimonial.rating ? 'currentColor' : 'none'}
/>
</motion.div>
))}
</div>
</div>
</motion.div>
);
};
const TestimonialSlider = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const paginate = (newDirection) => {
setCurrentIndex((prevIndex) => {
let newIndex = prevIndex + newDirection;
if (newIndex < 0) newIndex = testimonials.length - 1;
if (newIndex >= testimonials.length) newIndex = 0;
return newIndex;
});
};
return (
<div className='relative w-full max-w-5xl mx-auto overflow-hidden py-8'>
<div className='flex items-center justify-center h-[400px] sm:h-[450px]'>
<AnimatePresence initial={false} custom={currentIndex}>
{[-1, 0, 1].map((offset) => {
const index =
(currentIndex + offset + testimonials.length) %
testimonials.length;
return (
<motion.div
key={testimonials[index].id}
custom={offset}
className='absolute w-full sm:w-[calc(100%-2rem)] max-w-sm sm:max-w-md'
initial={(custom) => ({
scale: custom === 0 ? 0.9 : 0.5,
opacity: custom === 0 ? 0.8 : 0.3,
x: \`${custom * 100}%\`,
})}
animate={(custom) => ({
scale: custom === 0 ? 1 : 0.65,
opacity: custom === 0 ? 1 : 0.5,
x: \`${custom * 75}%\`,
y: custom === 0 ? 0 : 30,
zIndex: custom === 0 ? 3 : 1,
})}
exit={(custom) => ({
scale: custom === 0 ? 0.9 : 0.5,
opacity: custom === 0 ? 0.8 : 0.3,
x: \`${custom * -100}%\`,
})}
transition={{ duration: 0.5 }}
>
<TestimonialCard
testimonial={testimonials[index]}
isActive={offset === 0}
onClick={() => offset !== 0 && setCurrentIndex(index)}
/>
</motion.div>
);
})}
</AnimatePresence>
</div>
<div className='absolute bottom-2 left-0 right-0 flex justify-center space-x-2 mt-4'>
{testimonials.map((_, index) => (
<button
key={index}
className={`md:w-3 md:h-3 h-5 w-5 rounded-full ${
currentIndex === index ? 'bg-purple-600' : 'bg-zinc-600'
}`}
onClick={() => setCurrentIndex(index)}
/>
))}
</div>
</div>
);
};
export default TestimonialSlider;
Details
The Terminal Window component is a fun and interactive way to display text-based information. It emulates the look and feel of a retro terminal interface, complete with a blinking cursor and customizable themes.
Usage
Import the TerminalWindow component and provide it with the necessary text content and styling options.
Examples
- Testimonials carousel on a landing page
- Customer reviews section
- Product testimonials slider