Patterns
One trigger for an album
One thumbnail stands for a whole album. It opens the first item, and every item in the album returns to it on close. It shows whichever item the viewer showed last, so the closing flight lands on a matching picture.
Loading the live example…
overlay.tsx
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/react";
import { album } from "./data";
export function Overlay() {
return (
<Fluidbox.Root items={album}>
{/* The first item opens; every album item closes back onto this trigger. */}
<Fluidbox.Trigger
item={album[0]!}
returns={album}
className="pattern-thumb pattern-album"
>
{({ item }) => (
<>
{/* Shows the item the viewer last showed. Keep the img mounted. */}
<img src={item?.src} alt={item ? itemLabel(item) : ""} />
<Fluidbox.ThumbnailOverlay travel className="pattern-overlay">
+{album.length - 1}
</Fluidbox.ThumbnailOverlay>
</>
)}
</Fluidbox.Trigger>
</Fluidbox.Root>
);
}Overlay.vue
<script setup lang="ts">
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/vue";
import { album } from "./data";
</script>
<template>
<Fluidbox.Root :items="album">
<!-- The cover opens the first photo; every album item closes back onto it. -->
<Fluidbox.Trigger
v-slot="{ item }"
:item="album[0]"
:returns="album"
class="pattern-thumb pattern-album"
>
<!-- Shows the item the viewer last showed. -->
<img :src="item?.src" :alt="item ? itemLabel(item) : ''" />
<Fluidbox.ThumbnailOverlay travel class="pattern-overlay">
+{{ album.length - 1 }}
</Fluidbox.ThumbnailOverlay>
</Fluidbox.Trigger>
</Fluidbox.Root>
</template>overlay.tsx
import { itemLabel } from "@ogdakke/fluidbox";
import { Fluidbox } from "@ogdakke/fluidbox/solid";
import { album } from "./data";
export function Overlay() {
return (
<Fluidbox.Root items={album}>
{/* The first item opens; every one of them closes back onto the trigger. */}
<Fluidbox.Trigger
item={album[0]!}
returns={album}
class="pattern-thumb pattern-album"
>
{(preview) => (
<>
{/* The preview follows the viewer, so the close lands on the right photo. */}
<img
src={preview().item?.src}
alt={preview().item ? itemLabel(preview().item!) : ""}
/>
<Fluidbox.ThumbnailOverlay travel class="pattern-overlay">
+{album.length - 1}
</Fluidbox.ThumbnailOverlay>
</>
)}
</Fluidbox.Trigger>
</Fluidbox.Root>
);
}overlay.ts
import { Component } from "@angular/core";
import { itemLabel } from "@ogdakke/fluidbox";
import { FluidboxModule } from "@ogdakke/fluidbox/angular";
import { album } from "./data";
@Component({
selector: "pattern-overlay",
standalone: true,
imports: [FluidboxModule],
template: `
<div fluidboxRoot [items]="album">
<!-- The cover opens the first photo; every one of them closes back onto it. -->
<button
[fluidboxTrigger]="album[0]"
[fluidboxReturns]="album"
#trigger="fluidboxTrigger"
class="pattern-thumb pattern-album"
>
<img
[src]="(trigger.preview().item ?? album[0]).src"
[alt]="label(trigger.preview().item ?? album[0])"
/>
<!-- The count flies with the photo, rendered live from its template. -->
<span fluidboxThumbnailOverlay travel class="pattern-overlay">
<ng-template>+{{ album.length - 1 }}</ng-template>
</span>
</button>
</div>
`,
})
export class Overlay {
readonly album = album;
readonly label = itemLabel;
}overlay.html
<fluidbox-gallery class="pattern-album">
<fluidbox-item item-id="album-dune" returns="album-canyon album-coast album-meadow album-alpine">
<button slot="trigger" type="button" class="pattern-thumb">
<img src="/art/dune.svg" alt="Dune at golden hour" />
<span class="pattern-overlay" data-fluidbox-thumbnail-overlay="travel"
>+4</span
>
</button>
<template slot="content">
<img src="/art/dune.svg" alt="Dune at golden hour" width="1800" height="1200" />
</template>
</fluidbox-item>
<fluidbox-item item-id="album-canyon">
<template slot="content">
<img src="/art/canyon.svg" alt="Canyon walls" width="1400" height="1400" />
</template>
</fluidbox-item>
<fluidbox-item item-id="album-coast">
<template slot="content">
<img src="/art/coast.svg" alt="Coastline panorama" width="2400" height="1000" />
</template>
</fluidbox-item>
<fluidbox-item item-id="album-meadow">
<template slot="content">
<img src="/art/meadow.svg" alt="Meadow hills" width="1600" height="1200" />
</template>
</fluidbox-item>
<fluidbox-item item-id="album-alpine">
<template slot="content">
<img src="/art/alpine.svg" alt="Alpine ridge at dawn" width="1920" height="1080" />
</template>
</fluidbox-item>
</fluidbox-gallery>overlay.ts
export function overlay(root: HTMLElement) {
const cover = root.querySelector("fluidbox-item")!;
const preview = cover.querySelector<HTMLImageElement>("img")!;
// Before the close flight lands, show the item it lands on.
cover.addEventListener("fluidbox-preview-change", ({ detail: { item } }) => {
if (item.type !== "img") return;
preview.src = item.src;
preview.alt = item.alt;
});
}overlay.ts
import { createFluidbox, itemLabel } from "@ogdakke/fluidbox";
import { album } from "./data";
export function mountOverlay(root: HTMLElement) {
const fluidbox = createFluidbox({ items: album });
const button = document.createElement("button");
button.type = "button";
button.className = "pattern-thumb pattern-album";
const preview = Object.assign(new Image(), { src: album[0]!.src, alt: itemLabel(album[0]!) });
const count = document.createElement("span");
count.className = "pattern-overlay";
count.textContent = `+${album.length - 1}`;
button.append(preview, count);
root.append(button);
// The cover opens the first photo; every one of them closes back onto it.
fluidbox.bind(button, album[0]!, {
returns: album,
onPreviewChange(item) {
preview.src = item.src;
preview.alt = itemLabel(item);
},
});
// The count flies with the photo.
fluidbox.bindOverlay(count, album[0]!, { returns: album, travel: true });
return () => {
fluidbox.destroy();
button.remove();
};
}- Multi-item trigger. The trigger opens its own item, and
returnslists every item that may close back onto it. Set it asreturnsinfluidbox.bindand on the frameworkTrigger(fluidboxReturnsin Angular), or asreturns="id id …"on a<fluidbox-item>. It may hold the whole album, the opened item included. - Preview follows the selection. The framework triggers hand their content the item they show (
{ item, index, status }). Plain JavaScript getsonPreviewChange(item, index). On custom elements, the<fluidbox-item>dispatchesfluidbox-preview-changewith{ id, item }, and you swap the trigger's image in its handler. Changes inside a trigger never make the gallery re-read its items. The preview updates before the closing flight measures it. - Thumbnail overlays. For a badge or caption on the thumbnail, use
fluidbox.bindOverlay(), theThumbnailOverlaypart, ordata-fluidbox-thumbnail-overlay. Withtravelthe overlay flies with the image and fades on landing;travelcan also be a renderer that draws live content into the flying frame. The framework parts render their content live there on their own. Angular's part takes it as an<ng-template>child. Withouttravel, the overlay stays in the page and gets--fluidbox-closedto animate against. A frame layer moves into the flying frame itself and scales with it, as a scrim does. It isFrameLayerin the frameworks (fluidboxFrameLayerin Angular),frameLayerin plain JavaScript, anddata-fluidbox-frame-layerin markup.