# Media

Photograph collections, lightboxes, image details and map controls.

- Group: Website
- Import: `import { MediaMasthead } from '@misoto22/folio/website'`
- Page: https://ui.misoto22.com/components/media/

## When to reach for it

Compose image browsing and map presentation while the host owns images and map lifecycles.

## Best practices

### Do

- Supply localized labels, descriptive links and meaningful image alternatives.

### Don’t

- Do not put service credentials, routing logic or backend models inside presentation components.

## MediaMasthead

The quiet opening to an image collection, before its controls and prints.

### Props

- `eyebrow` — `ReactNode`.
- `title` (required) — `ReactNode`.
- `description` — `ReactNode`.
- `headingLevel` — `1 | 2 | 3 | 4 | 5 | 6` default `1`. The title level when this page composition sits inside another document.

Also accepts: `Omit<HTMLAttributes<HTMLElement>, 'title'>`.

## PhotographFeature

A photograph and an editorial introduction, with the original image ratio.

### Props

- `image` (required) — `ReactNode`.
- `title` (required) — `ReactNode`.
- `eyebrow` — `ReactNode`.
- `description` — `ReactNode`.
- `caption` — `ReactNode`.
- `action` — `ReactNode`.

Also accepts: `Omit<HTMLAttributes<HTMLElement>, 'title'>`.

## MediaCollection

Page-level gallery composition; the host owns its filters and collection.

### Props

- `masthead` (required) — `ReactNode`.
- `controls` — `ReactNode`.
- `collectionId` — `string`.
- `footer` — `ReactNode`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## MediaGallery

Level mounts keep unlike aspect ratios aligned without cropping the prints.

### Props

- `children` — `ReactNode`.
- `emptyTitle` — `ReactNode`. Supply localized empty copy when the collection may have no children.
- `emptyAction` — `ReactNode`.

Also accepts: `HTMLAttributes<HTMLUListElement>`.

## MediaGalleryItem

### Props

- `children` (required) — `ReactElement`. A host link containing its image. The package supplies the print layout.
- `title` (required) — `ReactNode`.
- `index` — `ReactNode`.
- `location` — `ReactNode`.
- `orientation` — `'portrait' | 'landscape' | 'square'`.

Also accepts: `Omit<HTMLAttributes<HTMLLIElement>, 'title'>`.

## MediaGallerySkeleton

### Props

- `label` (required) — `string`.
- `count` — `number` default `6`.

## MediaDetailLayout

Portraits share a spread with their notes; landscapes lead a full-width spread.

### Props

- `title` (required) — `ReactNode`.
- `titleId` (required) — `string`.
- `headingLevel` — `1 | 2 | 3 | 4 | 5 | 6` default `1`. The title level when this page composition sits inside another document.
- `category` — `ReactNode`.
- `notesLabel` (required) — `string`.
- `orientation` (required) — `'portrait' | 'landscape' | 'square'`.
- `backLink` (required) — `ReactElement`.
- `index` — `ReactNode`.
- `media` (required) — `ReactNode`.
- `light` — `ReactNode`.
- `actions` — `ReactNode`.
- `metadata` — `ReactNode`.
- `location` — `ReactNode`.
- `pager` — `ReactNode`.
- `related` — `ReactNode`.

Also accepts: `Omit<HTMLAttributes<HTMLElement>, 'title'>`.

## MediaMetadata

### Props

- `items` (required) — `DescriptionListItem[]`.

Also accepts: `HTMLAttributes<HTMLDListElement>`.

## MediaPager

### Props

- `label` (required) — `string`.
- `previous` — `ReactNode`.
- `next` — `ReactNode`.

Also accepts: `HTMLAttributes<HTMLElement>`.

## MediaPagerItem

### Props

- `link` (required) — `ReactElement`.
- `direction` (required) — `'previous' | 'next'`.
- `label` (required) — `ReactNode`.
- `title` (required) — `ReactNode`.
- `image` — `ReactNode`.

## MediaThumbnailStrip

### Props

- `heading` (required) — `ReactNode`.

Also accepts: `HTMLAttributes<HTMLElement>`.

## MediaThumbnail

### Props

- `children` (required) — `ReactElement`.

## MediaLightNote

The host supplies observations and formatted labels; this is presentation only.

### Props

- `label` (required) — `ReactNode`.
- `lines` (required) — `MediaLightNoteLine[]`.

## MediaMapSection

### Props

- `title` (required) — `ReactNode`.
- `hint` — `ReactNode`.
- `aliasId` — `string`. An optional legacy anchor, owned and named by the host.
- `map` (required) — `ReactNode`.
- `preview` (required) — `ReactNode`.

Also accepts: `Omit<HTMLAttributes<HTMLElement>, 'title'>`.

## MediaMapFrame

### Props

- `label` — `ReactNode`.
- `hint` — `ReactNode`.
- `navigation` — `ReactNode`.
- `compact` — `boolean` default `false`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## MediaMapNavigation

### Props

- `zoomInLabel` (required) — `string`.
- `zoomOutLabel` (required) — `string`.
- `onZoomIn` (required) — `() => void`.
- `onZoomOut` (required) — `() => void`.

## MediaMapCanvas

A sized provider container. Map creation, themes and geographic math stay in the host.

Also accepts: `ComponentPropsWithRef<'div'>`.

## MediaMapPreview

### Props

- `title` (required) — `ReactNode`.
- `image` — `ReactNode`.
- `action` — `ReactNode`.
- `place` — `ReactNode`.
- `coordinates` — `ReactNode`.
- `children` — `ReactNode`.

## MediaMapAreaList

### Props

- `items` (required) — `MediaMapArea[]`.
- `value` — `string`.
- `onValueChange` (required) — `(value: string) => void`.

## MediaMapMarker

A keyboard-operable 44px marker for a host's map-provider portal.

### Props

- `label` (required) — `string`.
- `active` — `boolean` default `false`.

Also accepts: `Omit<Extract<ButtonProps, { href?: undefined }>, 'children' | 'iconOnly'>`.

## MediaMapPin

### Props

- `active` — `boolean` default `false`.

## MediaMapSkeleton

## MediaLightbox

A print and its controlled, focus-trapped full-screen dialog.

### Props

- `open` (required) — `boolean`.
- `onOpenChange` (required) — `(open: boolean) => void`.
- `title` (required) — `string`.
- `triggerLabel` (required) — `string`.
- `closeLabel` (required) — `string`.
- `aspectRatio` (required) — `number`. Intrinsic width divided by height; invalid dimensions use a safe fallback.
- `media` (required) — `ReactNode`.
- `expandedMedia` — `ReactNode` default `media`.

## Example — photograph collection

```tsx
import { MediaCollection, MediaGallery, MediaGalleryItem, MediaMasthead } from '@misoto22/folio/website'

<MediaCollection masthead={<MediaMasthead title="Field observations" description="A small collection of places and light." headingLevel={4} />}>
  <MediaGallery>
    <MediaGalleryItem title="The far shore" index="01" location="Coast" orientation="landscape">
      <a href="#far-shore"><svg viewBox="0 0 640 420" role="img" aria-label="Illustration of a shoreline beneath a pale sky">
        <rect width="640" height="420" fill="var(--bg-2)" /><path d="M0 270 L180 210 L420 260 L640 160 V420 H0Z" fill="var(--rule)" />
      </svg></a>
    </MediaGalleryItem>
    <MediaGalleryItem title="A quiet corner" index="02" location="City" orientation="portrait">
      <a href="#quiet-corner"><svg viewBox="0 0 420 560" role="img" aria-label="Illustration of a doorway with afternoon shadow">
        <rect width="420" height="560" fill="var(--bg-2)" /><path d="M110 90 H310 V560 H110Z" fill="var(--rule)" />
      </svg></a>
    </MediaGalleryItem>
  </MediaGallery>
</MediaCollection>
```

## Example — photograph detail

```tsx
import { MediaDetailLayout } from '@misoto22/folio/website'

<MediaDetailLayout
  title="Low tide"
  titleId="low-tide"
  headingLevel={4}
  notesLabel="Field notes for low tide"
  orientation="landscape"
  index="02 / 12"
  backLink={<a href="#field-observations">Back to field observations</a>}
  media={
    <figure className="m-0 w-full">
      <svg viewBox="0 0 960 540" role="img" aria-label="Illustration of a low shoreline at dusk">
        <rect width="960" height="540" fill="var(--bg-2)" />
        <path d="M0 350 L240 290 L480 340 L720 245 L960 310 V540 H0Z" fill="var(--rule)" />
      </svg>
    </figure>
  }
/>
```
