# Gallery

A justified photo grid whose photos open from exactly where they sit: swipe to travel, pull down to put one back, pinch or double-tap to look closer.

- Family: Signature, imported from `mizu-ui` or `mizu-ui/signature`
- Docs: https://mizu.user64bit.world/components/gallery
- Source: https://github.com/0xuser64bit/mizu/blob/main/packages/mizu/src/signature/Gallery.tsx

## Usage

```tsx
import { Gallery } from "mizu-ui";

export function Example() {
  const field = (ground: string, mark: string) =>
    `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="720" height="360"><pattern id="d" width="36" height="36" patternUnits="userSpaceOnUse"><path d="M18 13l5 5-5 5-5-5z" fill="${mark}"/></pattern><rect width="720" height="360" fill="${ground}"/><rect width="720" height="360" fill="url(#d)"/></svg>`)}`;
  return (
    <Gallery
      label="Field studies"
      rowHeight={160}
      items={[
        {
          id: "ink",
          src: field("#141413", "#7e776b"),
          alt: "Diamond field on ink",
          width: 720,
          height: 360,
          caption: "Field, ink",
        },
        {
          id: "paper",
          src: field("#f4f0e8", "#93877a"),
          alt: "Diamond field on paper",
          width: 720,
          height: 360,
          caption: "Field, paper",
        },
      ]}
    />
  );
}
```

## Copy it with the shadcn CLI

```sh
npx shadcn@latest init 0xuser64bit/mizu/preset # once, where there is no components.json
npx shadcn@latest add 0xuser64bit/mizu/gallery
```

## Props

- `label / items`: `string / GalleryItem[]`. Region name; each photo has an id, src, alt, width and height, with an optional thumbnail and caption.
- `index / defaultIndex / onIndexChange`: `number | null`. The open photo, or null while the viewer is closed.
- `rowHeight / gap`: `number`. Rows aim for this height (220) and full rows adjust to fill the width exactly; gap defaults to 6.
- `loading / empty`: `boolean / ReactNode`. Skeleton rows with a scan line, or what to show when there are no photos.
- `justifyRows()`: `(sizes, width, target, gap) => Row[]`. The layout on its own, for server rendering or grids of your own.

## Keyboard

- ← →: Previous or next photo, in the grid or viewer
- ↑ ↓: The row above or below, in the grid
- Home End: First or last photo
- + − 0: Zoom in, out, or back to fit
- Esc: Put the photo back

## Accessibility

Photos are buttons named by their alt text, in a list; arrow keys move between them, up and down by row. The viewer is a native modal dialog: the page goes inert and stops scrolling, Escape closes, and focus returns to the photo you closed on. Position and alt text are announced as you travel, and every gesture also has a key or a button.

## Motion

A photo flies out of its tile into the viewer, and back into the tile of whichever photo you close on. Swipes follow your finger with resistance at the ends; pulling down shrinks the photo and thins the backdrop until it lets go. Zoom springs around the point you pinch, scroll or double-tap. Reduced motion trades the flight for a short fade.
