{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":["lucide-react","motion"],"description":"Grid testimonials block with navigation arrows","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport {\n  Avatar,\n  AvatarFallback,\n  AvatarImage,\n} from \"@/components/ui/avatar\";\nimport { getAvatarUrl, getTestimonials } from \"@/lib/smoothui-data\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useCallback, useEffect, useState } from \"react\";\n\nconst testimonials = getTestimonials(7);\n\nexport function TestimonialsGrid() {\n  const shouldReduceMotion = useReducedMotion();\n  const [active, setActive] = useState(0);\n  const [autoplay] = useState(false);\n\n  const handleNext = useCallback(() => {\n    setActive((prev) => (prev + 1) % testimonials.length);\n  }, []);\n\n  const handlePrev = () => {\n    setActive((prev) => (prev - 1 + testimonials.length) % testimonials.length);\n  };\n\n  const isActive = (index: number) => index === active;\n\n  useEffect(() => {\n    if (autoplay) {\n      const interval = setInterval(handleNext, 5000);\n      return () => clearInterval(interval);\n    }\n  }, [autoplay, handleNext]);\n\n  return (\n    <section>\n      <div className=\"min-h-auto bg-muted py-24\">\n        <div className=\"container mx-auto w-full max-w-6xl px-6\">\n          <motion.div\n            animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}\n            className=\"mb-12\"\n            initial={\n              shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 20 }\n            }\n            transition={\n              shouldReduceMotion\n                ? { duration: 0 }\n                : { duration: 0.6, ease: [0.22, 1, 0.36, 1] }\n            }\n          >\n            {/* Layout: Title on left, Testimonial on right */}\n            <div className=\"grid grid-cols-1 gap-8 lg:grid-cols-2 lg:gap-12\">\n              {/* Left side - Title and description */}\n              <div className=\"flex flex-col justify-center\">\n                <h2 className=\"mb-4 font-semibold text-4xl text-foreground\">\n                  What Developers Say\n                </h2>\n                <p className=\"text-balance text-foreground/70 text-lg\">\n                  Join thousands of developers who are building faster, more\n                  beautiful UIs with SmoothUI. See what they&apos;re saying\n                  about their experience.\n                </p>\n              </div>\n              {/* Right side - Testimonial card */}\n              <div className=\"relative flex min-h-fit flex-col items-end\">\n                {/* Navigation Arrows - Above the card */}\n                <div className=\"mb-4 flex justify-center gap-2\">\n                  <motion.button\n                    animate={\n                      shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }\n                    }\n                    aria-label=\"Previous testimonial\"\n                    className=\"group/button flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border bg-background shadow-lg transition-all duration-200 hover:scale-110 hover:shadow-xl\"\n                    initial={\n                      shouldReduceMotion\n                        ? { opacity: 1 }\n                        : { opacity: 0, x: -20 }\n                    }\n                    onClick={handlePrev}\n                    transition={\n                      shouldReduceMotion\n                        ? { duration: 0 }\n                        : { delay: 0.4, duration: 0.3 }\n                    }\n                    type=\"button\"\n                    whileHover={shouldReduceMotion ? {} : { scale: 1.1 }}\n                    whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}\n                  >\n                    <ChevronLeft className=\"h-5 w-5 text-foreground transition-transform duration-300 group-hover/button:-rotate-12\" />\n                  </motion.button>\n                  <motion.button\n                    animate={\n                      shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }\n                    }\n                    aria-label=\"Next testimonial\"\n                    className=\"group/button flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border bg-background shadow-lg transition-all duration-200 hover:scale-110 hover:shadow-xl\"\n                    initial={\n                      shouldReduceMotion\n                        ? { opacity: 1 }\n                        : { opacity: 0, x: 20 }\n                    }\n                    onClick={handleNext}\n                    transition={\n                      shouldReduceMotion\n                        ? { duration: 0 }\n                        : { delay: 0.4, duration: 0.3 }\n                    }\n                    type=\"button\"\n                    whileHover={shouldReduceMotion ? {} : { scale: 1.1 }}\n                    whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}\n                  >\n                    <ChevronRight className=\"h-5 w-5 text-foreground transition-transform duration-300 group-hover/button:rotate-12\" />\n                  </motion.button>\n                </div>\n                <div className=\"relative h-full w-full max-w-md\">\n                  <AnimatePresence>\n                    {testimonials.map((testimonial, index) => (\n                      <motion.div\n                        animate={\n                          shouldReduceMotion\n                            ? { opacity: isActive(index) ? 1 : 0 }\n                            : {\n                                opacity: isActive(index) ? 1 : 0,\n                                scale: isActive(index) ? 1 : 0.95,\n                                y: isActive(index) ? 0 : 30,\n                              }\n                        }\n                        className={`absolute inset-0 min-h-fit ${isActive(index) ? \"z-10\" : \"z-0\"}`}\n                        exit={\n                          shouldReduceMotion\n                            ? { opacity: 0, transition: { duration: 0 } }\n                            : {\n                                opacity: 0,\n                                scale: 0.9,\n                                y: -30,\n                              }\n                        }\n                        initial={\n                          shouldReduceMotion\n                            ? { opacity: 0 }\n                            : {\n                                opacity: 0,\n                                scale: 0.9,\n                                y: 30,\n                              }\n                        }\n                        key={testimonial.name}\n                        transition={\n                          shouldReduceMotion\n                            ? { duration: 0 }\n                            : {\n                                duration: 0.4,\n                                ease: \"easeInOut\",\n                              }\n                        }\n                      >\n                        <div className=\"rounded-2xl border bg-background px-6 py-6 shadow-lg transition-all duration-200\">\n                          <motion.p\n                            animate={\n                              shouldReduceMotion\n                                ? { opacity: 1 }\n                                : { opacity: 1, y: 0 }\n                            }\n                            className=\"mb-6 text-foreground text-lg\"\n                            initial={\n                              shouldReduceMotion\n                                ? { opacity: 1 }\n                                : { opacity: 0, y: 10 }\n                            }\n                            key={active}\n                            transition={\n                              shouldReduceMotion\n                                ? { duration: 0 }\n                                : { duration: 0.3 }\n                            }\n                          >\n                            {(testimonial.content || \"\")\n                              .split(\" \")\n                              .map((word, wordIndex) => (\n                                <motion.span\n                                  animate={\n                                    shouldReduceMotion\n                                      ? { opacity: 1 }\n                                      : {\n                                          filter: \"blur(0px)\",\n                                          opacity: 1,\n                                          y: 0,\n                                        }\n                                  }\n                                  className=\"inline-block\"\n                                  initial={\n                                    shouldReduceMotion\n                                      ? { opacity: 1 }\n                                      : {\n                                          filter: \"blur(4px)\",\n                                          opacity: 0,\n                                          y: 5,\n                                        }\n                                  }\n                                  key={`${testimonial.name}-word-${wordIndex}`}\n                                  transition={\n                                    shouldReduceMotion\n                                      ? { duration: 0 }\n                                      : {\n                                          delay: wordIndex * 0.02,\n                                          duration: 0.2,\n                                          ease: \"easeInOut\",\n                                        }\n                                  }\n                                >\n                                  {word}&nbsp;\n                                </motion.span>\n                              ))}\n                          </motion.p>\n                          <motion.div\n                            animate={\n                              shouldReduceMotion\n                                ? { opacity: 1 }\n                                : { opacity: 1, y: 0 }\n                            }\n                            className=\"flex items-center gap-3\"\n                            initial={\n                              shouldReduceMotion\n                                ? { opacity: 1 }\n                                : { opacity: 0, y: 10 }\n                            }\n                            transition={\n                              shouldReduceMotion\n                                ? { duration: 0 }\n                                : { delay: 0.2, duration: 0.3 }\n                            }\n                          >\n                            <Avatar className=\"size-8 border border-transparent shadow ring-1 ring-foreground/10\">\n                              <AvatarImage\n                                alt={testimonial.name}\n                                src={getAvatarUrl(testimonial.avatar, 64)}\n                              />\n                              <AvatarFallback>\n                                {testimonial.name.charAt(0)}\n                              </AvatarFallback>\n                            </Avatar>\n                            <div>\n                              <div className=\"font-semibold text-foreground\">\n                                {testimonial.name}\n                              </div>\n                              <span className=\"text-muted-foreground text-sm\">\n                                {testimonial.role}\n                              </span>\n                            </div>\n                          </motion.div>\n                        </div>\n                      </motion.div>\n                    ))}\n                  </AnimatePresence>\n                </div>\n              </div>\n            </div>\n          </motion.div>\n        </div>\n      </div>\n    </section>\n  );\n}\n\nexport default TestimonialsGrid;\n","path":"index.tsx","target":"components/smoothui/testimonials-2/index.tsx","type":"registry:block"}],"name":"testimonials-2","registryDependencies":["avatar","https://smoothui.dev/r/data.json"],"title":"Testimonials 2","type":"registry:block"}