Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ The following plugins are bundled in the package:
slideshow button
- [Thumbnails](https://yet-another-react-lightbox.com/plugins/thumbnails) - adds
thumbnails track
- [Filmstrip](https://yet-another-react-lightbox.com/plugins/filmstrip) - adds a
scrollable virtualized thumbnail rail
- [Video](https://yet-another-react-lightbox.com/plugins/video) - adds support
for video slides
- [Zoom](https://yet-another-react-lightbox.com/plugins/zoom) - adds image zoom
Expand Down
126 changes: 126 additions & 0 deletions dev/AppWithFilmstrip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* To test the filmstrip plugin, replace the import of App.js with AppWithFilmstrip.js in index.tsx */

import * as React from "react";

import Lightbox from "../src/index.js";
import Counter from "../src/plugins/counter/index.js";
import Captions from "../src/plugins/captions/index.js";
import Download from "../src/plugins/download/index.js";
import Fullscreen from "../src/plugins/fullscreen/index.js";
import Share from "../src/plugins/share/index.js";
import Slideshow from "../src/plugins/slideshow/index.js";
import Filmstrip from "../src/plugins/filmstrip/index.js";
import Video from "../src/plugins/video/index.js";
import Zoom from "../src/plugins/zoom/index.js";

import "../src/styles.scss";
import "../src/plugins/counter/counter.scss";
import "../src/plugins/captions/captions.scss";
import "../src/plugins/filmstrip/filmstrip.scss";

import slides from "./slides.js";

type Example = {
id: string;
title: string;
slides?: React.ComponentProps<typeof Lightbox>["slides"];
carousel?: { finite?: boolean; preload?: number };
filmstrip?: NonNullable<React.ComponentProps<typeof Lightbox>["filmstrip"]>;
};

const EXAMPLES: Example[] = [
{
id: "default",
title: "Default",
},
{
id: "finite",
title: "Finite carousel",
carousel: { finite: true },
},
{
id: "single-slide-default-viewport",
title: "Single slide, default filmstrip viewport",
carousel: { preload: 2 },
slides: slides.slice(0, 1),
},
{
id: "preload-2-viewport-full",
title: "Many slides, full-width filmstrip viewport",
carousel: { preload: 2 },
filmstrip: { scrollViewportMax: "full" },
},
{
id: "start",
title: "Vertical rail on the start edge",
filmstrip: { position: "start" },
},
{
id: "hidden-start",
title: "Hidden until the user shows it",
filmstrip: { hidden: true, showToggle: true },
},
{
id: "hide-scrollbar",
title: "Hide scrollbar",
filmstrip: { hideScrollbar: true },
},
{
id: "styling",
title: "Styling the filmstrip",
filmstrip: {
width: 72,
height: 48,
gap: 8,
padding: 2,
border: 2,
borderRadius: 8,
borderStyle: "dashed",
borderColor: "rgba(147, 197, 253, 0.95)",
},
},
];

export default function AppWithFilmstrip() {
const [openId, setOpenId] = React.useState<string | null>(null);
const active = openId ? EXAMPLES.find((e) => e.id === openId) : undefined;

const labelsForExample =
active?.id === "labels"
? {
Filmstrip: "Slide filmstrip",
"Show filmstrip": "Reveal filmstrip",
"Hide filmstrip": "Hide filmstrip",
}
: undefined;

return (
<div className="dev-filmstrip">
<h1 className="dev-filmstrip-heading">Filmstrip examples</h1>

<div className="dev-filmstrip-examples">
{EXAMPLES.map((ex) => (
<section key={ex.id} className="dev-filmstrip-example">
<h2 className="dev-filmstrip-example-title">{ex.title}</h2>
<button type="button" className="button" aria-haspopup="dialog" onClick={() => setOpenId(ex.id)}>
Open this example
</button>
</section>
))}
</div>

{active && (
<Lightbox
key={active.id}
open
close={() => setOpenId(null)}
slides={active.slides ?? slides}
carousel={active.carousel ?? {}}
filmstrip={active.filmstrip ?? {}}
labels={labelsForExample}
plugins={[Captions, Counter, Download, Share, Fullscreen, Slideshow, Filmstrip, Video, Zoom]}
/>
)}
</div>
);
}
3 changes: 2 additions & 1 deletion dev/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from "react";
import * as ReactDOM from "react-dom/client";

import App from "./App.js";
// import App from "./App.js";
import App from "./AppWithFilmstrip.js";
import "./styles.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
Expand Down
16 changes: 16 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ via corresponding CSS class.
<td>thumbnailsContainer</td>
<td>yarl__thumbnails_container</td>
</tr>
<tr>
<td>filmstripContainer</td>
<td>yarl__filmstrip_container</td>
</tr>
<tr>
<td>filmstripTrack</td>
<td>yarl__filmstrip_track</td>
</tr>
<tr>
<td>filmstripThumbnail</td>
<td>yarl__filmstrip_thumbnail</td>
</tr>
<tr>
<td>filmstripScrollViewport</td>
<td>yarl__filmstrip_scroll_viewport</td>
</tr>
</tbody>
</table>

Expand Down
1 change: 1 addition & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The following plugins are bundled in the package:
- [Captions](/plugins/captions) - adds support for slide title and description
- [Counter](/plugins/counter) - adds slides counter
- [Download](/plugins/download) - adds download button
- [Filmstrip](/plugins/filmstrip) - adds a scrollable filmstrip preview rail
- [Fullscreen](/plugins/fullscreen) - adds support for fullscreen mode
- [Inline](/plugins/inline) - transforms the lightbox into an image carousel
- [Share](/plugins/share) - adds sharing button
Expand Down
193 changes: 193 additions & 0 deletions docs/plugins/filmstrip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Filmstrip Plugin

The Filmstrip plugin adds a scrollable preview rail along one edge of the
lightbox. Tap a preview to jump to that slide; built-in images use
`loading="lazy"`. Choose this or [Thumbnails](/plugins/thumbnails), not both.

The plugin comes with an additional CSS stylesheet.

```jsx
import "yet-another-react-lightbox/plugins/filmstrip.css";
```

## Documentation

The Filmstrip plugin adds the following `Lightbox` properties:

<table class="docs">
<tbody>
<tr>
<td>filmstrip</td>
<td>
&#123;<br />
&nbsp;&nbsp;ref?: React.ForwardedRef&#8203;&lt;FilmstripRef&gt;;<br />
&nbsp;&nbsp;position?: "top" | "bottom" | "start" | "end";<br />
&nbsp;&nbsp;width?: number;<br />
&nbsp;&nbsp;height?: number;<br />
&nbsp;&nbsp;border?: number;<br />
&nbsp;&nbsp;borderStyle?: string;<br />
&nbsp;&nbsp;borderColor?: string;<br />
&nbsp;&nbsp;borderRadius?: number;<br />
&nbsp;&nbsp;padding?: number;<br />
&nbsp;&nbsp;gap?: number;<br />
&nbsp;&nbsp;imageFit?: "contain" | "cover";<br />
&nbsp;&nbsp;vignette?: boolean;<br />
&nbsp;&nbsp;hidden?: boolean;<br />
&nbsp;&nbsp;showToggle?: boolean;<br />
&nbsp;&nbsp;hideScrollbar?: boolean;<br />
&nbsp;&nbsp;scrollViewportMax?: "full" | number | string;<br />
&#125;
</td>
<td>
<p>Filmstrip plugin settings:</p>
<ul>
<li>`ref` - Filmstrip plugin ref. See <a href="#FilmstripRef">Filmstrip Ref</a> for details.</li>
<li>`position` - rail position</li>
<li>`width` - preview width (px)</li>
<li>`height` - preview height (px)</li>
<li>`border` - border width</li>
<li>`borderStyle` - border style</li>
<li>`borderColor` - border color</li>
<li>`borderRadius` - border radius (px)</li>
<li>`padding` - inner padding (px)</li>
<li>`gap` - gap between previews (px)</li>
<li>`imageFit` - `object-fit` for built-in images</li>
<li>`vignette` - edge fade on the scroll viewport</li>
<li>`hidden` - if `true`, rail starts hidden</li>
<li>`showToggle` - if `true`, show filmstrip show/hide in the toolbar</li>
<li>`hideScrollbar` - if `true`, hide scrollbars (scroll still works)</li>
<li>
`scrollViewportMax` - max size of the scroll viewport on the strip axis; omit for a cap derived from
`carousel.preload`; `"full"` / number (px) / raw CSS string
</li>
</ul>
<p>
Defaults:
<span class="font-mono">
&#123; position: "bottom", width: 120, height: 80, border: 1, borderRadius: 4, padding: 4, gap: 16,
imageFit: "contain", vignette: true, hidden: false, showToggle: false, hideScrollbar: false &#125;
</span>
</p>
</td>
</tr>
<tr>
<td>render</td>
<td>
&#123;<br />
&nbsp;&nbsp;thumbnail?: (props: RenderThumbnailProps) => React.ReactNode;<br />
&nbsp;&nbsp;buttonFilmstrip?: (props: FilmstripRef) => React.ReactNode;<br />
&nbsp;&nbsp;iconFilmstripVisible?: () => React.ReactNode;<br />
&nbsp;&nbsp;iconFilmstripHidden?: () => React.ReactNode;<br />
&#125;
</td>
<td>
<p>`thumbnail` - custom cell content (same as Thumbnails). `buttonFilmstrip`, `iconFilmstripVisible`, `iconFilmstripHidden` - toolbar show/hide.</p>
</td>
</tr>
<tr>
<td>labels</td>
<td>
&#123;<br />
&nbsp;&nbsp;Filmstrip?: string;<br />
&nbsp;&nbsp;"Show filmstrip"?: string;<br />
&nbsp;&nbsp;"Hide filmstrip"?: string;<br />
&#125;
</td>
<td>
<ul>
<li>`Filmstrip` - `aria-label` for the rail</li>
<li>`Show filmstrip` / `Hide filmstrip` - titles when `showToggle` is on</li>
</ul>
</td>
</tr>
<tr>
<td>styles</td>
<td>
&#123;<br />
&nbsp;&nbsp;filmstripContainer?: React.CSSProperties;<br />
&nbsp;&nbsp;filmstripTrack?: React.CSSProperties;<br />
&nbsp;&nbsp;filmstripThumbnail?: React.CSSProperties;<br />
&nbsp;&nbsp;filmstripScrollViewport?: React.CSSProperties;<br />
&#125;
</td>
<td>Inline styles for container, track, thumbnail buttons, scroll viewport.</td>
</tr>
</tbody>
</table>

and the following `Slide` properties:

<table class="docs">
<tbody>
<tr>
<td>thumbnail</td>
<td>string</td>
<td>Optional preview URL (defaults to slide `src`; video can use `poster`).</td>
</tr>
</tbody>
</table>

## Filmstrip Ref

The plugin provides a ref object to control its features externally.

```jsx
// Filmstrip ref usage example

const filmstripRef = React.useRef(null);

// ...

return (
<Lightbox
plugins={[Filmstrip]}
filmstrip={{ ref: filmstripRef, showToggle: true }}
on={{
click: () => {
(filmstripRef.current?.visible
? filmstripRef.current?.hide
: filmstripRef.current?.show)?.();
},
}}
// ...
/>
);
```

<table class="docs">
<tbody>
<tr>
<td>visible</td>
<td>boolean</td>
<td>If `true`, the rail is visible.</td>
</tr>
<tr>
<td>show</td>
<td>() => void</td>
<td>Show the rail.</td>
</tr>
<tr>
<td>hide</td>
<td>() => void</td>
<td>Hide the rail.</td>
</tr>
</tbody>
</table>

## Example

```jsx
import Lightbox from "yet-another-react-lightbox";
import Filmstrip from "yet-another-react-lightbox/plugins/filmstrip";
import "yet-another-react-lightbox/styles.css";
import "yet-another-react-lightbox/plugins/filmstrip.css";

// ...

return (
<Lightbox
plugins={[Filmstrip]}
// ...
/>
);
```
Loading