# Getting started

## Install

```bash
npm install @ogdakke/fluidbox
```

The framework bindings are in the same package. React, Vue, Solid and Angular are optional peer dependencies, so install only the one your app already uses.

## Import the styles

The viewer's layout and default look are in one stylesheet. Import it once, in a client entry, before your own CSS:

```ts
import "@ogdakke/fluidbox/styles.css";
```

The filmstrip and sheet add-ons have their own stylesheets, `@ogdakke/fluidbox/filmstrip.css` and `@ogdakke/fluidbox/sheet.css`. Import each only if you use that add-on.

## Describe your items

An item is a plain object whose fields are the attributes of the element the viewer draws: `src`, `srcset`, `sizes` and `alt` for an image, as on `<img>`. It needs `width` and `height`, so the viewer can size the slide before the file loads. A `thumbnail` gives the viewer a picture to show while the full image loads.

```ts
import type { FluidboxItem } from "@ogdakke/fluidbox";

export const photos: FluidboxItem[] = [
  {
    type: "img",
    src: "/photos/aurora.jpg",
    alt: "Aurora over the tundra",
    width: 2200,
    height: 3300,
    thumbnail: { src: "/photos/aurora-thumb.jpg", width: 440, height: 660 },
  },
];
```

Each item is known by its `id`, which defaults to its `src`. Give items an `id` of your own when two share a `src`, or when you want to open one by a name. See [Media types](https://fluidbox.dwe.workers.dev/docs/media.md) for video, iframes and custom content.

## A first gallery

One viewer for the whole list, and a trigger for each thumbnail. Pick your framework in the tabs:

#### React

`basic.tsx`

```tsx
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/react";
import { photos } from "./data";

export function Basic() {
  return (
    <Fluidbox.Root items={photos}>
      <div className="pattern-grid">
        {photos.map((photo) => (
          <Fluidbox.Trigger
            key={photo.src}
            item={photo}
            className="pattern-thumb"
          >
            <img src={photo.src} alt={itemLabel(photo)} />
          </Fluidbox.Trigger>
        ))}
      </div>
      {/* Without a Viewer, the viewer has these same three buttons. */}
      <Fluidbox.Viewer>
        <Fluidbox.Previous />
        <Fluidbox.Next />
        <Fluidbox.Close />
      </Fluidbox.Viewer>
    </Fluidbox.Root>
  );
}
```

#### Vue

`Basic.vue`

```vue
<script setup lang="ts">
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/vue";
import { photos } from "./data";
</script>

<template>
  <!-- Without a Viewer, the viewer has the default Previous, Next and Close. -->
  <Fluidbox.Root :items="photos">
    <div class="pattern-grid">
      <Fluidbox.Trigger
        v-for="photo in photos"
        :key="photo.id"
        :item="photo"
        class="pattern-thumb"
      >
        <img :src="photo.src" :alt="itemLabel(photo)" />
      </Fluidbox.Trigger>
    </div>
  </Fluidbox.Root>
</template>
```

#### Solid

`basic.tsx`

```tsx
import { For } from "solid-js";
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/solid";
import { photos } from "./data";

export function Basic() {
  return (
    <Fluidbox.Root items={photos}>
      <div class="pattern-grid">
        <For each={photos}>
          {(photo) => (
            <Fluidbox.Trigger item={photo} class="pattern-thumb">
              <img src={photo.src} alt={itemLabel(photo)} />
            </Fluidbox.Trigger>
          )}
        </For>
      </div>
      {/* Without a Viewer, the viewer has these three by default. */}
      <Fluidbox.Viewer>
        <Fluidbox.Previous />
        <Fluidbox.Next />
        <Fluidbox.Close />
      </Fluidbox.Viewer>
    </Fluidbox.Root>
  );
}
```

#### Angular

`basic.ts`

```ts
import { Component } from "@angular/core";
import { itemLabel } from "@ogdakke/fluidbox";
import { FluidboxModule } from "@ogdakke/fluidbox/angular";
import { photos } from "./data";

@Component({
  selector: "pattern-basic",
  standalone: true,
  imports: [FluidboxModule],
  template: `
    <div fluidboxRoot [items]="photos" class="pattern-grid">
      @for (photo of photos; track photo.id) {
        <button [fluidboxTrigger]="photo" class="pattern-thumb">
          <img [src]="photo.src" [alt]="label(photo)" />
        </button>
      }
      <!-- Without a viewer template these three are the default; with one, you place them. -->
      <ng-template fluidboxViewer>
        <button fluidboxPrevious></button>
        <button fluidboxNext></button>
        <button fluidboxClose></button>
      </ng-template>
    </div>
  `,
})
export class Basic {
  readonly photos = photos;
  readonly label = itemLabel;
}
```

#### Elements

`basic.html`

```html
<fluidbox-gallery class="pattern-grid">
  <fluidbox-item item-id="photo-aurora">
    <button slot="trigger" type="button" class="pattern-thumb">
      <img src="/art/aurora.svg" alt="Aurora over the tundra" />
    </button>
    <template slot="content">
      <img src="/art/aurora.svg" alt="Aurora over the tundra" width="1100" height="1650" />
    </template>
  </fluidbox-item>
  <fluidbox-item item-id="photo-ridge">
    <button slot="trigger" type="button" class="pattern-thumb">
      <img src="/art/ridge.svg" alt="Night ridge" />
    </button>
    <template slot="content">
      <img src="/art/ridge.svg" alt="Night ridge" width="1200" height="1500" />
    </template>
  </fluidbox-item>
  <fluidbox-item item-id="photo-fjord">
    <button slot="trigger" type="button" class="pattern-thumb">
      <img src="/art/fjord.svg" alt="Fjord in the blue hour" />
    </button>
    <template slot="content">
      <img src="/art/fjord.svg" alt="Fjord in the blue hour" width="1200" height="1600" />
    </template>
  </fluidbox-item>
  <fluidbox-item item-id="photo-lagoon">
    <button slot="trigger" type="button" class="pattern-thumb">
      <img src="/art/lagoon.svg" alt="Lagoon after sunset" />
    </button>
    <template slot="content">
      <img src="/art/lagoon.svg" alt="Lagoon after sunset" width="1800" height="1200" />
    </template>
  </fluidbox-item>
</fluidbox-gallery>
```

#### JavaScript

`basic.ts`

```ts
import { createFluidbox, itemLabel } from "@ogdakke/fluidbox";
import { photos } from "./data";

export function mountBasic(root: HTMLElement) {
  // The viewer has Previous, Next and Close buttons unless you draw your own.
  const fluidbox = createFluidbox({ items: photos });

  const grid = document.createElement("div");
  grid.className = "pattern-grid";
  photos.forEach((photo, index) => {
    const button = document.createElement("button");
    button.type = "button";
    button.className = "pattern-thumb";
    button.append(Object.assign(new Image(), { src: photo.src, alt: itemLabel(photo) }));
    grid.append(button);
    fluidbox.bind(button, index);
  });
  root.append(grid);

  return () => {
    // Also unbinds the buttons.
    fluidbox.destroy();
    grid.remove();
  };
}
```

The examples import `photos` from a local `./data` module, described in [the example data](https://fluidbox.dwe.workers.dev/docs/patterns.md#the-example-data). They import `itemLabel` from `@ogdakke/fluidbox`, which returns an item's `alt` or `title`. Every API shows the default Previous, Next and Close buttons until you give the viewer content of your own. After that, the `controls` option, the `Controls` part or `<fluidbox-controls>` keeps them.

## Framework setup

| Framework | Import | Peer dependency |
| --- | --- | --- |
| React | `@ogdakke/fluidbox/react` | `react` and `react-dom` 18 or later |
| Vue | `@ogdakke/fluidbox/vue` | `vue` 3.3 or later |
| Solid | `@ogdakke/fluidbox/solid` | `solid-js` 1.8 or later |
| Angular | `@ogdakke/fluidbox/angular` | `@angular/core` 17 or later |
| Custom elements | `@ogdakke/fluidbox/elements` | none |
| Plain JavaScript | `@ogdakke/fluidbox` | none |

Every binding has the same parts with the same names: `Root` holds the items and owns the viewer, `Trigger` opens an item, `Viewer` renders into the viewer's layer, and `Filmstrip` and `Sheet` turn those add-ons on by being there. `useFluidbox()` (`injectFluidbox()` in Angular) reads the state and returns the commands. Each framework page shows them in its own idiom: [React](https://fluidbox.dwe.workers.dev/docs/react.md), [Vue](https://fluidbox.dwe.workers.dev/docs/vue.md), [Solid](https://fluidbox.dwe.workers.dev/docs/solid.md), [Angular](https://fluidbox.dwe.workers.dev/docs/angular.md). [Next.js](https://fluidbox.dwe.workers.dev/docs/nextjs.md) covers the React binding with server components and `next/image`.

- **Custom elements.** `<fluidbox-gallery>` with `<fluidbox-item>` children. See [Custom elements](https://fluidbox.dwe.workers.dev/docs/custom-elements.md).
- **Plain JavaScript.** `createFluidbox()`, and `fluidbox.bind()` for each trigger. See [JavaScript](https://fluidbox.dwe.workers.dev/docs/javascript.md).

## Loading the viewer

The page loads the triggers, the items and the viewer's state. The viewer itself, with its gestures, transitions and slides, is a chunk of its own. By default it loads when a mouse rests on a trigger for 100 ms, a finger or pen touches one, or one takes focus, so it has usually arrived by the time the click does. An open loads it if it hasn't.

The `loading` option picks the moment:

| `loading` | The viewer's code loads |
| --- | --- |
| `"hover"` (default) | When a mouse rests on a trigger for 100 ms, a finger or pen touches one, or one takes focus. |
| `"press"` | When a trigger is pressed. Nothing loads for a pointer that only passes over. |
| `"eager"` | Once the page is idle, whether or not anyone opens a viewer. |

For a signal of your own, call `load()`. A route that's about to show the gallery, or a menu that leads to it, can start the download early:

```ts
menu.addEventListener("toggle", () => void fluidbox.load(), { once: true });
```

`load()` resolves `true` once the code has loaded and `false` if it failed, as when offline. A failed load tries again on the next call or open. The bindings take `loading` on Root and put `load` next to the commands, and `<fluidbox-gallery>` has a `loading` attribute and a `load()` method.

## Add-ons

The filmstrip and the sheet are add-ons with their own entries, so their code loads only when you use them. Turn one on, with an option, a framework part or a `<fluidbox-filmstrip>` or `<fluidbox-sheet>` child, and the fluidbox loads its entry. An open waits for it. To bundle an entry up front, import it once in the browser bundle:

```ts
import "@ogdakke/fluidbox/filmstrip";
import "@ogdakke/fluidbox/filmstrip.css";
import "@ogdakke/fluidbox/sheet";
import "@ogdakke/fluidbox/sheet.css";
```

Each needs its stylesheet, `filmstrip.css` or `sheet.css`, whichever way you turn it on. See [Filmstrip and sheet](https://fluidbox.dwe.workers.dev/docs/filmstrip-and-sheet.md).

## Server rendering

The package root, `@ogdakke/fluidbox`, is safe to import on the server. It has no side effects and touches no browser globals until you call `createFluidbox()`. The add-on entries are safe there too. The framework bindings create the viewer only in the browser, after mount, so they render on the server as plain markup. [Next.js](https://fluidbox.dwe.workers.dev/docs/nextjs.md) walks through one server-rendered setup end to end.

Importing `@ogdakke/fluidbox/elements` defines the custom elements, so that entry is browser only. In a server-rendered app, import it after hydration:

```ts
if (typeof customElements !== "undefined") await import("@ogdakke/fluidbox/elements");
```

## Next

- The [patterns](https://fluidbox.dwe.workers.dev/docs/patterns.md) show nine common setups in every framework.
- [Items and origins](https://fluidbox.dwe.workers.dev/docs/items-and-origins.md) covers large or virtualized lists, and where the flight lands.
- [Styling](https://fluidbox.dwe.workers.dev/docs/styling.md) shows how to theme the viewer and add your own controls.
