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

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

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;