{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-gallery",
  "title": "Image Gallery",
  "description": "A responsive image collection with grid and masonry layouts and an accessible lightbox.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.tinkererslabs.com/r/image-grid.json",
    "https://ui.tinkererslabs.com/r/lightbox.json"
  ],
  "files": [
    {
      "path": "registry/default/image-gallery/image-gallery.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Grid2X2, Images, Rows3 } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  ImageGrid,\n  type GalleryImage,\n  type ImageGridLayout,\n} from \"@/registry/default/image-grid/image-grid\"\nimport { Lightbox } from \"@/registry/default/lightbox/lightbox\"\n\nexport type ImageGalleryItem = GalleryImage\nexport type ImageGalleryLayout = ImageGridLayout\n\nexport interface ImageGalleryProps extends Omit<\n  React.ComponentPropsWithoutRef<\"section\">,\n  \"title\"\n> {\n  images: ImageGalleryItem[]\n  title?: React.ReactNode\n  layout?: ImageGalleryLayout\n  defaultLayout?: ImageGalleryLayout\n  onLayoutChange?: (layout: ImageGalleryLayout) => void\n  selectedId?: string | null\n  defaultSelectedId?: string | null\n  onSelectedIdChange?: (id: string | null) => void\n  showLayoutToggle?: boolean\n  emptyState?: React.ReactNode\n  renderImage?: (\n    image: ImageGalleryItem,\n    context: { lightbox: boolean }\n  ) => React.ReactNode\n}\n\n/*\n * A block: the grid, the lightbox, and the chrome that holds them. Image Grid\n * needs nothing but React, and Lightbox works over any set of images, so reach\n * for those directly when you do not want this arrangement.\n */\nexport const ImageGallery = React.forwardRef<HTMLElement, ImageGalleryProps>(\n  function ImageGallery(\n    {\n      images,\n      title = \"Gallery\",\n      layout: controlledLayout,\n      defaultLayout = \"grid\",\n      onLayoutChange,\n      selectedId: controlledSelectedId,\n      defaultSelectedId = null,\n      onSelectedIdChange,\n      showLayoutToggle = true,\n      emptyState = \"No images yet.\",\n      renderImage,\n      className,\n      ...sectionProps\n    },\n    forwardedRef\n  ) {\n    const [uncontrolledLayout, setUncontrolledLayout] =\n      React.useState<ImageGalleryLayout>(defaultLayout)\n    const [uncontrolledSelectedId, setUncontrolledSelectedId] = React.useState<\n      string | null\n    >(defaultSelectedId)\n    const lastTriggerRef = React.useRef<HTMLButtonElement>(null)\n\n    const layout = controlledLayout ?? uncontrolledLayout\n    const selectedId = controlledSelectedId ?? uncontrolledSelectedId\n\n    function changeLayout(nextLayout: ImageGalleryLayout) {\n      if (controlledLayout === undefined) setUncontrolledLayout(nextLayout)\n      onLayoutChange?.(nextLayout)\n    }\n\n    function changeSelectedId(nextId: string | null) {\n      if (controlledSelectedId === undefined) setUncontrolledSelectedId(nextId)\n      onSelectedIdChange?.(nextId)\n    }\n\n    return (\n      <>\n        <section\n          {...sectionProps}\n          data-slot=\"image-gallery\"\n          ref={forwardedRef}\n          className={cn(\"w-full space-y-6\", className)}\n        >\n          <header\n            data-slot=\"image-gallery-header\"\n            className=\"flex min-h-11 items-center justify-between gap-4\"\n          >\n            <div className=\"flex min-w-0 items-center gap-3\">\n              <h2 className=\"truncate text-xl font-semibold\">{title}</h2>\n              <span className=\"bg-muted text-muted-foreground shrink-0 rounded-full px-2.5 py-1 text-xs font-semibold whitespace-nowrap tabular-nums\">\n                {images.length}\n                <span className=\"hidden sm:inline\">\n                  {images.length === 1 ? \" image\" : \" images\"}\n                </span>\n              </span>\n            </div>\n\n            {showLayoutToggle && images.length > 0 && (\n              <button\n                type=\"button\"\n                data-slot=\"image-gallery-layout-toggle\"\n                aria-label={\n                  layout === \"grid\" ? \"Use masonry layout\" : \"Use grid layout\"\n                }\n                className=\"border-border hover:bg-muted focus-visible:ring-ring inline-flex size-11 shrink-0 items-center justify-center rounded-full border transition-colors duration-150 focus-visible:ring-2 focus-visible:outline-none motion-reduce:transition-none\"\n                onClick={() =>\n                  changeLayout(layout === \"grid\" ? \"masonry\" : \"grid\")\n                }\n              >\n                {layout === \"grid\" ? (\n                  <Rows3 aria-hidden=\"true\" size={18} />\n                ) : (\n                  <Grid2X2 aria-hidden=\"true\" size={18} />\n                )}\n              </button>\n            )}\n          </header>\n\n          <ImageGrid\n            emptyState={\n              <span className=\"inline-flex items-center gap-2\">\n                <Images aria-hidden=\"true\" size={18} />\n                {emptyState}\n              </span>\n            }\n            images={images}\n            layout={layout}\n            renderImage={\n              renderImage\n                ? (image) => renderImage(image, { lightbox: false })\n                : undefined\n            }\n            onSelect={(image, event) => {\n              lastTriggerRef.current = event.currentTarget\n              changeSelectedId(image.id)\n            }}\n          />\n        </section>\n\n        <Lightbox\n          closeLabel=\"Close gallery\"\n          finalFocus={lastTriggerRef}\n          images={images}\n          openId={selectedId}\n          renderImage={\n            renderImage\n              ? (image) => renderImage(image, { lightbox: true })\n              : undefined\n          }\n          onOpenIdChange={changeSelectedId}\n        />\n      </>\n    )\n  }\n)\n",
      "type": "registry:block"
    }
  ],
  "categories": [
    "gallery",
    "images",
    "dialog",
    "layout"
  ],
  "type": "registry:block"
}