# Media Grid

> Photos and videos as a grid of square tiles that open the Lightbox, with your photo consent rule applied.

- Name: `media-grid`
- Status: beta
- Categories: media, display
- File: the install writes `media-grid.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/icon`, `@arbokui/lightbox`, `@arbokui/media-image`. The install adds them.
- Gallery: https://arbokui.com/components/media-grid
- Registry item: https://arbokui.com/r/media-grid.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/media-grid
   ```

## Use it for

- A set of photos and videos, like an event's gallery or a team's album.
- Name the set with label. It names the list and the lightbox.
- Pass manage only to people who manage your media. They see hidden photos as tiles that say why.
- Set minSize for smaller or larger tiles. Columns fill the width.
- Return false from onOpen to open a photo yourself, like on its own page.

## Avoid it for

- One photo. Use MediaImage.
- Files, documents, or a list of attachments.
- Photos that people pick, as in a form. Its tiles open the viewer, they do not select.

## Props

- `items`: `MediaItem[]`. Required. The photos and videos, in order. See MediaImage for MediaItem.
- `manage`: `boolean`. Default `false`. For people who manage your media: hidden photos show as tiles that say why, and the lightbox names people.
- `minSize`: `number`. Default `150`. The smallest a tile gets, in pixels. Tiles sit 6px apart.
- `label`: `string`. Default `"Photos"`. The set's name, for the list and the lightbox.
- `actions`: `(item: MediaItem) => { icon: ReactNode; label: string; onClick: () => void }[]`. Icon buttons in the lightbox for the photo shown.
- `onOpen`: `(index: number, item: MediaItem) => void | false`. A tile was opened, with its place in items. Return false to open it yourself instead of in the lightbox.
- `photoConsent`: `{ who?: "everyone" | "juniors" | "nobody"; missing?: "blur" | "hide" }`. Default `{ who: "everyone", missing: "blur" }`. Your consent rule. See MediaImage.
- `className`: `string`. Extra classes on the list. Later classes win.

## Accessibility

- A list named by label. Each tile is a button named by its photo, plus video and its length, a blur, or why it is hidden.
- One Tab stop. ← and → move along a row, ↑ and ↓ move between rows by the grid's own columns, and Home and End go to the ends. Right to left, ← and → swap.
- Enter or Space opens the photo in the Lightbox. When it closes, focus is on the tile of the photo you were looking at.
- Members never meet a hidden photo, not even in the Tab order.

## Examples

### A gallery as members see it

```tsx
<MediaGrid items={photos} label="Photos from the team offsite" />
```

### For a media manager, with actions

```tsx
import { Download, Trash2 } from "lucide-react";

<MediaGrid
  items={photos}
  label="Photos from the launch"
  manage
  minSize={120}
  actions={(photo) => [
    { icon: <Download />, label: "Download", onClick: () => download(photo) },
    { icon: <Trash2 />, label: "Delete", onClick: () => remove(photo) },
  ]}
/>
```
