hovertestimonial

testimonial

Words from Others

What our happy clients say

Lorem ipsum dolor sit amet consectetur adipisicing elit nam maxime quas fugiat tempore blanditiis, eveniet quia accusantium.

Daniella Doe

Daniella Doe

Mobile dev

Jane doe

Jane doe

Marketing

Jane doe

Jane doe

Marketing

Jane doe

Jane doe

Marketing

Jane doe

Jane doe

Marketing

Jane doe

Jane doe

Marketing

Installation

Copy the source code

components/ui/hovertestimonialcard.tsx
import { FC, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { cn } from '@/lib/utils';

interface TestimonialCardProps {
    name: string;
    description: string;
    image: string;
    profession: string;
    stars: number;
}

const TestimonialCard: FC<TestimonialCardProps> = ({
    name,
    description,
    image,
    profession,
    stars
}) => {
    const [isHover, setHover] = useState(false);

    const CardVariants = {
        open: { height: "auto", opacity: 1, y: "-110%" },
        close: { height: 0, opacity: 0, y: "30%" },
    };

    return (
        <div
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            className='relative'
        >
            <AnimatePresence>
                {isHover && (
                    <motion.div
                        variants={CardVariants}
                        className='absolute border shadow-inner bg-gradient-to-r from-slate-900 to-slate-700 shadow-white/70 rounded-2xl'
                        initial="close"
                        animate={isHover ? "open" : "close"}
                        exit="close"
                        transition={{ duration: 0.5, type: 'spring', stiffness: 150 }}
                    >
                        <p className="text-white text-sm font-light m-1 bg-black p-3 text-center shadow-md rounded-2xl">
                            {description}
                        </p>
                    </motion.div>
                )}
            </AnimatePresence>
            <div className='flex cursor-pointer justify-center items-center border shadow-inner dark:bg-white/20 backdrop-blur-xl bg-black dark:shadow-black/30 shadow-white rounded-2xl'>
                <div className={cn('m-0.5 bg-white dark:bg-black shadow-lg rounded-xl flex border transition-all duration-150 ease-in-out', isHover ? "rotate-0" : "even:rotate-2 odd:-rotate-2")}>
                    <div>
                        <img
                            className="block h-full w-12 rounded-l-xl object-cover"
                            src={image}
                            alt={name}
                        />
                    </div>
                    <div className='my-2 mx-3'>
                        <div className="flex w-full gap-4">
                            <h3 className="m-0 text-sm font-semibold text-gray-900 dark:text-gray-100">
                                {name}
                            </h3>
                            <p className="font-regular m-0 text-center text-sm text-gray-600 dark:text-gray-400">
                                {profession}
                            </p>
                        </div>
                        <div className='flex mt-2 -translate-x-1 mb-0 justify-start items-center'>
                            {Array(5).fill(0).map((_, i) => (
                                <svg
                                    key={i}
                                    className={cn("w-3 h-3 ms-1", i + 1 <= stars ? "text-yellow-300" : "text-gray-300 dark:text-gray-500")}
                                    aria-hidden="true"
                                    xmlns="http://www.w3.org/2000/svg"
                                    fill="currentColor"
                                    viewBox="0 0 22 20"
                                >
                                    <path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z" />
                                </svg>
                            ))}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default TestimonialCard;
components/ui/hovertestimonialheader.tsx
import { motion } from 'framer-motion';

const FADE_DOWN_ANIMATION_VARIANTS = {
    hidden: { opacity: 0, y: -10 },
    show: { opacity: 1, y: 0, transition: { duration: 1, type: "spring" } },
};

const TestimonialHeader = () => (
    <motion.div
        initial="hidden"
        whileInView="show"
        viewport={{ once: true }}
        variants={{
            hidden: {},
            show: {
                transition: {
                    staggerChildren: 0.3,
                },
            },
        }}
        className='max-w-3xl flex flex-col items-center px-5 lg:px-0'
    >
        <motion.p
            variants={FADE_DOWN_ANIMATION_VARIANTS}
            className='mb-5 rounded-2xl p-2 w-40 font-medium text-sm border text-center bg-black shadow-inner shadow-white text-white'
        >
            Words from Others
        </motion.p>
        <motion.h1
            variants={FADE_DOWN_ANIMATION_VARIANTS}
            className="text-xl md:text-4xl font-bold text-center"
        >
            What our happy clients say
        </motion.h1>
        <motion.h1
            variants={FADE_DOWN_ANIMATION_VARIANTS}
            className="pt-6 pb-8 text-base max-w-2xl text-center m-auto"
        >
            Lorem ipsum dolor sit amet consectetur adipisicing elit nam maxime quas fugiat tempore blanditiis, eveniet quia accusantium.
        </motion.h1>
    </motion.div>
);

export default TestimonialHeader;