{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gallery-1",
  "title": "Filterable Photo Grid",
  "description": "Image gallery grid with tag filter toggles, hairline-gap tiles, hover overlays naming the contributor and location, and a lightbox dialog per photo.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "dialog",
    "toggle-group"
  ],
  "files": [
    {
      "path": "registry/blocks/gallery/1/gallery-block.tsx",
      "content": "\"use client\"\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\"\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\"\nimport { useState } from \"react\"\nimport { IconPlaceholder } from \"@/components/icons/icon-placeholder\"\n\nconst tiles = [\n  {\n    id: 1,\n    label: \"Mountain sunrise\",\n    location: \"Rocky Mountains, CO\",\n    contributor: {\n      name: \"Maya Osei\",\n      initials: \"MO\",\n      avatar: \"https://i.pravatar.cc/40?img=1\",\n    },\n    tag: \"Landscape\",\n    featured: true,\n    src: \"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&q=80&crop=entropy\",\n    full: \"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200&q=80\",\n  },\n  {\n    id: 2,\n    label: \"City skyline\",\n    location: \"Chicago, IL\",\n    contributor: {\n      name: \"Leo Farris\",\n      initials: \"LF\",\n      avatar: \"https://i.pravatar.cc/40?img=2\",\n    },\n    tag: \"Urban\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?w=800&q=80&crop=entropy\",\n    full: \"https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?w=1200&q=80\",\n  },\n  {\n    id: 3,\n    label: \"Forest trail\",\n    location: \"Olympic NP, WA\",\n    contributor: {\n      name: \"Sara Kim\",\n      initials: \"SK\",\n      avatar: \"https://i.pravatar.cc/40?img=5\",\n    },\n    tag: \"Nature\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=800&q=80&crop=entropy\",\n    full: \"https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=1200&q=80\",\n  },\n  {\n    id: 4,\n    label: \"Ocean waves\",\n    location: \"Big Sur, CA\",\n    contributor: {\n      name: \"Dante Ruiz\",\n      initials: \"DR\",\n      avatar: \"https://i.pravatar.cc/40?img=7\",\n    },\n    tag: \"Coastal\",\n    featured: true,\n    src: \"https://images.unsplash.com/photo-1505144808419-1957a94ca61e?w=800&q=80&crop=edges\",\n    full: \"https://images.unsplash.com/photo-1505144808419-1957a94ca61e?w=1200&q=80\",\n  },\n  {\n    id: 5,\n    label: \"Desert dunes\",\n    location: \"Sahara, Morocco\",\n    contributor: {\n      name: \"Nadia Voss\",\n      initials: \"NV\",\n      avatar: \"https://i.pravatar.cc/40?img=9\",\n    },\n    tag: \"Landscape\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1473580044384-7ba9967e16a0?w=800&q=80&crop=entropy\",\n    full: \"https://images.unsplash.com/photo-1473580044384-7ba9967e16a0?w=1200&q=80\",\n  },\n  {\n    id: 6,\n    label: \"Snowy peaks\",\n    location: \"Lauterbrunnen, CH\",\n    contributor: {\n      name: \"Jin Park\",\n      initials: \"JP\",\n      avatar: \"https://i.pravatar.cc/40?img=11\",\n    },\n    tag: \"Alpine\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1454496522488-7a8e488e8606?w=800&q=80&crop=edges\",\n    full: \"https://images.unsplash.com/photo-1454496522488-7a8e488e8606?w=1200&q=80\",\n  },\n  {\n    id: 7,\n    label: \"Autumn leaves\",\n    location: \"Vermont, USA\",\n    contributor: {\n      name: \"Fiona Blake\",\n      initials: \"FB\",\n      avatar: \"https://i.pravatar.cc/40?img=13\",\n    },\n    tag: \"Seasonal\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1507371341162-763b5e419408?w=800&q=80&crop=entropy\",\n    full: \"https://images.unsplash.com/photo-1507371341162-763b5e419408?w=1200&q=80\",\n  },\n  {\n    id: 8,\n    label: \"Wildflower field\",\n    location: \"Tuscany, Italy\",\n    contributor: {\n      name: \"Arjun Mehta\",\n      initials: \"AM\",\n      avatar: \"https://i.pravatar.cc/40?img=15\",\n    },\n    tag: \"Nature\",\n    featured: true,\n    src: \"https://images.unsplash.com/photo-1490750967868-88aa4486c946?w=800&q=80&crop=edges\",\n    full: \"https://images.unsplash.com/photo-1490750967868-88aa4486c946?w=1200&q=80\",\n  },\n  {\n    id: 9,\n    label: \"Coastal cliffs\",\n    location: \"Moher, Ireland\",\n    contributor: {\n      name: \"Cleo Torres\",\n      initials: \"CT\",\n      avatar: \"https://i.pravatar.cc/40?img=17\",\n    },\n    tag: \"Coastal\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1510414842594-a61c69b5ae57?w=800&q=80&crop=edges\",\n    full: \"https://images.unsplash.com/photo-1510414842594-a61c69b5ae57?w=1200&q=80&crop=edges\",\n  },\n  {\n    id: 10,\n    label: \"River valley\",\n    location: \"Columbia Gorge, OR\",\n    contributor: {\n      name: \"Eli Grant\",\n      initials: \"EG\",\n      avatar: \"https://i.pravatar.cc/40?img=19\",\n    },\n    tag: \"Landscape\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1426604966848-d7adac402bff?w=800&q=80&crop=edges\",\n    full: \"https://images.unsplash.com/photo-1426604966848-d7adac402bff?w=1200&q=80&crop=edges\",\n  },\n  {\n    id: 11,\n    label: \"Jungle canopy\",\n    location: \"Amazon Basin, BR\",\n    contributor: {\n      name: \"Amara Diop\",\n      initials: \"AD\",\n      avatar: \"https://i.pravatar.cc/40?img=21\",\n    },\n    tag: \"Nature\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1445264718234-a623be589d37?w=800&q=80&crop=edges\",\n    full: \"https://images.unsplash.com/photo-1445264718234-a623be589d37?w=1200&q=80&crop=edges\",\n  },\n  {\n    id: 12,\n    label: \"Lakeside dawn\",\n    location: \"Lake Bled, SI\",\n    contributor: {\n      name: \"Theo Hartmann\",\n      initials: \"TH\",\n      avatar: \"https://i.pravatar.cc/40?img=23\",\n    },\n    tag: \"Landscape\",\n    featured: false,\n    src: \"https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=800&q=80&crop=entropy\",\n    full: \"https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=1200&q=80&crop=entropy\",\n  },\n]\n\nconst ALL_TAG = \"All\"\nconst tags = [ALL_TAG, ...Array.from(new Set(tiles.map((t) => t.tag)))]\n\nexport default function GalleryBlock() {\n  const [active, setActive] = useState(ALL_TAG)\n\n  const visible =\n    active === ALL_TAG ? tiles : tiles.filter((t) => t.tag === active)\n\n  return (\n    <section className=\"flex w-full items-center justify-center bg-background px-6 py-20 text-foreground\">\n      <div className=\"mx-auto w-full max-w-5xl\">\n        <div className=\"mx-auto max-w-2xl text-center\">\n          <span className=\"text-xs font-semibold tracking-[0.18em] text-muted-foreground uppercase\">\n            Gallery\n          </span>\n          <h2 className=\"mt-3 font-heading text-3xl font-bold tracking-tight sm:text-4xl\">\n            Acme in the wild\n          </h2>\n          <p className=\"mt-3 text-base text-muted-foreground\">\n            A curated selection of places and moments captured by our community\n            around the world.\n          </p>\n        </div>\n\n        <div className=\"mt-10 flex flex-wrap items-center justify-center gap-2 border-b border-border pb-6\">\n          <ToggleGroup\n            variant=\"outline\"\n            size=\"sm\"\n            value={[active]}\n            onValueChange={(value) => {\n              const next = value[0]\n              if (next) setActive(next)\n            }}\n            aria-label=\"Filter photos by tag\"\n            className=\"flex-wrap justify-center\"\n          >\n            {tags.map((tag) => (\n              <ToggleGroupItem\n                key={tag}\n                value={tag}\n                aria-label={`Show ${tag} photos`}\n                className=\"text-xs font-medium tracking-wide\"\n              >\n                {tag}\n              </ToggleGroupItem>\n            ))}\n          </ToggleGroup>\n        </div>\n\n        <div className=\"mt-8 grid grid-cols-2 gap-px overflow-hidden rounded-xl bg-border sm:grid-cols-3 md:grid-cols-4\">\n          {visible.map((tile) => (\n            <Dialog key={tile.id}>\n              <DialogTrigger\n                render={\n                  <button\n                    type=\"button\"\n                    aria-label={`View ${tile.label}`}\n                    className=\"group relative aspect-square overflow-hidden bg-muted text-left focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-inset\"\n                  />\n                }\n              >\n                <img\n                  src={tile.src}\n                  alt={tile.label}\n                  className=\"size-full object-cover transition-transform duration-500 group-hover:scale-105\"\n                />\n\n                <div className=\"absolute inset-0 flex flex-col justify-between bg-foreground/0 p-3 transition-colors duration-300 group-hover:bg-foreground/70\">\n                  <div className=\"flex items-start justify-between opacity-0 transition-opacity duration-300 group-hover:opacity-100\">\n                    <Badge\n                      variant=\"secondary\"\n                      className=\"text-[10px] font-semibold tracking-wide\"\n                    >\n                      {tile.tag}\n                    </Badge>\n                    {tile.featured && (\n                      <span className=\"size-1.5 bg-primary\" title=\"Featured\" />\n                    )}\n                  </div>\n\n                  <div className=\"opacity-0 transition-opacity duration-300 group-hover:opacity-100\">\n                    <p className=\"text-xs font-semibold tracking-wide text-background\">\n                      {tile.label}\n                    </p>\n                    <div className=\"mt-1 flex items-center gap-1.5\">\n                      <IconPlaceholder\n                        lucide=\"MapPin\"\n                        tabler=\"IconMapPin\"\n                        hugeicons=\"MapPinIcon\"\n                        phosphor=\"MapPin\"\n                        remixicon=\"RiMapPinLine\"\n                        className=\"size-3 shrink-0 text-background/70\"\n                        aria-hidden=\"true\"\n                      />\n                      <span className=\"truncate text-[10px] text-background/70\">\n                        {tile.location}\n                      </span>\n                    </div>\n                    <div className=\"mt-2 flex items-center gap-1.5 border-t border-background/20 pt-2\">\n                      <Avatar className=\"size-5\">\n                        <AvatarImage\n                          src={tile.contributor.avatar}\n                          alt={tile.contributor.name}\n                          className=\"grayscale\"\n                        />\n                        <AvatarFallback className=\"text-[8px]\">\n                          {tile.contributor.initials}\n                        </AvatarFallback>\n                      </Avatar>\n                      <span className=\"truncate text-[10px] text-background/80\">\n                        {tile.contributor.name}\n                      </span>\n                    </div>\n                  </div>\n                </div>\n              </DialogTrigger>\n\n              <DialogContent showCloseButton className=\"gap-0 p-0 sm:max-w-2xl\">\n                <DialogTitle className=\"sr-only\">{tile.label}</DialogTitle>\n                <DialogDescription className=\"sr-only\">\n                  {tile.label}, {tile.location}, photographed by{\" \"}\n                  {tile.contributor.name}.\n                </DialogDescription>\n                <div className=\"aspect-[4/3] w-full overflow-hidden rounded-t-[inherit] border-b border-border bg-muted\">\n                  <img\n                    src={tile.full}\n                    alt={tile.label}\n                    className=\"size-full object-cover\"\n                  />\n                </div>\n                <div className=\"flex flex-col gap-3 p-5\">\n                  <div className=\"flex items-start justify-between gap-3\">\n                    <div className=\"flex flex-col gap-1\">\n                      <span className=\"text-sm font-semibold tracking-tight text-foreground\">\n                        {tile.label}\n                      </span>\n                      <span className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n                        <IconPlaceholder\n                          lucide=\"MapPin\"\n                          tabler=\"IconMapPin\"\n                          hugeicons=\"MapPinIcon\"\n                          phosphor=\"MapPin\"\n                          remixicon=\"RiMapPinLine\"\n                          className=\"size-3 shrink-0\"\n                          aria-hidden=\"true\"\n                        />\n                        {tile.location}\n                      </span>\n                    </div>\n                    <Badge\n                      variant=\"secondary\"\n                      className=\"text-[10px] font-semibold tracking-wide uppercase\"\n                    >\n                      {tile.tag}\n                    </Badge>\n                  </div>\n                  <div className=\"flex items-center justify-between gap-3 border-t border-border pt-3\">\n                    <div className=\"flex items-center gap-2\">\n                      <Avatar className=\"size-6\">\n                        <AvatarImage\n                          src={tile.contributor.avatar}\n                          alt={tile.contributor.name}\n                          className=\"grayscale\"\n                        />\n                        <AvatarFallback className=\"text-[9px]\">\n                          {tile.contributor.initials}\n                        </AvatarFallback>\n                      </Avatar>\n                      <span className=\"text-xs font-medium text-foreground\">\n                        {tile.contributor.name}\n                      </span>\n                    </div>\n                    <DialogClose\n                      render={<Button variant=\"outline\" size=\"sm\" />}\n                    >\n                      Close\n                    </DialogClose>\n                  </div>\n                </div>\n              </DialogContent>\n            </Dialog>\n          ))}\n        </div>\n\n        <div className=\"mt-8 flex items-center justify-between border-t border-border pt-6\">\n          <p className=\"text-xs text-muted-foreground\">\n            Showing {visible.length} of {tiles.length} photos\n          </p>\n          <Button\n            variant=\"ghost\"\n            className=\"gap-1.5 text-xs font-medium\"\n            render={<a href=\"#\" />}\n            nativeButton={false}\n          >\n            View Full Collection\n            <IconPlaceholder\n              lucide=\"ArrowRight\"\n              tabler=\"IconArrowRight\"\n              hugeicons=\"ArrowRightIcon\"\n              phosphor=\"ArrowRight\"\n              remixicon=\"RiArrowRightLine\"\n              className=\"size-3.5\"\n              aria-hidden=\"true\"\n            />\n          </Button>\n        </div>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/gallery-1.tsx"
    }
  ],
  "meta": {
    "height": "1262px",
    "tier": "free"
  },
  "docs": "Requires a `base-*` style in components.json (the `shadcn init` default). Legacy `new-york`/`radix-*` styles install Radix primitives, which reject the `render` prop this block uses.",
  "categories": [
    "gallery"
  ],
  "type": "registry:block"
}