{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-grid",
  "title": "Image Grid",
  "description": "Thumbnails in even cells or masonry columns, with nothing but React behind them.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/image-grid/image-grid.tsx",
      "content": "\"use client\"\n\n/* eslint-disable @next/next/no-img-element -- This must work outside Next.js. */\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface GalleryImage {\n  id: string\n  src: string\n  alt: string\n  width?: number\n  height?: number\n  caption?: React.ReactNode\n  description?: React.ReactNode\n  downloadUrl?: string\n  loading?: \"eager\" | \"lazy\"\n}\n\nexport type ImageGridLayout = \"grid\" | \"masonry\"\n\nexport interface ImageGridProps extends Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  \"onSelect\" | \"children\"\n> {\n  images: readonly GalleryImage[]\n  /** Even cells, or columns that keep each image's own height. */\n  layout?: ImageGridLayout\n  /** Makes every tile a button. Without it the grid is not interactive. */\n  onSelect?: (\n    image: GalleryImage,\n    event: React.MouseEvent<HTMLButtonElement>\n  ) => void\n  renderImage?: (image: GalleryImage) => React.ReactNode\n  /** Shown in place of the grid when there is nothing to show. */\n  emptyState?: React.ReactNode\n}\n\n/**\n * The thumbnails on their own, with no dialog and no dependency beyond React.\n * Pair it with Lightbox for a viewer, or use it as a picker.\n */\nexport function ImageGrid({\n  images,\n  layout = \"grid\",\n  onSelect,\n  renderImage,\n  emptyState = \"No images yet.\",\n  className,\n  ...rootProps\n}: ImageGridProps) {\n  if (images.length === 0) {\n    return (\n      <div\n        data-slot=\"image-grid-empty\"\n        className={cn(\n          \"border-border text-muted-foreground rounded-[var(--radius)] border border-dashed px-6 py-12 text-center text-sm\",\n          className\n        )}\n        {...rootProps}\n      >\n        {emptyState}\n      </div>\n    )\n  }\n\n  return (\n    <div\n      data-slot=\"image-grid\"\n      data-layout={layout}\n      className={cn(\n        layout === \"grid\"\n          ? \"grid grid-cols-2 gap-3 md:grid-cols-3\"\n          : \"columns-2 gap-3 md:columns-3\",\n        className\n      )}\n      {...rootProps}\n    >\n      {images.map((image) => {\n        const tile = (\n          <>\n            <span data-slot=\"image-grid-image\" className=\"contents\">\n              {renderImage?.(image) ?? (\n                <img\n                  src={image.src}\n                  alt={onSelect ? image.alt : image.alt}\n                  width={image.width}\n                  height={image.height}\n                  loading={image.loading ?? \"lazy\"}\n                  decoding=\"async\"\n                  className={cn(\n                    \"w-full object-cover transition-transform duration-200 ease-out group-hover:scale-[1.03] group-focus-visible:scale-[1.03] motion-reduce:transition-none motion-reduce:group-hover:scale-100 motion-reduce:group-focus-visible:scale-100\",\n                    layout === \"grid\" ? \"h-full\" : \"h-auto\"\n                  )}\n                />\n              )}\n            </span>\n            <span\n              data-slot=\"image-grid-caption\"\n              className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-3 pt-10 pb-3 text-sm font-semibold text-white\"\n            >\n              {image.caption ?? image.alt}\n            </span>\n          </>\n        )\n\n        const shape = cn(\n          \"group bg-muted relative w-full overflow-hidden rounded-[var(--radius)]\",\n          layout === \"grid\" ? \"aspect-[4/3]\" : \"mb-3 break-inside-avoid\"\n        )\n\n        // A tile is only a control when choosing one does something.\n        return onSelect ? (\n          <button\n            key={image.id}\n            data-slot=\"image-grid-item\"\n            type=\"button\"\n            aria-label={`Open ${image.alt}`}\n            className={cn(\n              shape,\n              \"focus-visible:ring-ring text-left focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none\"\n            )}\n            onClick={(event) => onSelect(image, event)}\n          >\n            {tile}\n          </button>\n        ) : (\n          <div key={image.id} data-slot=\"image-grid-item\" className={shape}>\n            {tile}\n          </div>\n        )\n      })}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "media",
    "gallery",
    "layout"
  ],
  "type": "registry:ui"
}