All components

Testimonal Slider

Testimonal Slider is a React and Tailwind CSS form component from Eldora UI, licensed MIT. Copy it and paste it into your project.

Eldora UI MIT forms

What it is

Testimonal Slider is a React and Tailwind CSS form component from Eldora UI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

Needs

npm i react @headlessui/react

Licence

This component comes from Eldora UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Testimonal Slider

registry/eldoraui/testimonal-slider.tsx
"use client"

import { useEffect, useRef, useState } from "react"
import Image from "next/image"
import { Transition } from "@headlessui/react"

interface Testimonial {
  img: string
  quote: string
  name: string
  role: string
}

export default function FancyTestimonialsSlider({
  testimonials,
  autorotateTiming = 7000,
}: {
  testimonials: Testimonial[]
  autorotateTiming?: number
}) {
  const testimonialsRef = useRef<HTMLDivElement>(null)
  const [active, setActive] = useState<number>(0)
  const [autorotate, setAutorotate] = useState<boolean>(true)

  useEffect(() => {
    if (!autorotate) return
    const interval = setInterval(() => {
      setActive(active + 1 === testimonials.length ? 0 : (active) => active + 1)
    }, autorotateTiming)
    return () => clearInterval(interval)
  }, [active, autorotate])

  const heightFix = () => {
    if (testimonialsRef.current && testimonialsRef.current.parentElement)
      testimonialsRef.current.parentElement.style.height = `${testimonialsRef.current.clientHeight}px`
  }

  useEffect(() => {
    heightFix()
  }, [])

  return (
    <div className="mx-auto w-full max-w-3xl px-4 text-center sm:px-6">
      {/* Testimonial image */}
      <div className="relative h-24 sm:h-32">
        <div className="pointer-events-none absolute top-0 left-1/2 h-[320px] w-[320px] -translate-x-1/2 before:absolute before:inset-0 before:-z-10 before:rounded-full before:bg-gradient-to-b before:from-cyan-500/25 before:via-cyan-500/5 before:via-25% before:to-cyan-500/0 before:to-75% sm:h-[480px] sm:w-[480px]">
          <div className="h-24 [mask-image:_linear-gradient(0deg,transparent,theme(colors.white)_20%,theme(colors.white))] sm:h-32">
            {testimonials.map((testimonial, index) => (
              <Transition
                as="div"
                key={index}
                show={active === index}
                className="absolute inset-0 -z-10 h-full"
                enter="transition ease-[cubic-bezier(0.68,-0.3,0.32,1)] duration-700 order-first"
                enterFrom="opacity-0 -rotate-[60deg]"
                enterTo="opacity-100 rotate-0"
                leave="transition ease-[cubic-bezier(0.68,-0.3,0.32,1)] duration-700"
                leaveFrom="opacity-100 rotate-0"
                leaveTo="opacity-0 rotate-[60deg]"
                beforeEnter={() => heightFix()}
              >
                <Image
                  className="relative top-8 left-1/2 -translate-x-1/2 rounded-full sm:top-11"
                  src={testimonial.img}
                  width={48}
                  height={48}
                  alt={testimonial.name}
                />
              </Transition>
            ))}
          </div>
        </div>
      </div>
      {/* Text */}
      <div className="mb-6 transition-all delay-300 duration-150 ease-in-out sm:mb-9">
        <div className="relative flex flex-col" ref={testimonialsRef}>
          {testimonials.map((testimonial, index) => (
            <Transition
              key={index}
              show={active === index}
              enter="transition ease-in-out duration-500 delay-200 order-first"
              enterFrom="opacity-0 -translate-x-4"
              enterTo="opacity-100 translate-x-0"
              leave="transition ease-out duration-300 delay-300 absolute"
              leaveFrom="opacity-100 translate-x-0"
              leaveTo="opacity-0 translate-x-4"
              beforeEnter={() => heightFix()}
            >
              <div className="px-2 text-lg font-bold text-cyan-900 before:content-['\201C'] after:content-['\201D'] sm:px-0 sm:text-xl lg:text-2xl">
                {testimonial.quote}
              </div>
            </Transition>
          ))}
        </div>
      </div>
      {/* Buttons */}
      <div className="-m-1 flex flex-wrap justify-center gap-1 sm:gap-1.5">
        {testimonials.map((testimonial, index) => (
          <button
            key={index}
            className={`m-1 inline-flex justify-center rounded-full px-2 py-1 text-xs whitespace-nowrap shadow-sm transition-colors duration-150 focus-visible:ring focus-visible:ring-cyan-300 focus-visible:outline-none sm:px-3 sm:py-1.5 sm:text-xs dark:focus-visible:ring-cyan-600 ${active === index ? "bg-cyan-500 text-white shadow-cyan-950/10" : "bg-white text-cyan-900 hover:bg-cyan-100"}`}
            onClick={() => {
              setActive(index)
              setAutorotate(false)
            }}
          >
            <span className="truncate">{testimonial.name}</span>{" "}
            <span
              className={`${active === index ? "text-cyan-200" : "text-cyan-300"}`}
            >
              -
            </span>{" "}
            <span className="truncate">{testimonial.role}</span>
          </button>
        ))}
      </div>
    </div>
  )
}

Similar components