{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gallery-grid-block-shadcnui",
  "type": "registry:block",
  "title": "Gallery Grid with Lightbox",
  "description": "Filterable image gallery with animated lightbox and navigation",
  "registryDependencies": [
    "button"
  ],
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-shadcn/src/components/sections/gallery-grid-block.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { ChevronLeft, ChevronRight, Grid, X, ZoomIn } from \"lucide-react\";\nimport { KeyboardEvent, useState } from \"react\";\n\nconst galleryImages = [\n  {\n    id: 1,\n    url: \"https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=500\",\n    title: \"Abstract Architecture\",\n    category: \"Architecture\",\n  },\n  {\n    id: 2,\n    url: \"https://images.unsplash.com/photo-1618556450994-a6a128ef0d9d?w=500\",\n    title: \"Modern Design\",\n    category: \"Design\",\n  },\n  {\n    id: 3,\n    url: \"https://images.unsplash.com/photo-1618005198919-d3d4b5a92ead?w=500\",\n    title: \"Urban Landscape\",\n    category: \"Nature\",\n  },\n  {\n    id: 4,\n    url: \"https://images.unsplash.com/photo-1634017839464-5c339ebe3cb4?w=500\",\n    title: \"Digital Art\",\n    category: \"Art\",\n  },\n  {\n    id: 5,\n    url: \"https://images.unsplash.com/photo-1618556450991-2f1af64e8191?w=500\",\n    title: \"Creative Space\",\n    category: \"Architecture\",\n  },\n  {\n    id: 6,\n    url: \"https://images.unsplash.com/photo-1618005198919-d3d4b5a92ead?w=500\",\n    title: \"Minimalist View\",\n    category: \"Design\",\n  },\n];\n\nexport function GalleryGridBlock() {\n  const [selectedImage, setSelectedImage] = useState<number | null>(null);\n  const [filter, setFilter] = useState<string>(\"All\");\n\n  const categories = [\n    \"All\",\n    ...new Set(galleryImages.map((img) => img.category)),\n  ];\n\n  const filteredImages =\n    filter === \"All\"\n      ? galleryImages\n      : galleryImages.filter((img) => img.category === filter);\n\n  const handleNext = () => {\n    if (selectedImage !== null) {\n      const currentIndex = galleryImages.findIndex(\n        (img) => img.id === selectedImage\n      );\n      const nextIndex = (currentIndex + 1) % galleryImages.length;\n      setSelectedImage(galleryImages[nextIndex].id);\n    }\n  };\n\n  const handlePrev = () => {\n    if (selectedImage !== null) {\n      const currentIndex = galleryImages.findIndex(\n        (img) => img.id === selectedImage\n      );\n      const prevIndex =\n        (currentIndex - 1 + galleryImages.length) % galleryImages.length;\n      setSelectedImage(galleryImages[prevIndex].id);\n    }\n  };\n\n  const selectedImageData = galleryImages.find(\n    (img) => img.id === selectedImage\n  );\n\n  const handleCardKeyDown = (\n    event: KeyboardEvent<HTMLDivElement>,\n    imageId: number\n  ) => {\n    if (event.key === \"Enter\" || event.key === \" \") {\n      event.preventDefault();\n      setSelectedImage(imageId);\n    }\n  };\n\n  return (\n    <section\n      className=\"w-full bg-background px-4 py-16\"\n      aria-labelledby=\"gallery-heading\"\n    >\n      <div className=\"mx-auto max-w-7xl\">\n        <motion.div\n          initial={{ opacity: 0, y: -20 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.6 }}\n          className=\"mb-12 text-center\"\n          role=\"region\"\n          aria-labelledby=\"gallery-heading\"\n        >\n          <Badge className=\"mb-4\" variant=\"secondary\">\n            <Grid className=\"mr-1 h-3 w-3\" />\n            Gallery\n          </Badge>\n          <h2\n            id=\"gallery-heading\"\n            className=\"mb-4 text-4xl font-bold tracking-tight\"\n          >\n            Our Portfolio\n          </h2>\n          <p className=\"mx-auto max-w-2xl text-muted-foreground\">\n            Explore our collection of stunning visuals and creative work\n          </p>\n        </motion.div>\n\n        {/* Filter Buttons */}\n        <motion.div\n          initial={{ opacity: 0, y: 10 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ delay: 0.2 }}\n          className=\"mb-8 flex flex-wrap justify-center gap-2\"\n          role=\"group\"\n          aria-label=\"Gallery categories\"\n        >\n          {categories.map((category) => (\n            <Button\n              key={category}\n              variant={filter === category ? \"default\" : \"outline\"}\n              size=\"sm\"\n              onClick={() => setFilter(category)}\n              aria-pressed={filter === category}\n            >\n              {category}\n            </Button>\n          ))}\n        </motion.div>\n\n        {/* Gallery Grid */}\n        <motion.div\n          layout\n          className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\"\n          role=\"list\"\n          aria-label=\"Gallery items\"\n        >\n          <AnimatePresence mode=\"popLayout\">\n            {filteredImages.map((image, index) => (\n              <motion.div\n                key={image.id}\n                layout\n                initial={{ opacity: 0, scale: 0.8 }}\n                animate={{ opacity: 1, scale: 1 }}\n                exit={{ opacity: 0, scale: 0.8 }}\n                transition={{ duration: 0.3, delay: index * 0.05 }}\n                role=\"listitem\"\n              >\n                <Card\n                  className=\"group relative cursor-pointer overflow-hidden border-border transition-all hover:border-ring hover:shadow-xl\"\n                  onClick={() => setSelectedImage(image.id)}\n                  onKeyDown={(event) => handleCardKeyDown(event, image.id)}\n                  role=\"button\"\n                  tabIndex={0}\n                  aria-label={`View details for ${image.title}`}\n                >\n                  <div className=\"relative aspect-square overflow-hidden\">\n                    <motion.img\n                      src={image.url}\n                      alt={image.title}\n                      className=\"h-full w-full object-cover\"\n                      whileHover={{ scale: 1.1 }}\n                      transition={{ duration: 0.3 }}\n                    />\n\n                    {/* Overlay */}\n                    <motion.div\n                      initial={{ opacity: 0 }}\n                      whileHover={{ opacity: 1 }}\n                      transition={{ duration: 0.2 }}\n                      className=\"absolute inset-0 flex flex-col items-center justify-center bg-black/60 backdrop-blur-sm\"\n                      aria-hidden=\"true\"\n                    >\n                      <ZoomIn className=\"mb-2 h-8 w-8 text-[var(--muted-foreground)]\" />\n                      <h3 className=\"mb-1 text-center text-lg font-semibold text-[var(--muted-foreground)]\">\n                        {image.title}\n                      </h3>\n                      <Badge variant=\"secondary\">{image.category}</Badge>\n                    </motion.div>\n                  </div>\n                </Card>\n              </motion.div>\n            ))}\n          </AnimatePresence>\n        </motion.div>\n\n        {/* Lightbox */}\n        <AnimatePresence>\n          {selectedImage !== null && selectedImageData && (\n            <motion.div\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/90 p-4\"\n              onClick={() => setSelectedImage(null)}\n              role=\"dialog\"\n              aria-modal=\"true\"\n              aria-labelledby=\"gallery-dialog-title\"\n              aria-describedby=\"gallery-dialog-description\"\n            >\n              <motion.div\n                initial={{ scale: 0.8, opacity: 0 }}\n                animate={{ scale: 1, opacity: 1 }}\n                exit={{ scale: 0.8, opacity: 0 }}\n                transition={{ type: \"spring\", damping: 25, stiffness: 300 }}\n                onClick={(e) => e.stopPropagation()}\n                className=\"relative max-h-[90vh] max-w-5xl\"\n              >\n                {/* Close Button */}\n                <Button\n                  size=\"icon\"\n                  variant=\"ghost\"\n                  className=\"absolute -right-12 top-0 text-[var(--muted-foreground)] hover:bg-white/10\"\n                  onClick={() => setSelectedImage(null)}\n                  aria-label=\"Close gallery dialog\"\n                >\n                  <X className=\"h-6 w-6\" />\n                </Button>\n\n                {/* Navigation Buttons */}\n                <Button\n                  size=\"icon\"\n                  variant=\"ghost\"\n                  className=\"absolute left-4 top-1/2 -translate-y-1/2 text-[var(--muted-foreground)] hover:bg-white/10\"\n                  onClick={(e) => {\n                    e.stopPropagation();\n                    handlePrev();\n                  }}\n                  aria-label=\"View previous image\"\n                >\n                  <ChevronLeft className=\"h-8 w-8\" />\n                </Button>\n                <Button\n                  size=\"icon\"\n                  variant=\"ghost\"\n                  className=\"absolute right-4 top-1/2 -translate-y-1/2 text-[var(--muted-foreground)] hover:bg-white/10\"\n                  onClick={(e) => {\n                    e.stopPropagation();\n                    handleNext();\n                  }}\n                  aria-label=\"View next image\"\n                >\n                  <ChevronRight className=\"h-8 w-8\" />\n                </Button>\n\n                {/* Image */}\n                <motion.img\n                  key={selectedImage}\n                  src={selectedImageData.url}\n                  alt={selectedImageData.title}\n                  className=\"max-h-[80vh] w-auto rounded-lg\"\n                  initial={{ opacity: 0 }}\n                  animate={{ opacity: 1 }}\n                  transition={{ duration: 0.2 }}\n                />\n\n                {/* Image Info */}\n                <motion.div\n                  initial={{ opacity: 0, y: 20 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  transition={{ delay: 0.1 }}\n                  className=\"mt-4 text-center text-[var(--muted-foreground)]\"\n                  id=\"gallery-dialog-description\"\n                >\n                  <h3\n                    className=\"mb-2 text-xl font-semibold\"\n                    id=\"gallery-dialog-title\"\n                  >\n                    {selectedImageData.title}\n                  </h3>\n                  <Badge variant=\"secondary\">\n                    {selectedImageData.category}\n                  </Badge>\n                </motion.div>\n              </motion.div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/uitripled/gallery-grid-block-shadcnui.tsx"
    }
  ]
}