{ "$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\";\r\n\r\nimport { motion, AnimatePresence, LayoutGroup } from \"motion/react\";\r\nimport React, { useState, useId, useRef } from \"react\";\r\nimport { useOutsideClick } from \"@/hooks/use-outside-click\";\r\nimport Image from \"next/image\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { ArrowLeft01Icon, ArrowRight01Icon } from \"@hugeicons/core-free-icons\";\r\nimport { HugeiconsIcon } from \"@hugeicons/react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\n// Change Here\r\nconst PHOTOS = [\r\n {\r\n id: \"photo-1\",\r\n\r\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\",\r\n alt: \"Technology setup\",\r\n rotation: -15,\r\n x: -90,\r\n y: 10,\r\n zIndex: 10,\r\n },\r\n {\r\n id: \"photo-2\",\r\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\",\r\n alt: \"Design research\",\r\n rotation: -3,\r\n x: -10,\r\n y: -15,\r\n zIndex: 20,\r\n },\r\n {\r\n id: \"photo-3\",\r\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\",\r\n alt: \"Code and development\",\r\n rotation: 12,\r\n x: 75,\r\n y: 5,\r\n zIndex: 30,\r\n },\r\n {\r\n id: \"photo-4\",\r\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\",\r\n alt: \"Dashboard interface\",\r\n },\r\n {\r\n id: \"photo-5\",\r\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\",\r\n alt: \"Product design\",\r\n },\r\n {\r\n id: \"photo-6\",\r\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\",\r\n alt: \"Laptop on desk\",\r\n },\r\n {\r\n id: \"photo-7\",\r\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\",\r\n alt: \"Team collaboration\",\r\n },\r\n {\r\n id: \"photo-8\",\r\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\",\r\n alt: \"UX wireframes\",\r\n },\r\n {\r\n id: \"photo-9\",\r\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\",\r\n alt: \"Developer workspace\",\r\n },\r\n];\r\n\r\nconst transition = {\r\n type: \"spring\",\r\n stiffness: 160,\r\n damping: 18,\r\n mass: 1,\r\n} as const;\r\n\r\nexport default function ExpandableGallery() {\r\n const [isExpanded, setIsExpanded] = useState(false);\r\n const layoutGroupId = useId();\r\n const containerRef = useRef(null);\r\n\r\n useOutsideClick(containerRef, () => {\r\n if (isExpanded) {\r\n setIsExpanded(false);\r\n }\r\n });\r\n\r\n return (\r\n
\r\n \r\n
\r\n
\r\n \r\n {isExpanded && (\r\n setIsExpanded(false)}\r\n className=\"flex items-center gap-2 text-muted-foreground hover:text-foreground transition-all group z-50\"\r\n >\r\n
\r\n \r\n
\r\n Go back\r\n \r\n )}\r\n
\r\n
\r\n\r\n \r\n \r\n {PHOTOS.map((photo, index) => {\r\n const isPrimary = index < 3;\r\n if (!isPrimary && !isExpanded) return null;\r\n\r\n return (\r\n !isExpanded && setIsExpanded(true)}\r\n >\r\n \r\n \r\n \r\n \r\n );\r\n })}\r\n
\r\n\r\n \r\n {!isExpanded && (\r\n \r\n

\r\n People don’t fall in love with components.{\" \"}\r\n
\r\n They fall in love with how something feels.\r\n

\r\n\r\n
\r\n setIsExpanded(true)}\r\n className=\"rounded-full cursor-pointer py-6 px-8 border-border/40 font-normal group \"\r\n >\r\n Explore more components\r\n \r\n \r\n
\r\n \r\n )}\r\n
\r\n \r\n \r\n
\r\n
\r\n );\r\n}\r\n", "type": "registry:component" }, { "path": "hooks/use-outside-click.ts", "content": "import { useEffect, type RefObject } from \"react\";\r\n\r\nexport function useOutsideClick(\r\n ref: RefObject,\r\n callback: (event: MouseEvent | TouchEvent) => void\r\n) {\r\n useEffect(() => {\r\n const listener = (event: MouseEvent | TouchEvent) => {\r\n if (!ref.current || ref.current.contains(event.target as Node)) {\r\n return;\r\n }\r\n callback(event);\r\n };\r\n\r\n document.addEventListener(\"mousedown\", listener);\r\n document.addEventListener(\"touchstart\", listener);\r\n\r\n return () => {\r\n document.removeEventListener(\"mousedown\", listener);\r\n document.removeEventListener(\"touchstart\", listener);\r\n };\r\n }, [ref, callback]);\r\n}\r\n", "type": "registry:hook" } ], "type": "registry:component" }