{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":["motion"],"description":"Interactive logo grid with hover effects","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport type React from \"react\";\nimport { useEffect, useState } from \"react\";\nimport {\n  Canpoy,\n  Canva,\n  Casetext,\n  Clearbit,\n  Descript,\n  Duolingo,\n  Faire,\n  Strava,\n} from \"@/components/smoothui/shared\";\n\nexport interface LogoGridProps {\n  columns?: 3 | 4 | 5 | 6;\n  description?: string;\n  logos?: Array<{\n    name: string;\n    logo: React.ReactNode;\n    href?: string;\n  }>;\n  title?: string;\n}\n\nconst DEFAULT_LOGOS = [\n  { logo: <Canpoy />, name: \"Canpoy\" },\n  { logo: <Canva />, name: \"Canva\" },\n  { logo: <Casetext />, name: \"Casetext\" },\n  { logo: <Strava />, name: \"Strava\" },\n  { logo: <Descript />, name: \"Descript\" },\n  { logo: <Duolingo />, name: \"Duolingo\" },\n  { logo: <Faire />, name: \"Faire\" },\n  { logo: <Clearbit />, name: \"Clearbit\" },\n];\n\nconst COLUMN_CLASSES = {\n  3: \"grid-cols-2 sm:grid-cols-3\",\n  4: \"grid-cols-2 sm:grid-cols-4\",\n  5: \"grid-cols-2 sm:grid-cols-3 lg:grid-cols-5\",\n  6: \"grid-cols-2 sm:grid-cols-3 lg:grid-cols-6\",\n};\n\n// Animation constants\nconst HOVER_LIFT_OFFSET = -4;\nconst INACTIVE_OPACITY = 0.6;\n\ninterface LogoItemProps {\n  isHoverDevice: boolean;\n  logo: {\n    name: string;\n    logo: React.ReactNode;\n    href?: string;\n  };\n  shouldReduceMotion: boolean | null;\n}\n\nfunction LogoItem({ logo, isHoverDevice, shouldReduceMotion }: LogoItemProps) {\n  const [isHovered, setIsHovered] = useState(false);\n\n  const content = (\n    <motion.div\n      animate={\n        shouldReduceMotion\n          ? {}\n          : {\n              y: isHovered ? HOVER_LIFT_OFFSET : 0,\n            }\n      }\n      className=\"group relative flex items-center justify-center p-6\"\n      onMouseEnter={() => isHoverDevice && setIsHovered(true)}\n      onMouseLeave={() => isHoverDevice && setIsHovered(false)}\n      transition={\n        shouldReduceMotion\n          ? { duration: 0 }\n          : {\n              bounce: 0.05,\n              duration: 0.25,\n              type: \"spring\" as const,\n            }\n      }\n    >\n      {/* Logo with grayscale effect */}\n      <div\n        className=\"transition-all duration-200 *:fill-foreground\"\n        style={{\n          filter: isHovered ? \"grayscale(0)\" : \"grayscale(1)\",\n          opacity: isHovered ? 1 : INACTIVE_OPACITY,\n        }}\n      >\n        {logo.logo}\n      </div>\n\n      {/* Tooltip */}\n      <AnimatePresence>\n        {isHovered ? (\n          <motion.div\n            animate={{ opacity: 1, scale: 1, x: \"-50%\", y: 0 }}\n            className=\"pointer-events-none absolute -top-2 left-1/2 z-10 whitespace-nowrap rounded-md bg-foreground px-3 py-1.5 font-medium text-background text-sm shadow-lg\"\n            exit={\n              shouldReduceMotion\n                ? { opacity: 0, transition: { duration: 0 } }\n                : { opacity: 0, scale: 0.95, y: 4 }\n            }\n            initial={\n              shouldReduceMotion\n                ? { opacity: 1, x: \"-50%\", y: 0 }\n                : { opacity: 0, scale: 0.95, x: \"-50%\", y: 4 }\n            }\n            transition={\n              shouldReduceMotion\n                ? { duration: 0 }\n                : {\n                    bounce: 0.05,\n                    duration: 0.25,\n                    type: \"spring\" as const,\n                  }\n            }\n          >\n            {logo.name}\n            {/* Tooltip arrow */}\n            <div className=\"absolute -bottom-1 left-1/2 h-2 w-2 -translate-x-1/2 rotate-45 bg-foreground\" />\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </motion.div>\n  );\n\n  if (logo.href) {\n    return (\n      <a\n        className=\"focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n        href={logo.href}\n        rel=\"noopener noreferrer\"\n        target=\"_blank\"\n      >\n        {content}\n      </a>\n    );\n  }\n\n  return content;\n}\n\nexport function LogoGrid({\n  title = \"Trusted by innovative companies\",\n  description = \"Leading organizations rely on our platform to power their success\",\n  logos = DEFAULT_LOGOS,\n  columns = 4,\n}: LogoGridProps) {\n  const shouldReduceMotion = useReducedMotion();\n  const [isHoverDevice, setIsHoverDevice] = useState(false);\n\n  useEffect(() => {\n    const mediaQuery = window.matchMedia(\"(hover: hover) and (pointer: fine)\");\n    setIsHoverDevice(mediaQuery.matches);\n\n    const handleChange = (e: MediaQueryListEvent) => {\n      setIsHoverDevice(e.matches);\n    };\n\n    mediaQuery.addEventListener(\"change\", handleChange);\n    return () => mediaQuery.removeEventListener(\"change\", handleChange);\n  }, []);\n\n  return (\n    <section className=\"bg-background py-20\">\n      <div className=\"mx-auto max-w-6xl px-6\">\n        <div className=\"mb-12 text-center\">\n          <h2 className=\"mb-4 font-bold text-2xl text-foreground lg:text-3xl\">\n            {title}\n          </h2>\n          <p className=\"text-foreground/70 text-lg\">{description}</p>\n        </div>\n\n        <div\n          className={`grid ${COLUMN_CLASSES[columns]} gap-4 rounded-xl border border-border/50 bg-muted/30 p-4`}\n        >\n          {logos.map((logo, index) => (\n            <LogoItem\n              isHoverDevice={isHoverDevice}\n              key={`${logo.name}-${index}`}\n              logo={logo}\n              shouldReduceMotion={shouldReduceMotion}\n            />\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}\n\nexport default LogoGrid;\n","path":"index.tsx","target":"components/smoothui/logo-cloud-4/index.tsx","type":"registry:block"}],"name":"logo-cloud-4","registryDependencies":["https://smoothui.dev/r/shared.json"],"title":"Logo Cloud 4","type":"registry:block"}