# PhotoPack

A responsive photo grid that arranges images into rows via breakpoint layouts, with per-image markdown captions.

**Category:** Components/Multimedia/PhotoPack

**Import:** `import { PhotoPack } from '@reuters-graphics/graphics-components'`

## Props

| Prop | Type | Default | Required | Description |
|------|------|---------|:--------:|-------------|
| `images` | `Image[]` | — | ✓ | Array of image objects |
| `layouts` | `Layout[]` | — |  | Array of layout objects |
| `gap` | `number` | `15` |  | Gap between images. |
| `id` | `string` | `'photopack-' + random4() + random4()` |  | Add an ID to target with SCSS. Should be unique from all other elements. |
| `class` | `string` | `''` |  | Add a class to target with SCSS. |
| `width` | `ContainerWidth` | `'normal'` |  | Width of the component within the text well: 'normal' \| 'wide' \| 'wider' \| 'widest' \| 'fluid' Options: `normal`, `wide`, `wider`, `widest`, `fluid` |
| `textWidth` | `ContainerWidth` | `'normal'` |  | Set a different width for captions within the text well. For example, "normal" to keep captions inline with the rest of the text well.  Can't ever be wider than `width`: 'normal' \| 'wide' \| 'wider' \| 'widest' \| 'fluid' Options: `normal`, `wide`, `wider`, `widest`, `fluid` |

## Types

```typescript
export interface Image {
    src: string;
    altText: string;
    caption?: string;
    maxHeight?: number;
  }

export interface Layout {
    breakpoint: number;
    rows: number[];
  }
```

## Examples

### Demo

```svelte
<PhotoPack
  width="wide"
  textWidth="normal"
  images={/* array — see Props/Types for full type */}
  layouts={[
    { breakpoint: 450, rows: [1, 2, 1] },
    { breakpoint: 750, rows: [1, 3] },
  ]}
/>
```

### ArchieML

```svelte
<PhotoPack archieMLBlock={/* object — see Props/Types for full type */} />
```

### Smart layouts

```svelte
{#snippet template(args)}
  {@const { imageCount, ...photoPackProps } =
    args as unknown as SmartDefaultsArgs}
  <PhotoPack
    {...photoPackProps}
    images={allImages.slice(0, imageCount || 4)}
  />
{/snippet}
```

## Documentation

# PhotoPack

The `PhotoPack` component makes simple photo grids with custom layouts at various breakpoints.

`images` are defined with their src, alt text, captions and an optional `maxHeight`, which ensures that an image is no taller than that height in any layout.

```javascript
const images = [
  {
    src: 'https://...',
    altText: 'Alt text',
    caption: 'Lorem ipsum. REUTERS/Photog',
    // Optional max-height of images across all layouts
    maxHeight: 800,
  },
  // ...
];
```

`layouts` optionally define how images are laid out at different breakpoints. You can customise the layouts and group images into `rows` above a certain `breakpoint` by specifying the number of images that should go in that row. For example:

```javascript
const layouts = [
  {
    breakpoint: 450,
    rows: [1, 2, 1],
  },
];
```

... tells the component that when the `PhotoPack` container is 450 pixels or wider, it should group the 4 images in 3 rows: 1 in the first, 2 in the second and 1 in the last.

If you don't specify any layouts, the component will use a default responsive layout based on the number of images in your pack.

You can define as many layouts for as many images as you like.

```svelte
<script>
  import { PhotoPack } from '@reuters-graphics/graphics-components';

  /** Array of photo metadata */
  const images = [
    {
      src: 'https://...',
      altText: 'Alt text',
      caption: 'Lorem ipsum. REUTERS/Photog',
      // Optional max-height of images across all layouts
      maxHeight: 800,
    },
    // ...
  ];

  /** Set the number of photos in each row at various breakpoints */
  const layouts = [
    {
      breakpoint: 450, // Applies to containers wider than 450px
      rows: [1, 2, 1], // Number of photos in each row
    },
    // Another layout for containers wider than 750px
    { breakpoint: 750, rows: [1, 3] },
  ];
</script>

<PhotoPack {images} {layouts} />
```

## Using with ArchieML docs

With the graphics kit, you'll likely get your text value from an ArchieML doc...

```yaml
# ArchieML doc
[blocks]
type: photo-pack
id: my-photo-pack # Optional
class: mb-2 # Optional
width: wide # Optional
textWidth: normal # Optional
gap: 10 # Optional; must be a number.

# Array of image metadata
  [.images]
    src: images/my-img-1.jpg
    altText: Alt text
    caption: Lorem ipsum. REUTERS/Photog

    src: images/my-img-2.jpg
    altText: Alt text
    caption: Lorem ipsum. REUTERS/Photog

    ...
  []

[]
```

... which you'll parse out of a ArchieML block object before passing to the `PhotoPack` component.

> **Important ❗**: The prop `gap` must be a number. ArchieML renders all values -- including numbers -- as strings, so convert the `prop` value to a number before passing it to `PhotoPack`.

```svelte
<!-- App.svelte -->
<script>
  import { PhotoPack } from '@reuters-graphics/graphics-components';
  import { asset } from '$app/paths'; // 👈 If using in the graphics kit...

  import content from '$locales/en/content.json';
</script>

{#each content.blocks as block}
  {#if block.type === 'photo-pack'}
    <!-- Use `asset` for the image source in graphics kit -->
    <PhotoPack
      id={block.id}
      class={block.class}
      width={block.width}
      textWidth={block.textWidth}
      gap={Number(block.gap)}
      images={block.images.map((img) => ({
        ...img,
        src: asset(`/${img.src}`),
      }))}
      layouts={[
        { breakpoint: 750, rows: [2, 3] },
        { breakpoint: 450, rows: [1, 2, 2] },
      ]}
    />
  {/if}
{/each}
```

## Smart default layouts

If you don't specify the `layouts` prop, `PhotoPack` will automatically generate responsive layouts based on the number of images and the container width.

**How it works:**

- **Desktop** (1024px+): Number of images per row depends on container width:
  - `normal`: max 2 per row
  - `wide` / `wider`: max 3 per row
  - `widest` / `fluid`: max 4 per row
- **Tablet** (768px+): Always max 2 per row
- **Mobile** (below 768px): 1 per row

The smart defaults use a **bottom-heavy distribution**, meaning earlier rows have fewer images (making them larger and more prominent), while later rows have more images.

**Examples:**

- 5 images, `wide` container, desktop: `[2, 3]` (2 in first row, 3 in second)
- 7 images, `widest` container, desktop: `[3, 4]` (3 in first row, 4 in second)
- 4 images, any container, desktop: `[2, 2]` (evenly distributed)

```svelte
<script>
  import { PhotoPack } from '@reuters-graphics/graphics-components';

  const images = [
    { src: asset('/image1.jpg'), altText: 'Photo 1', caption: 'Caption 1' },
    { src: asset('/image2.jpg'), altText: 'Photo 2', caption: 'Caption 2' },
    { src: asset('/image3.jpg'), altText: 'Photo 3', caption: 'Caption 3' },
    { src: asset('/image4.jpg'), altText: 'Photo 4', caption: 'Caption 4' },
  ];
</script>

<!-- No layouts prop = smart defaults! -->
<PhotoPack {images} width="wide" />
```
