Patterns
Filmstrip
The filmstrip is an optional add-on from @ogdakke/fluidbox/filmstrip. The default strip draws the items' thumbnails, or you draw each thumbnail yourself.
Loading the live example…
filmstrip.tsx
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/react";
import "@ogdakke/fluidbox/filmstrip.css";
import { photos } from "./data";
function Thumbnails() {
return (
<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>
);
}
export function Filmstrip() {
return (
<>
<Fluidbox.Root items={photos}>
<Thumbnails />
{/* Being there turns the filmstrip on. */}
<Fluidbox.Filmstrip />
</Fluidbox.Root>
<Fluidbox.Root items={photos}>
<Thumbnails />
{/* Your own thumbnails. The selected button has data-fluidbox-selected. */}
<Fluidbox.Filmstrip>
{({ index }) => (
<span className="pattern-filmstrip-thumb">
{index + 1}
</span>
)}
</Fluidbox.Filmstrip>
</Fluidbox.Root>
</>
);
}Filmstrip.vue
<script setup lang="ts">
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/vue";
import "@ogdakke/fluidbox/filmstrip.css";
import { photos } from "./data";
</script>
<template>
<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.Filmstrip />
</Fluidbox.Root>
<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>
<!-- Your own thumbnails. The selected button has data-fluidbox-selected. -->
<Fluidbox.Filmstrip v-slot="{ index }">
<span class="pattern-filmstrip-thumb">{{ index + 1 }}</span>
</Fluidbox.Filmstrip>
</Fluidbox.Root>
</template>filmstrip.tsx
import { For } from "solid-js";
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/solid";
import "@ogdakke/fluidbox/filmstrip.css";
import { photos } from "./data";
function Thumbnails() {
return (
<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>
);
}
export function Filmstrip() {
return (
<>
<Fluidbox.Root items={photos}>
<Thumbnails />
<Fluidbox.Filmstrip />
</Fluidbox.Root>
<Fluidbox.Root items={photos}>
<Thumbnails />
{/* Your own thumbnails. The selected button has data-fluidbox-selected. */}
<Fluidbox.Filmstrip>
{(thumb) => (
<span class="pattern-filmstrip-thumb">
{thumb.index + 1}
</span>
)}
</Fluidbox.Filmstrip>
</Fluidbox.Root>
</>
);
}filmstrip.ts
import { Component } from "@angular/core";
import { itemLabel } from "@ogdakke/fluidbox";
import { FluidboxModule } from "@ogdakke/fluidbox/angular";
import "@ogdakke/fluidbox/filmstrip.css";
import { photos } from "./data";
@Component({
selector: "pattern-filmstrip",
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>
}
<ng-container fluidboxFilmstrip />
</div>
<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>
}
<!-- Your own thumbnails. The selected button has data-fluidbox-selected. -->
<ng-template fluidboxFilmstrip let-index="index">
<span class="pattern-filmstrip-thumb">{{ index + 1 }}</span>
</ng-template>
</div>
`,
})
export class Filmstrip {
readonly photos = photos;
readonly label = itemLabel;
}filmstrip.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-filmstrip></fluidbox-filmstrip>
</fluidbox-gallery>
<fluidbox-gallery class="pattern-grid numbered-strip">
<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-filmstrip></fluidbox-filmstrip>
</fluidbox-gallery>filmstrip.ts
import "@ogdakke/fluidbox/filmstrip.css";
export function filmstrip(root: HTMLElement) {
// Your own thumbnails. The selected button has data-fluidbox-selected.
const strip = root.querySelector(".numbered-strip")!.querySelector("fluidbox-filmstrip")!;
strip.render = ({ index, target }) => {
const thumb = document.createElement("span");
thumb.className = "pattern-filmstrip-thumb";
thumb.textContent = String(index + 1);
target.append(thumb);
};
}filmstrip.ts
import { createFluidbox, itemLabel, type FluidboxOptions } from "@ogdakke/fluidbox";
import "@ogdakke/fluidbox/filmstrip";
import "@ogdakke/fluidbox/filmstrip.css";
import { photos } from "./data";
function gallery(root: HTMLElement, filmstrip: FluidboxOptions["filmstrip"]) {
const fluidbox = createFluidbox({ items: photos, filmstrip });
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 () => {
fluidbox.destroy();
grid.remove();
};
}
export function mountFilmstrip(root: HTMLElement) {
const standard = gallery(root, true);
// Your own thumbnails. The selected button has data-fluidbox-selected.
const custom = gallery(root, {
render({ index, target }) {
const thumb = document.createElement("span");
thumb.className = "pattern-filmstrip-thumb";
thumb.textContent = String(index + 1);
target.append(thumb);
return () => thumb.remove();
},
});
return () => {
standard();
custom();
};
}import "@ogdakke/fluidbox/filmstrip"once, thenfilmstrip: trueturns it on. In the frameworks, rendering theFilmstrippart turns it on. On custom elements, a<fluidbox-filmstrip>child does, and it loads the add-on itself. It shows only when there is more than one item.- For your own thumbnails, use
filmstrip: { render }in plain JavaScript, therenderproperty of<fluidbox-filmstrip>, or a child of the frameworkFilmstrippart. - The selected thumbnail's button has
data-fluidbox-selected, so CSS can mark it without your thumbnails tracking anything. - The add-on is its own import, so it costs nothing until you use it.