# Graphics gallery

`GraphicsGallery` is the section under the video where a viewer collects their pictures. Each graphic is a tile on the palette's own background with a download button. A graphic the scan has not found yet is a locked tile, so there is always one more to get.

> Live preview: try it at https://bettertyped.github.io/react-wrapped/docs/graphics/gallery/
>
> The share card, three insight cards and two that are still locked.

## Installation

```bash
pnpm add @react-wrapped/graphics @react-wrapped/core gsap
```

```tsx
import "@react-wrapped/core/styles.css";
import "@react-wrapped/graphics/styles.css";
```

## Usage

Describe each graphic as a `WrappedGraphic` and pass the list with the palette. A graphic can be an image that already has an address, or a function that draws it. Memoize the list: a tile draws its image again whenever its `image` function changes.

`things-to-keep.tsx`

```tsx
import { useMemo } from "react";

import { resolvePalette, type WrappedConfig } from "@react-wrapped/core";
import {
  GraphicsGallery,
  type InsightCardSpec,
  renderInsightCard,
  renderShareCard,
  type ShareCardSpec,
  type WrappedGraphic,
} from "@react-wrapped/graphics";

/** An insight card with a name for its file, and whether the scan still has to find it. */
type Insight = InsightCardSpec & { id: string; locked?: boolean };

export function ThingsToKeep({
  card,
  insights,
  config,
}: {
  card: ShareCardSpec;
  insights: readonly Insight[];
  config: WrappedConfig;
}) {
  const { palette: id, accent, mode } = config;
  const palette = useMemo(() => resolvePalette({ palette: id, accent, mode }), [id, accent, mode]);
  // Each graphic says how to draw itself. Nothing is drawn until its tile is about to scroll into view.
  const graphics = useMemo(
    (): WrappedGraphic[] => [
      {
        id: "share-card",
        title: "Share card",
        line: "The year on one card, sized for a post.",
        image: () => renderShareCard(card, palette),
        fileName: `${card.handle}-wrapped`,
        wide: true,
      },
      ...insights.map((insight) => ({
        id: insight.id,
        title: insight.name,
        line: insight.insight,
        image: () => renderInsightCard(insight, palette),
        fileName: `${card.handle}-${insight.id}`,
        wide: true,
        locked: insight.locked,
      })),
    ],
    [card, insights, palette],
  );
  return <GraphicsGallery graphics={graphics} palette={palette} />;
}
```

## Props

| Prop | Type | What it does |
| --- | --- | --- |
| `graphics` | `readonly WrappedGraphic[]` | The tiles. Unlocked ones are shown first, whatever order you pass. |
| `palette` | `WrappedPalette` | The video's palette: `resolvePalette(config)`. Each tile sits on its background. |
| `className?` | `string` | A class for the gallery's root. |
| `classNames?` | `SlotClassNames<GraphicsGallerySlot>` | A class per part, by slot name: `"gallery-tile"`, `"gallery-download"`. See [styling](https://bettertyped.github.io/react-wrapped/docs/guides/styling/). |

## A graphic

| Key | Type | What it is |
| --- | --- | --- |
| `id` | `string` | Stable and unique in the gallery. |
| `title` | `string` | The tile's heading. |
| `line` | `string` | One line under it. |
| `image` | `string \| (() => Promise<Blob>)` | Where the picture comes from: the address of an image, or a function that draws it when the tile is about to be seen. |
| `fileName` | `string` | The name of the saved file, without an extension. The gallery saves a PNG. |
| `markdown?` | `string` | A snippet that embeds the image. With one, the tile gets a second button that copies it. |
| `locked?` | `boolean` | Not found yet. The tile is shown closed, with nothing to download. |
| `wide?` | `boolean` | Wide artwork, such as a card. The tile takes two columns. |

## Locked tiles

Set `locked` on a graphic that belongs to a drop the [scan](https://bettertyped.github.io/react-wrapped/docs/scanner/) has not found. The tile shows a lock and "Still hidden", the heading counts only what can be taken, and the line under it says how many more are waiting. When the scan finds the drop, pass the graphic again without `locked` and its tile draws itself.

## Drawing and downloading

- A tile draws its image when it is about to scroll into view, not when the page loads. A gallery of ten cards three screens down costs nothing until someone gets there.
- Download saves a PNG named `fileName`. An image given by address is fetched and redrawn at twice its size first, so an SVG badge becomes a PNG people can post.
- With `markdown` set, a second button copies that snippet. Use it for images you host, such as a badge for a README that should stay live.
- A graphic with `wide` takes two columns. The grid is one column on a phone, two on a tablet and four on a wide screen.

## Related

- [Share card](https://bettertyped.github.io/react-wrapped/docs/graphics/share-card/)
- [Insight cards](https://bettertyped.github.io/react-wrapped/docs/graphics/insight-cards/)
- [Scanner](https://bettertyped.github.io/react-wrapped/docs/scanner/)
- [Graphics](https://bettertyped.github.io/react-wrapped/docs/graphics/)
- [Full Wrapped example](https://bettertyped.github.io/react-wrapped/examples/full-wrapped/)

Source: https://bettertyped.github.io/react-wrapped/docs/graphics/gallery/
