{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "expandable-gallery",
  "title": "Expandable Gallery",
  "description": "A photo gallery with smooth item expansion.",
  "dependencies": [
    "motion",
    "@hugeicons/core-free-icons",
    "@hugeicons/react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/default/example/expandable-gallery.tsx",
      "content": "\"use client\";\n\nimport { motion, AnimatePresence, LayoutGroup } from \"motion/react\";\nimport React, { useState, useId, useRef } from \"react\";\nimport { useOutsideClick } from \"@/hooks/use-outside-click\";\nimport Image from \"next/image\";\nimport { Button } from \"@/components/ui/button\";\nimport { ArrowLeft01Icon, ArrowRight01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"@/lib/utils\";\n\n// Change Here\nconst PHOTOS = [\n  {\n    id: \"photo-1\",\n\n    src: \"https://images.unsplash.com/photo-1755398104393-746e52af4a9f?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8Mzd8fHxlbnwwfHx8fHw%3D?q=80&w=800\",\n    alt: \"Technology setup\",\n    rotation: -15,\n    x: -90,\n    y: 10,\n    zIndex: 10,\n  },\n  {\n    id: \"photo-2\",\n    src: \"https://images.unsplash.com/photo-1756764099214-b09a5666914b?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTB8fHxlbnwwfHx8fHw%3D?q=80&w=800\",\n    alt: \"Design research\",\n    rotation: -3,\n    x: -10,\n    y: -15,\n    zIndex: 20,\n  },\n  {\n    id: \"photo-3\",\n    src: \"https://images.unsplash.com/photo-1757372429884-92e02350c5d9?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MjJ8fHxlbnwwfHx8fHw%3D?q=80&w=800\",\n    alt: \"Code and development\",\n    rotation: 12,\n    x: 75,\n    y: 5,\n    zIndex: 30,\n  },\n  {\n    id: \"photo-4\",\n    src: \"https://images.unsplash.com/photo-1756993399574-2fa126269ce7?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8NzR8fHxlbnwwfHx8fHw%3D?q=80&w=800\",\n    alt: \"Dashboard interface\",\n  },\n  {\n    id: \"photo-5\",\n    src: \"https://images.unsplash.com/photo-1756990637536-714b76296a30?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8ODJ8fHxlbnwwfHx8fHw%3D?q=80&w=800\",\n    alt: \"Product design\",\n  },\n  {\n    id: \"photo-6\",\n    src: \"https://images.unsplash.com/photo-1756838197413-07f174def66c?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTA0fHx8ZW58MHx8fHx8?q=80&w=800\",\n    alt: \"Laptop on desk\",\n  },\n  {\n    id: \"photo-7\",\n    src: \"https://images.unsplash.com/photo-1756310406492-3ce3bef447aa?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTQwfHx8ZW58MHx8fHx8?q=80&w=800\",\n    alt: \"Team collaboration\",\n  },\n  {\n    id: \"photo-8\",\n    src: \"https://images.unsplash.com/photo-1755311905796-d539c7d24acd?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTgzfHx8ZW58MHx8fHx8?q=80&w=800\",\n    alt: \"UX wireframes\",\n  },\n  {\n    id: \"photo-9\",\n    src: \"https://images.unsplash.com/photo-1755542366797-b3f036b11310?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MTg2fHx8ZW58MHx8fHx8?q=80&w=800\",\n    alt: \"Developer workspace\",\n  },\n];\n\nconst transition = {\n  type: \"spring\",\n  stiffness: 160,\n  damping: 18,\n  mass: 1,\n} as const;\n\nexport default function ExpandableGallery() {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const layoutGroupId = useId();\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useOutsideClick(containerRef, () => {\n    if (isExpanded) {\n      setIsExpanded(false);\n    }\n  });\n\n  return (\n    <section className=\"relative w-full  px-4 md:px-8 bg-background flex flex-col items-center justify-start min-h-[850px] overflow-hidden\">\n      <LayoutGroup id={layoutGroupId}>\n        <div className=\"w-full max-w-6xl mx-auto flex flex-col items-center\">\n          <div className=\"w-full h-12 flex items-center justify-between px-4 mb-2\">\n            <AnimatePresence>\n              {isExpanded && (\n                <motion.button\n                  key=\"back-button\"\n                  initial={{ opacity: 0, x: -10 }}\n                  animate={{ opacity: 1, x: 0 }}\n                  exit={{ opacity: 0, x: -10 }}\n                  onClick={() => setIsExpanded(false)}\n                  className=\"flex items-center gap-2 text-muted-foreground hover:text-foreground transition-all group z-50\"\n                >\n                  <div className=\"p-2 rounded-full bg-muted group-hover:bg-accent transition-colors text-foreground\">\n                    <HugeiconsIcon\n                      icon={ArrowLeft01Icon}\n                      width={20}\n                      height={20}\n                    />\n                  </div>\n                  <span className=\"font-medium\">Go back</span>\n                </motion.button>\n              )}\n            </AnimatePresence>\n          </div>\n\n          <motion.div\n            ref={containerRef}\n            layout\n            className={cn(\n              \"relative w-full\",\n              isExpanded\n                ? \"grid grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8 px-4\"\n                : \"flex flex-col items-center justify-start pt-4\"\n            )}\n            transition={transition}\n          >\n            <div\n              className={cn(\n                \"relative\",\n                isExpanded\n                  ? \"contents\"\n                  : \"h-[450px] w-full flex items-center justify-center mb-8\"\n              )}\n            >\n              {PHOTOS.map((photo, index) => {\n                const isPrimary = index < 3;\n                if (!isPrimary && !isExpanded) return null;\n\n                return (\n                  <motion.div\n                    key={`card-${photo.id}`}\n                    layoutId={`card-container-${photo.id}`}\n                    layout\n                    initial={{ opacity: 0, scale: 0.9 }}\n                    animate={{\n                      opacity: 1,\n                      scale: 1,\n                      rotate: !isExpanded ? photo.rotation || 0 : 0,\n                      x: !isExpanded ? photo.x || 0 : 0,\n                      y: !isExpanded ? photo.y || 0 : 0,\n                      zIndex: !isExpanded ? photo.zIndex || index : 10,\n                    }}\n                    transition={transition}\n                    whileHover={\n                      !isExpanded\n                        ? {\n                            scale: 1.05,\n                            y: (photo.y || 0) - 15,\n                            rotate: (photo.rotation || 0) * 0.8,\n                            zIndex: 50,\n                            transition: {\n                              type: \"spring\",\n                              stiffness: 400,\n                              damping: 25,\n                            },\n                          }\n                        : { scale: 1.02 }\n                    }\n                    className={cn(\n                      \"cursor-pointer overflow-hidden bg-muted\",\n                      isExpanded\n                        ? \"relative aspect-square rounded-[2rem] md:rounded-[3rem] border-4 md:border-[6px] border-background shadow-lg\"\n                        : \"absolute w-44 h-44 md:w-60 md:h-60 rounded-[2.5rem] md:rounded-[3rem] border-[6px] border-background shadow-[0_20px_50px_rgba(0,0,0,0.15)]\"\n                    )}\n                    onClick={() => !isExpanded && setIsExpanded(true)}\n                  >\n                    <motion.div\n                      layoutId={`image-inner-${photo.id}`}\n                      layout=\"position\"\n                      className=\"w-full h-full relative\"\n                      transition={transition}\n                    >\n                      <Image\n                        src={photo.src}\n                        alt={photo.alt}\n                        fill\n                        className=\"object-cover select-none pointer-events-none\"\n                        sizes={\n                          isExpanded\n                            ? \"(max-width: 1024px) 50vw, 33vw\"\n                            : \"240px\"\n                        }\n                        priority={isPrimary}\n                      />\n                    </motion.div>\n                  </motion.div>\n                );\n              })}\n            </div>\n\n            <AnimatePresence>\n              {!isExpanded && (\n                <motion.div\n                  key=\"stack-content\"\n                  initial={{ opacity: 1 }}\n                  exit={{ opacity: 0 }}\n                  className=\"text-center max-w-2xl space-y-8\"\n                >\n                  <h2 className=\"text-2xl md:text-4xl font-normal tracking-tight text-foreground/90 leading-tight\">\n                    People don’t fall in love with components.{\" \"}\n                    <br className=\"hidden md:block\" />\n                    They fall in love with how something feels.\n                  </h2>\n\n                  <div className=\"flex justify-center\">\n                    <Button\n                      variant=\"default\"\n                      onClick={() => setIsExpanded(true)}\n                      className=\"rounded-full cursor-pointer py-6 px-8 border-border/40 font-normal group \"\n                    >\n                      Explore more components\n                      <HugeiconsIcon\n                        icon={ArrowRight01Icon}\n                        className=\"transition-transform group-hover:translate-x-1\"\n                        width={20}\n                        height={20}\n                      />\n                    </Button>\n                  </div>\n                </motion.div>\n              )}\n            </AnimatePresence>\n          </motion.div>\n        </div>\n      </LayoutGroup>\n    </section>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "hooks/use-outside-click.ts",
      "content": "import { useEffect, type RefObject } from \"react\";\n\nexport function useOutsideClick(\n  ref: RefObject<HTMLElement | null>,\n  callback: (event: MouseEvent | TouchEvent) => void\n) {\n  useEffect(() => {\n    const listener = (event: MouseEvent | TouchEvent) => {\n      if (!ref.current || ref.current.contains(event.target as Node)) {\n        return;\n      }\n      callback(event);\n    };\n\n    document.addEventListener(\"mousedown\", listener);\n    document.addEventListener(\"touchstart\", listener);\n\n    return () => {\n      document.removeEventListener(\"mousedown\", listener);\n      document.removeEventListener(\"touchstart\", listener);\n    };\n  }, [ref, callback]);\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:component"
}