Testimonials 3
Testimonials 3 é um componente de layout para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
SmoothUI
MIT
layout
O que é
Testimonials 3 é um componente de layout para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i lucide-react motion
Licença
Este componente vem de SmoothUI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Testimonials 3
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
import { cn } from "@/lib/utils";
import { getAvatarUrl, getTestimonials } from "@/lib/smoothui-data";
import { Star } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
const testimonials = getTestimonials(4);
export function TestimonialsStars() {
const shouldReduceMotion = useReducedMotion();
return (
<section>
<div className="py-24">
<div className="container mx-auto w-full max-w-5xl px-6">
<motion.div
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
className="mb-12"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 20 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: { duration: 0.6, ease: [0.22, 1, 0.36, 1] }
}
>
<h2 className="font-semibold text-4xl text-foreground">
Developer Reviews
</h2>
<p className="my-4 text-balance text-lg text-muted-foreground">
See what the community is saying about SmoothUI. Real feedback
from developers building amazing user experiences.
</p>
</motion.div>
<motion.div
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1 }}
className="grid 3xl:grid-cols-3 3xl:gap-12 gap-6 lg:grid-cols-2"
initial={shouldReduceMotion ? { opacity: 1 } : { opacity: 0 }}
transition={
shouldReduceMotion
? { duration: 0 }
: { delay: 0.2, duration: 0.6, ease: [0.22, 1, 0.36, 1] }
}
>
{testimonials.map((testimonial, index) => (
<motion.div
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
className="group rounded-2xl border border-transparent px-4 py-3 duration-200 hover:border-border hover:bg-background/50"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 30 }
}
key={testimonial.name}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15,
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}
}
whileHover={
shouldReduceMotion
? {}
: {
transition: { duration: 0.2, ease: [0.22, 1, 0.36, 1] },
y: -4,
}
}
>
<motion.div
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, scale: 1 }
}
className="flex gap-1"
initial={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 0, scale: 0.8 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.2,
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}
}
>
{Array.from({ length: 5 }).map((_, i) => (
<motion.div
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, scale: 1 }
}
initial={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 0, scale: 0 }
}
key={`${testimonial.name}-star-${i}`}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.2 + i * 0.05,
duration: 0.3,
ease: [0.68, -0.55, 0.265, 1.55],
}
}
>
<Star
className={cn(
"size-4 transition-colors duration-200",
i < (testimonial.stars || 0)
? "fill-accent stroke-accent"
: "fill-primary stroke-border"
)}
/>
</motion.div>
))}
</motion.div>
<motion.p
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
className="my-4 text-foreground"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 10 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.4,
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}
}
>
{testimonial.content}
</motion.p>
<motion.div
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }
}
className="flex items-center gap-2"
initial={
shouldReduceMotion ? { opacity: 1 } : { opacity: 0, x: -10 }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
delay: index * 0.15 + 0.5,
duration: 0.3,
ease: [0.22, 1, 0.36, 1],
}
}
>
<Avatar className="size-6 border border-transparent shadow ring-1 ring-foreground/10">
<AvatarImage
alt={testimonial.name}
src={getAvatarUrl(testimonial.avatar, 48)}
/>
<AvatarFallback>
{testimonial.name.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="font-medium text-foreground text-sm">
{testimonial.name}
</div>
<span
aria-hidden="true"
className="size-1 rounded-full bg-foreground/25"
/>
<span className="text-muted-foreground text-sm">
{testimonial.role}
</span>
</motion.div>
</motion.div>
))}
</motion.div>
</div>
</div>
</section>
);
}
export default TestimonialsStars;