react-motion-gallery 2.0.19 → 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +14 -4
- package/README.md +714 -295
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/dist/FullscreenRuntime-DVRVGK5F.mjs +4 -0
- package/dist/FullscreenRuntime-QX6YELBN.css +1 -0
- package/dist/GridSkeleton-B5wWBN9L.d.mts +23 -0
- package/dist/MasonrySkeleton-D8aZRUiv.d.mts +29 -0
- package/dist/{chunk-NABNX5HB.mjs → chunk-4NT4UVB5.mjs} +1 -1
- package/dist/chunk-4VHNCVVB.mjs +0 -0
- package/dist/chunk-5ZLGEQ55.mjs +1 -0
- package/dist/chunk-6PUPWNGD.mjs +1 -0
- package/dist/chunk-6XG7U4FJ.mjs +1 -0
- package/dist/chunk-ADIHG7AT.mjs +1 -0
- package/dist/chunk-AX2FSVFD.mjs +2 -0
- package/dist/chunk-B4CC5AGE.mjs +1 -0
- package/dist/chunk-BJBHSWMF.mjs +1 -0
- package/dist/chunk-C6PKH3FH.mjs +1 -0
- package/dist/chunk-CKXN2H7S.mjs +1 -0
- package/dist/chunk-D3T6HIS2.mjs +1 -0
- package/dist/chunk-DCUCXQHE.mjs +3 -0
- package/dist/chunk-EFXHC36P.mjs +0 -0
- package/dist/chunk-G5G54AZD.mjs +4 -0
- package/dist/chunk-GNGOOOVY.mjs +1 -0
- package/dist/chunk-GTS3HDGY.mjs +1 -0
- package/dist/chunk-H6XFG3CJ.mjs +1 -0
- package/dist/chunk-HGY3QLCE.mjs +1 -0
- package/dist/chunk-HK2DPKES.mjs +1 -0
- package/dist/chunk-HMT6GZUO.mjs +4 -0
- package/dist/chunk-JJMFOLJZ.mjs +1 -0
- package/dist/chunk-L2HRIINV.mjs +1 -0
- package/dist/chunk-L4TRPKGX.mjs +4 -0
- package/dist/chunk-LVMYHM4T.mjs +1 -0
- package/dist/chunk-MKCNUQFD.mjs +1 -0
- package/dist/chunk-NHIKOJLU.mjs +1 -0
- package/dist/chunk-NQI246HG.mjs +1 -0
- package/dist/chunk-ONCDUVQT.mjs +5 -0
- package/dist/chunk-OZSG45IP.mjs +4 -0
- package/dist/chunk-PFEGIWQJ.mjs +1 -0
- package/dist/chunk-PPFUWP7C.mjs +3 -0
- package/dist/chunk-R6EGYRTJ.mjs +6 -0
- package/dist/chunk-RLT5FULN.mjs +0 -0
- package/dist/chunk-RNLUNA5L.mjs +2 -0
- package/dist/chunk-STRS7UNJ.mjs +1 -0
- package/dist/chunk-TIQVSK5S.mjs +1 -0
- package/dist/chunk-UML6FCOQ.mjs +1 -0
- package/dist/chunk-UP6P6CQS.mjs +2 -0
- package/dist/chunk-UUAWLGWO.mjs +1 -0
- package/dist/chunk-V25YIPLC.mjs +1 -0
- package/dist/chunk-V7DPXRZF.mjs +1 -0
- package/dist/chunk-VGXO2IAF.mjs +1 -0
- package/dist/chunk-VPFAU2TE.mjs +1 -0
- package/dist/chunk-VWEQRZ24.mjs +1 -0
- package/dist/chunk-WGVWASZM.mjs +1 -0
- package/dist/chunk-WMG2LTLR.mjs +1 -0
- package/dist/chunk-X4HEGEZV.mjs +1 -0
- package/dist/chunk-XUQO5F2F.mjs +1 -0
- package/dist/chunk-Y7NUGXTR.mjs +1 -0
- package/dist/chunk-YIUXD3CO.mjs +1 -0
- package/dist/chunk-Z34PSRMG.mjs +1 -0
- package/dist/chunk-ZCCYTID7.mjs +1 -0
- package/dist/chunk-ZFDPVAPM.mjs +6 -0
- package/dist/chunk-ZXW3RIAC.mjs +1 -0
- package/dist/core.d.mts +22 -22
- package/dist/core.mjs +1 -1
- package/dist/entries.css +1 -1
- package/dist/entries.d.mts +49 -17
- package/dist/entries.mjs +1 -1
- package/dist/force-C5m1QpdF.d.mts +7 -0
- package/dist/fullscreen-captions.css +1 -0
- package/dist/fullscreen-captions.d.mts +19 -0
- package/dist/fullscreen-captions.mjs +1 -0
- package/dist/fullscreen-controls.d.mts +19 -0
- package/dist/fullscreen-controls.mjs +1 -0
- package/dist/fullscreen-crossfade.d.mts +19 -0
- package/dist/fullscreen-crossfade.mjs +1 -0
- package/dist/fullscreen-lazy-load.css +1 -0
- package/dist/fullscreen-lazy-load.d.mts +19 -0
- package/dist/fullscreen-lazy-load.mjs +1 -0
- package/dist/fullscreen-slider.css +1 -0
- package/dist/fullscreen-slider.d.mts +19 -0
- package/dist/fullscreen-slider.mjs +1 -0
- package/dist/fullscreen-thumbnails.d.mts +19 -0
- package/dist/fullscreen-thumbnails.mjs +1 -0
- package/dist/fullscreen-video.css +1 -0
- package/dist/fullscreen-video.d.mts +19 -0
- package/dist/fullscreen-video.mjs +1 -0
- package/dist/fullscreen-zoom-pan.d.mts +19 -0
- package/dist/fullscreen-zoom-pan.mjs +1 -0
- package/dist/fullscreen.css +1 -1
- package/dist/fullscreen.d.mts +29 -67
- package/dist/fullscreen.mjs +1 -1
- package/dist/fullscreenThumbnails.css +1 -1
- package/dist/fullscreenThumbnails.d.mts +8 -8
- package/dist/fullscreenThumbnails.mjs +1 -1
- package/dist/grid-lazy-load.css +1 -0
- package/dist/grid-lazy-load.d.mts +9 -0
- package/dist/grid-lazy-load.mjs +1 -0
- package/dist/grid-ready.d.mts +12 -0
- package/dist/grid-ready.mjs +1 -0
- package/dist/grid.css +1 -1
- package/dist/grid.d.mts +8 -29
- package/dist/grid.mjs +1 -1
- package/dist/index-DG19CAvz.d.mts +21 -0
- package/dist/{index-CwwxTQKa.d.mts → index-lEnLoQv4.d.mts} +5 -6
- package/dist/index.css +1 -1
- package/dist/index.d.mts +25 -28
- package/dist/index.mjs +1 -1
- package/dist/layout-DoYnPD0I.d.mts +137 -0
- package/dist/lazy-dGoYpcRa.d.mts +14 -0
- package/dist/masonry-lazy-load.css +1 -0
- package/dist/masonry-lazy-load.d.mts +9 -0
- package/dist/masonry-lazy-load.mjs +1 -0
- package/dist/masonry-ready.d.mts +12 -0
- package/dist/masonry-ready.mjs +1 -0
- package/dist/masonry.css +1 -1
- package/dist/masonry.d.mts +14 -13
- package/dist/masonry.mjs +1 -1
- package/dist/{plyrTypes-Cq4C3ul5.d.mts → media.d.mts} +1 -8
- package/dist/media.mjs +1 -0
- package/dist/metafile-esm.json +1 -1
- package/dist/plyrTypes-DhzgHNfX.d.mts +9 -0
- package/dist/responsive-CrESIWcm.d.mts +530 -0
- package/dist/responsive.d.mts +15 -0
- package/dist/responsive.mjs +1 -0
- package/dist/responsiveNumber-CouEMJ9O.d.mts +5 -0
- package/dist/skeleton-base.css +1 -0
- package/dist/skeleton-base.d.mts +52 -0
- package/dist/skeleton-base.mjs +1 -0
- package/dist/skeleton-grid.css +1 -0
- package/dist/skeleton-grid.d.mts +63 -0
- package/dist/skeleton-grid.mjs +1 -0
- package/dist/skeleton-masonry.css +1 -0
- package/dist/skeleton-masonry.d.mts +62 -0
- package/dist/skeleton-masonry.mjs +1 -0
- package/dist/skeleton-slider.css +1 -0
- package/dist/skeleton-slider.d.mts +208 -0
- package/dist/skeleton-slider.mjs +40 -0
- package/dist/slider-arrows.d.mts +9 -0
- package/dist/slider-arrows.mjs +1 -0
- package/dist/slider-auto-height.d.mts +9 -0
- package/dist/slider-auto-height.mjs +1 -0
- package/dist/slider-auto-play.d.mts +9 -0
- package/dist/slider-auto-play.mjs +1 -0
- package/dist/slider-auto-scroll.d.mts +9 -0
- package/dist/slider-auto-scroll.mjs +1 -0
- package/dist/slider-crossfade.d.mts +9 -0
- package/dist/slider-crossfade.mjs +1 -0
- package/dist/slider-dots.css +1 -0
- package/dist/slider-dots.d.mts +9 -0
- package/dist/slider-dots.mjs +1 -0
- package/dist/slider-fade.d.mts +9 -0
- package/dist/slider-fade.mjs +1 -0
- package/dist/slider-fullscreen.d.mts +9 -0
- package/dist/slider-fullscreen.mjs +1 -0
- package/dist/slider-lazy-load.css +1 -0
- package/dist/slider-lazy-load.d.mts +9 -0
- package/dist/slider-lazy-load.mjs +1 -0
- package/dist/slider-loading.css +1 -0
- package/dist/slider-loading.d.mts +9 -0
- package/dist/slider-loading.mjs +1 -0
- package/dist/slider-parallax.d.mts +9 -0
- package/dist/slider-parallax.mjs +1 -0
- package/dist/slider-progress.d.mts +9 -0
- package/dist/slider-progress.mjs +1 -0
- package/dist/slider-ready.d.mts +14 -0
- package/dist/slider-ready.mjs +1 -0
- package/dist/slider-ripple.d.mts +9 -0
- package/dist/slider-ripple.mjs +1 -0
- package/dist/slider-scale.d.mts +9 -0
- package/dist/slider-scale.mjs +1 -0
- package/dist/slider-scrollbar.css +1 -0
- package/dist/slider-scrollbar.d.mts +9 -0
- package/dist/slider-scrollbar.mjs +1 -0
- package/dist/slider.css +1 -1
- package/dist/slider.d.mts +7 -81
- package/dist/slider.mjs +1 -1
- package/dist/styles.css +4 -0
- package/dist/text-BBcRGVzn.d.mts +10 -0
- package/dist/thumbnails.css +1 -1
- package/dist/thumbnails.d.mts +8 -11
- package/dist/thumbnails.mjs +1 -1
- package/dist/transitions-DU3ftmIq.d.mts +6 -0
- package/dist/types-BiXSaEk7.d.mts +451 -0
- package/dist/types-Br27DWP7.d.mts +61 -0
- package/dist/{types-ROPjU8Nl.d.mts → types-DNd5jSkS.d.mts} +3 -2
- package/dist/{types-CHUayqcj.d.mts → types-DXFoG8LC.d.mts} +5 -3
- package/dist/types-Do4Pq-Td.d.mts +57 -0
- package/dist/video.css +1 -1
- package/dist/video.d.mts +2 -1
- package/dist/video.mjs +1 -1
- package/dist/zoomPan.d.mts +2 -4
- package/dist/zoomPan.mjs +1 -1
- package/package.json +146 -9
- package/dist/chunk-5HIHJGIV.mjs +0 -45
- package/dist/chunk-6TPHLAUP.mjs +0 -1
- package/dist/chunk-BIDZ4WZB.mjs +0 -2
- package/dist/chunk-DBIFLX6Y.mjs +0 -6
- package/dist/chunk-ECQ74X24.mjs +0 -1
- package/dist/chunk-FJYYM5TH.mjs +0 -1
- package/dist/chunk-GSEIEFRW.mjs +0 -1
- package/dist/chunk-GT6IL37J.mjs +0 -1
- package/dist/chunk-J4E4PKE5.mjs +0 -1
- package/dist/chunk-JD3VAF3N.mjs +0 -4
- package/dist/chunk-JMFDRKTX.mjs +0 -2
- package/dist/chunk-K6PQU6HF.mjs +0 -1
- package/dist/chunk-KSOQWCCL.mjs +0 -6
- package/dist/chunk-NEJ27O2B.mjs +0 -2
- package/dist/chunk-Q2PY6ZMU.mjs +0 -2
- package/dist/chunk-TKPLWDPW.mjs +0 -7
- package/dist/chunk-UV2SUN5V.mjs +0 -1
- package/dist/chunk-VXSRNAH4.mjs +0 -1
- package/dist/chunk-WLWVKQPL.mjs +0 -4
- package/dist/chunk-WZWMG4ZT.mjs +0 -1
- package/dist/chunk-XOS5AXSR.mjs +0 -4
- package/dist/chunk-ZX5E327W.mjs +0 -1
- package/dist/controls-SpWg1Kgt.d.mts +0 -44
- package/dist/layout-CR6f2aPH.d.mts +0 -95
- package/dist/responsive-D_xhZmVI.d.mts +0 -186
- package/dist/sliderSub-Bo6Y8as_.d.mts +0 -45
- package/dist/text-Cl2tR8oO.d.mts +0 -4
- package/dist/types-DY058l5M.d.mts +0 -301
- package/dist/types-VULXzSa2.d.mts +0 -68
- package/dist/types-XEr8LRal.d.mts +0 -65
- package/dist/types-_1D0QtfD.d.mts +0 -174
package/README.md
CHANGED
|
@@ -1,24 +1,56 @@
|
|
|
1
1
|
# React Motion Gallery
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Composable React media gallery primitives for production interfaces: sliders, grids, masonry, structured entries, fullscreen, thumbnails, video, zoom/pan, and loading states that are designed around the layout they protect.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The package stays close to React composition. `Slider`, `Grid`, and `Masonry` render children directly; `Entries` renders structured data; `GalleryCore` coordinates fullscreen state; `Video` handles Plyr-backed media; `ZoomPanImage` gives you a standalone zoom surface; and `Skeleton` can be used inside or outside gallery layouts. For loading-state precision, the repo also includes a development-time browser measurement workflow that turns real rendered text into stable skeleton text authoring data, including reflow-sensitive layouts such as masonry.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Runtime Gzip Sizes
|
|
8
|
+
|
|
9
|
+
This table reports local gzip measurements for selected runtime surfaces. Type-only imports are erased and add no JS; the responsive row measures `BREAKPOINT_MAP`, and feature subpath rows measure only that feature entry point. The script rebundles one export at a time from its published ESM entry point, excludes peer and runtime externals, and gzips the resulting JS bundle. If a surface creates async chunks, the row reports initial plus async JS. Run `npm run build && npm run size:readme` in `packages/react-motion-gallery` to refresh it.
|
|
8
10
|
|
|
9
11
|
<!-- bundle-size:start -->
|
|
10
|
-
|
|
|
12
|
+
| Surface | JS gzip |
|
|
11
13
|
| --- | --- |
|
|
12
|
-
| `Entries` |
|
|
13
|
-
| `FullscreenThumbnailSlider` |
|
|
14
|
-
| `GalleryCore` | 2.
|
|
15
|
-
| `Grid` |
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
14
|
+
| `Entries` | 13.1kB |
|
|
15
|
+
| `FullscreenThumbnailSlider` | 20.3kB |
|
|
16
|
+
| `GalleryCore` | 2.6kB |
|
|
17
|
+
| `Grid` | 6.3kB |
|
|
18
|
+
| `grid/ready` | 323.0B |
|
|
19
|
+
| `grid/lazy-load` | 3.3kB |
|
|
20
|
+
| `Masonry` | 6.5kB |
|
|
21
|
+
| `masonry/ready` | 323.0B |
|
|
22
|
+
| `masonry/lazy-load` | 3.3kB |
|
|
23
|
+
| `Skeleton base` | 8.1kB |
|
|
24
|
+
| `skeleton/slider` | 16.9kB |
|
|
25
|
+
| `skeleton/grid` | 10.4kB |
|
|
26
|
+
| `skeleton/masonry` | 17.8kB |
|
|
27
|
+
| `Slider core` | 18.7kB |
|
|
28
|
+
| `slider/ready` | 894.0B |
|
|
29
|
+
| `slider/arrows` | 1.2kB |
|
|
30
|
+
| `slider/dots` | 932.0B |
|
|
31
|
+
| `slider/progress` | 892.0B |
|
|
32
|
+
| `slider/scrollbar` | 1.2kB |
|
|
33
|
+
| `slider/auto-height` | 1.3kB |
|
|
34
|
+
| `slider/lazy-load` | 3.9kB |
|
|
35
|
+
| `slider/parallax` | 1.4kB |
|
|
36
|
+
| `slider/scale` | 1.2kB |
|
|
37
|
+
| `slider/fade` | 1.2kB |
|
|
38
|
+
| `slider/crossfade` | 2.8kB |
|
|
39
|
+
| `slider/fullscreen` | 959.0B |
|
|
40
|
+
| `ThumbnailSlider` | 18.9kB |
|
|
41
|
+
| `useFullscreenController` | 4.9kB |
|
|
42
|
+
| `fullscreen/slider` | 35.8kB |
|
|
43
|
+
| `fullscreen/controls` | 173.0B |
|
|
44
|
+
| `fullscreen/captions` | 13.1kB |
|
|
45
|
+
| `fullscreen/zoom-pan` | 9.9kB |
|
|
46
|
+
| `fullscreen/video` | 16.3kB |
|
|
47
|
+
| `fullscreen/lazy-load` | 13.1kB |
|
|
48
|
+
| `fullscreen/crossfade` | 181.0B |
|
|
49
|
+
| `fullscreen/thumbnails` | 160.0B |
|
|
50
|
+
| `Video` | 12.7kB |
|
|
51
|
+
| `ZoomPanImage` | 8.7kB |
|
|
52
|
+
| `media / toMediaItems` | 260.0B |
|
|
53
|
+
| `responsive / BREAKPOINT_MAP` | 85.0B |
|
|
22
54
|
<!-- bundle-size:end -->
|
|
23
55
|
|
|
24
56
|
## Overview
|
|
@@ -43,6 +75,7 @@ Mental model:
|
|
|
43
75
|
- `GalleryCore` and `useFullscreenController` power fullscreen behavior.
|
|
44
76
|
- `Video` is the gallery-ready video primitive.
|
|
45
77
|
- `ZoomPanImage` attaches click-to-zoom, drag pan, ctrl-wheel pinch, and touch pinch to one clipped image surface.
|
|
78
|
+
- `Skeleton` renders standalone placeholders or wraps real content with shared loading-layer timing.
|
|
46
79
|
|
|
47
80
|
`MediaItem` accepts three shapes:
|
|
48
81
|
|
|
@@ -54,7 +87,8 @@ Mental model:
|
|
|
54
87
|
|
|
55
88
|
```typescript
|
|
56
89
|
import "react-motion-gallery/styles.css";
|
|
57
|
-
import {
|
|
90
|
+
import { toMediaItems, type MediaItem } from "react-motion-gallery/media";
|
|
91
|
+
import { Slider } from "react-motion-gallery/slider";
|
|
58
92
|
|
|
59
93
|
const items: MediaItem[] = toMediaItems([
|
|
60
94
|
"https://picsum.photos/id/1015/1600/900",
|
|
@@ -84,7 +118,95 @@ export function QuickStart() {
|
|
|
84
118
|
|
|
85
119
|
Responsive numeric props in this package accept either a plain number or a breakpoint map like `{ 0: 1, md: 2, 1200: 3 }`. Named breakpoints resolve from the internal map: `xs: 0`, `sm: 600`, `md: 900`, `lg: 1200`, `xl: 1536`.
|
|
86
120
|
|
|
87
|
-
The package root
|
|
121
|
+
The package root exports the primary public components, helper functions, and companion prop types. Use it when one module needs several gallery surfaces. Prefer subpaths for routes or components that only need one surface, such as `react-motion-gallery/media` or `react-motion-gallery/slider`.
|
|
122
|
+
|
|
123
|
+
Subpaths give bundlers a smaller graph than the root. Less JS to transfer, parse, evaluate, and hydrate can improve first loads, cache misses, slower devices, and perceived speed.
|
|
124
|
+
|
|
125
|
+
| Entry point | Main surface |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| `react-motion-gallery/media` | `toMediaItems`, `MediaItem`, `MediaInput` |
|
|
128
|
+
| `react-motion-gallery/responsive` | `BREAKPOINT_MAP` and responsive value types |
|
|
129
|
+
| `react-motion-gallery/core` | `GalleryCore`, `GalleryCoreProvider`, `useGalleryCore` |
|
|
130
|
+
| `react-motion-gallery/slider` | `Slider`, `createSliderIndexChannel`, slider types |
|
|
131
|
+
| `react-motion-gallery/slider/ready` | `useSliderReady` |
|
|
132
|
+
| `react-motion-gallery/slider/arrows` | `sliderArrows` |
|
|
133
|
+
| `react-motion-gallery/slider/dots` | `sliderDots` |
|
|
134
|
+
| `react-motion-gallery/slider/progress` | `sliderProgress` |
|
|
135
|
+
| `react-motion-gallery/slider/scrollbar` | `sliderScrollbar` |
|
|
136
|
+
| `react-motion-gallery/slider/ripple` | `sliderRipple` |
|
|
137
|
+
| `react-motion-gallery/slider/auto-play` | `sliderAutoPlay` |
|
|
138
|
+
| `react-motion-gallery/slider/auto-scroll` | `sliderAutoScroll` |
|
|
139
|
+
| `react-motion-gallery/slider/auto-height` | `sliderAutoHeight` |
|
|
140
|
+
| `react-motion-gallery/slider/lazy-load` | `sliderLazyLoad` |
|
|
141
|
+
| `react-motion-gallery/slider/parallax` | `sliderParallax` |
|
|
142
|
+
| `react-motion-gallery/slider/scale` | `sliderScale` |
|
|
143
|
+
| `react-motion-gallery/slider/fade` | `sliderFade` |
|
|
144
|
+
| `react-motion-gallery/slider/crossfade` | `sliderCrossfade` |
|
|
145
|
+
| `react-motion-gallery/slider/fullscreen` | `sliderFullscreen` |
|
|
146
|
+
| `react-motion-gallery/slider/loading` | `sliderLoading` |
|
|
147
|
+
| `react-motion-gallery/grid` | `Grid`, `Grid.Item`, grid types |
|
|
148
|
+
| `react-motion-gallery/grid/ready` | `useGridReady` |
|
|
149
|
+
| `react-motion-gallery/grid/lazy-load` | `gridLazyLoad` |
|
|
150
|
+
| `react-motion-gallery/masonry` | `Masonry`, `Masonry.Item`, masonry types |
|
|
151
|
+
| `react-motion-gallery/masonry/ready` | `useMasonryReady` |
|
|
152
|
+
| `react-motion-gallery/masonry/lazy-load` | `masonryLazyLoad` |
|
|
153
|
+
| `react-motion-gallery/entries` | `Entries`, `flattenEntries`, entry media container helpers |
|
|
154
|
+
| `react-motion-gallery/skeleton/base` | Standalone `Skeleton` and generic skeleton authoring types |
|
|
155
|
+
| `react-motion-gallery/skeleton/slider` | `SliderSkeleton` and slider skeleton authoring types |
|
|
156
|
+
| `react-motion-gallery/skeleton/grid` | `GridSkeleton` and grid skeleton authoring types |
|
|
157
|
+
| `react-motion-gallery/skeleton/masonry` | `MasonrySkeleton` and masonry skeleton authoring types |
|
|
158
|
+
| `react-motion-gallery/fullscreen` | `useFullscreenController` and fullscreen types |
|
|
159
|
+
| `react-motion-gallery/fullscreen/slider` | `fullscreenSlider` |
|
|
160
|
+
| `react-motion-gallery/fullscreen/controls` | `fullscreenControls` |
|
|
161
|
+
| `react-motion-gallery/fullscreen/captions` | `fullscreenCaptions` |
|
|
162
|
+
| `react-motion-gallery/fullscreen/zoom-pan` | `fullscreenZoomPan` |
|
|
163
|
+
| `react-motion-gallery/fullscreen/video` | `fullscreenVideo` |
|
|
164
|
+
| `react-motion-gallery/fullscreen/lazy-load` | `fullscreenLazyLoad` |
|
|
165
|
+
| `react-motion-gallery/fullscreen/crossfade` | `fullscreenCrossfade` |
|
|
166
|
+
| `react-motion-gallery/fullscreen/thumbnails` | `fullscreenThumbnails` |
|
|
167
|
+
| `react-motion-gallery/thumbnails` | `ThumbnailSlider`, thumbnail sync helpers |
|
|
168
|
+
| `react-motion-gallery/fullscreenThumbnails` | `FullscreenThumbnailSlider` |
|
|
169
|
+
| `react-motion-gallery/video` | `Video` and optional Plyr-backed video types |
|
|
170
|
+
| `react-motion-gallery/zoomPan` | `ZoomPanImage` and zoom/pan types |
|
|
171
|
+
|
|
172
|
+
## Acknowledgements
|
|
173
|
+
|
|
174
|
+
React Motion Gallery's slider engine includes portions of code derived from [Embla Carousel](https://github.com/davidjerleke/embla-carousel), which is MIT licensed. Those portions have been substantially adapted for React Motion Gallery's React architecture, public API, transition system, fullscreen integration, loading layers, and media workflows.
|
|
175
|
+
|
|
176
|
+
See [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md) for the preserved Embla Carousel copyright and MIT license notice.
|
|
177
|
+
|
|
178
|
+
## Core
|
|
179
|
+
|
|
180
|
+
`GalleryCore` is the shared state boundary for fullscreen-aware galleries. Wrap a layout in it when you need shared breakpoints, a normalized fullscreen media list, fullscreen-open state, or programmatic fullscreen opening. `useGalleryCore()` is the public hook for reading that core state from descendants.
|
|
181
|
+
|
|
182
|
+
### `GalleryCore` props
|
|
183
|
+
|
|
184
|
+
| Option | Type | Default | Notes |
|
|
185
|
+
| --- | --- | --- | --- |
|
|
186
|
+
| `children` | `React.ReactNode` | `—` | The gallery tree using the shared core. |
|
|
187
|
+
| `layout` | `"slider" \| "grid" \| "masonry" \| "entries"` | `—` | Declares the owning base layout. Omit it for standalone fullscreen/core usage. |
|
|
188
|
+
| `breakpoints` | `Record<string, number>` | `xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536` | Breakpoint map shared with descendants. |
|
|
189
|
+
| `fullscreenItems` | `MediaItem[] \| string[]` | `[]` | Normalized fullscreen media list. |
|
|
190
|
+
| `nodes` | `ReactNode \| ReactNode[]` | `—` | Advanced initial node list used by the slider-backed imperative state. |
|
|
191
|
+
|
|
192
|
+
### `useGalleryCore` API
|
|
193
|
+
|
|
194
|
+
`GalleryApi` is the public alias for `GalleryCoreApi`. It covers core fullscreen state and programmatic fullscreen opening. Slider item mutation lives on `SliderHandle` and `SliderApi`.
|
|
195
|
+
|
|
196
|
+
| Field / Method | Type | Notes |
|
|
197
|
+
| --- | --- | --- |
|
|
198
|
+
| `layout` | `"slider" \| "grid" \| "masonry" \| "entries" \| null` | Current owning layout, or `null` for standalone fullscreen/core usage. |
|
|
199
|
+
| `effectiveBreakpoints` | `Record<string, number>` | Breakpoint map after merging custom `GalleryCore.breakpoints` with defaults. |
|
|
200
|
+
| `normalizedItems` | `MediaItem[]` | Fullscreen item list normalized from `fullscreenItems`. |
|
|
201
|
+
| `fsEnabled` | `boolean` | `true` when a mounted fullscreen controller has enabled fullscreen behavior. |
|
|
202
|
+
| `setFsEnabled` | `(enabled: boolean) => void` | Enables or disables fullscreen behavior. Usually handled by `useFullscreenController`. |
|
|
203
|
+
| `isFullscreenOpen` | `boolean` | `true` while fullscreen is open. |
|
|
204
|
+
| `isFullscreenOpenRef` | `React.RefObject<boolean>` | Ref mirror for handlers that need the current fullscreen-open state. |
|
|
205
|
+
| `setFullscreenOpen` | `(open: boolean) => void` | Updates fullscreen-open state. Usually handled by the fullscreen runtime. |
|
|
206
|
+
| `openFullscreenAt` | `({ index, method?, event? }) => void` | Opens fullscreen at a normalized fullscreen item index. Pass the source event for scale-origin detection. |
|
|
207
|
+
| `notifyBaseVisibleIndex` | `(index: number) => void` | Emits the visible base media index for fullscreen lazy-load/prewarm coordination. |
|
|
208
|
+
| `notifyFsVisibleIndex` | `(index: number) => void` | Emits the active fullscreen index back to base media. |
|
|
209
|
+
| `registerExpandableImage` | `(index: number, node: HTMLElement \| null) => void` | Registers an origin surface for layoutless scale transitions. |
|
|
88
210
|
|
|
89
211
|
## ZoomPanImage
|
|
90
212
|
|
|
@@ -108,10 +230,68 @@ export function ZoomPanCard() {
|
|
|
108
230
|
|
|
109
231
|
`ZoomPanImage` is the lightweight standalone zoom surface. The component root is the clipping container, so border radius, aspect ratio, and overflow all live on the same element.
|
|
110
232
|
|
|
233
|
+
## Skeleton
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { Skeleton, type SkeletonNode } from "react-motion-gallery/skeleton/base";
|
|
237
|
+
|
|
238
|
+
const shellSkeleton: SkeletonNode = {
|
|
239
|
+
kind: "rect",
|
|
240
|
+
style: { width: "100%", height: 320 },
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export function LoadingShell({ ready, children }: { ready: boolean; children: React.ReactNode }) {
|
|
244
|
+
return (
|
|
245
|
+
<Skeleton
|
|
246
|
+
layout={shellSkeleton}
|
|
247
|
+
ready={ready}
|
|
248
|
+
timing={{ exitMs: 520, minVisibleMs: 220 }}
|
|
249
|
+
force={false}
|
|
250
|
+
ariaLabel={ready ? undefined : "Loading content"}
|
|
251
|
+
>
|
|
252
|
+
{children}
|
|
253
|
+
</Skeleton>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`Skeleton` can render a standalone placeholder by itself, or it can wrap real content and own the loading transition. Wrapper mode is enabled when `children` are provided.
|
|
259
|
+
|
|
260
|
+
| Option | Type | Default | Notes |
|
|
261
|
+
| --- | --- | --- | --- |
|
|
262
|
+
| `layout` | `SkeletonNode` | `—` | Structured placeholder layout tree. |
|
|
263
|
+
| `children` | `React.ReactNode` | `—` | Real content. When present, `Skeleton` renders content and loading layers. |
|
|
264
|
+
| `ready` | `boolean` | `false` | Reveals content and exits the skeleton once true. |
|
|
265
|
+
| `enabled` | `boolean` | `true` | Set false to render content immediately with no skeleton layer. |
|
|
266
|
+
| `force` | `boolean \| { enabled?: boolean; showContent?: boolean; skeletonOpacity?: number }` | `false` | Keeps the skeleton visible. Set `showContent: true` to preview ready content under the skeleton, and tune the overlay with `skeletonOpacity`. |
|
|
267
|
+
| `timing.exitMs` | `number` | `600` | Keeps the skeleton layer mounted for this long after exit starts and controls the opacity transition. |
|
|
268
|
+
| `timing.minVisibleMs` | `number` | `220` | Minimum time the skeleton stays visible before exit can begin. |
|
|
269
|
+
| `shellClassName` / `shellStyle` | `string` / `CSSProperties` | `—` | Wrapper-layer class and style for content+skeleton mode. |
|
|
270
|
+
| `contentClassName` / `contentStyle` | `string` / `CSSProperties` | `—` | Content-layer class and style for wrapper mode. |
|
|
271
|
+
|
|
272
|
+
The wrapper timing model matches the gallery loading layers: content begins fading in as soon as the skeleton exit starts; it does not wait for the skeleton to unmount.
|
|
273
|
+
|
|
274
|
+
### Browser-measured skeleton text authoring
|
|
275
|
+
|
|
276
|
+
Responsive text is one of the easiest places for a polished loading state to drift away from the real UI. React Motion Gallery's skeleton text workflow measures real DOM text in a live page with headless Chrome, then emits `lines`, `barWidth`, `lastBarWidth`, and optional `barHeight`/`lineHeight` values for the skeleton `text` nodes used by `Slider`, `Grid`, `Masonry`, `Entries`, and standalone `Skeleton` layouts.
|
|
277
|
+
|
|
278
|
+
This is development-time authoring support, not production client code. It is especially useful for multiline cards, responsive grids, equal-height sliders, and reflow-sensitive masonry surfaces where a generic text placeholder can otherwise change row height, item height, or column packing when real content appears.
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm run --silent generate:skeleton-text-module -- \
|
|
282
|
+
--input ./path/to/example.skeleton-text.browser.manifest.json \
|
|
283
|
+
--analysis-output ./path/to/example.skeleton-text.measurements.json
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Use `responsiveBy: "container"` when text wrapping follows the card or cell width more closely than the viewport. For equal-height card sliders, the browser analyzer can also measure all canonical slider items and emit `rowHeightCompensation` so unseen cards cannot surprise the skeleton row height. See [`docs/skeleton-text-authoring.md`](./docs/skeleton-text-authoring.md) for manifest fields, command options, and the Codex-friendly workflow.
|
|
287
|
+
|
|
111
288
|
## Slider
|
|
112
289
|
|
|
290
|
+
The default `Slider` is the small synchronous core: children, drag, wheel navigation, snapping, grouping, looping, index channels, intro, and the imperative ref API. Heavier behavior is opt-in through first-party plugins, so importing one feature, such as arrows or parallax, does not pull in the rest of the slider feature set. Structured slider skeletons and restore behavior are owned by `SliderSkeleton`, composed with `useSliderReady()`.
|
|
291
|
+
|
|
113
292
|
```typescript
|
|
114
|
-
import { Slider } from "react-motion-gallery";
|
|
293
|
+
import { Slider } from "react-motion-gallery/slider";
|
|
294
|
+
import { sliderArrows } from "react-motion-gallery/slider/arrows";
|
|
115
295
|
|
|
116
296
|
const slides = [
|
|
117
297
|
"https://picsum.photos/id/1015/1600/900",
|
|
@@ -121,7 +301,7 @@ const slides = [
|
|
|
121
301
|
|
|
122
302
|
export function BasicSlider() {
|
|
123
303
|
return (
|
|
124
|
-
<Slider>
|
|
304
|
+
<Slider plugins={[sliderArrows()]}>
|
|
125
305
|
{slides.map((src, index) => (
|
|
126
306
|
<img key={src} src={src} alt={`Slide ${index + 1}`} style={{ width: "100%" }} />
|
|
127
307
|
))}
|
|
@@ -135,97 +315,89 @@ export function BasicSlider() {
|
|
|
135
315
|
| Option | Type | Default | Notes |
|
|
136
316
|
| --- | --- | --- | --- |
|
|
137
317
|
| `children` | `React.ReactNode` | `—` | Slide content rendered in order. |
|
|
318
|
+
| `initialIndex` | `number` | `0` | Selects the slide index used for the first layout and intro fade-in. |
|
|
138
319
|
| `breakpoints` | `Record<string, number>` | `xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536` | Merged with the internal breakpoint map for responsive values. |
|
|
139
|
-
| `expandableImageRefs` | `React.RefObject<(HTMLImageElement | null)[]>` | internal ref | Supplies origin images for fullscreen scale transitions. |
|
|
140
320
|
| `indexChannel` | `SliderIndexChannel` | internal channel | Share index state with thumbnails or sibling sliders. |
|
|
321
|
+
| `plugins` | `SliderPlugin[]` | `[]` | Explicit first-party slider features such as arrows, dots, auto-height, effects, fullscreen, or lazy-load. |
|
|
141
322
|
|
|
142
323
|
### Slider layout and scroll options
|
|
143
324
|
|
|
144
325
|
| Option | Type | Default | Notes |
|
|
145
326
|
| --- | --- | --- | --- |
|
|
146
|
-
| `layout.gap` | `number
|
|
327
|
+
| `layout.gap` | `number \| Record<string, number>` | `20` | Responsive gap between cells. |
|
|
147
328
|
| `layout.cellsPerSlide` | `number \| Record<string, number>` | `—` | Groups multiple cells into a slide page. |
|
|
148
329
|
| `direction.dir` | `"ltr" \| "rtl"` | `"ltr"` | Text direction and arrow direction. |
|
|
149
330
|
| `direction.axis` | `"x" \| "y"` | `"x"` | Horizontal or vertical slider axis. |
|
|
150
331
|
| `align` | `"start" \| "center"` | `"start"` | Slide alignment inside the viewport. |
|
|
151
332
|
| `scroll.groupCells` | `boolean` | `false` | Scrolls by grouped cells instead of every cell. |
|
|
152
|
-
| `scroll.skipSnaps` | `boolean` | `false` | Allows momentum to skip snap points. |
|
|
333
|
+
| `scroll.skipSnaps` | `boolean \| { enabled?: boolean; threshold?: number }` | `false` | Allows momentum to skip snap points. Object form enables skip snaps by default and `threshold` requires release force to reach a multiple of the adjacent snap distance before multi-snap momentum is used. |
|
|
334
|
+
| `scroll.strictSnaps` | `boolean` | `false` | Prevents one drag release from settling more than one snap away from where the drag started. Overrides `scroll.skipSnaps`. |
|
|
153
335
|
| `scroll.freeScroll` | `boolean` | `false` | Enables free dragging instead of strict snapping. |
|
|
154
336
|
| `scroll.loop` | `boolean` | `false` | Wraps around at the ends. |
|
|
155
337
|
|
|
156
|
-
### Slider element and
|
|
338
|
+
### Slider element and plugin options
|
|
339
|
+
|
|
340
|
+
`elements`, `motion`, and `transitions.intro` stay in the core slider. Controls, autoplay, lazy media, effects, auto-height, fullscreen, and loading overlays are explicit plugin imports.
|
|
157
341
|
|
|
158
342
|
| Option | Type | Default | Notes |
|
|
159
343
|
| --- | --- | --- | --- |
|
|
160
344
|
| `elements.viewport` | `ElementStyle` | `—` | Class and inline style for the viewport element. |
|
|
161
345
|
| `elements.container` | `ElementStyle` | `—` | Class and inline style for the moving slider container. |
|
|
162
|
-
| `lazyLoad.enabled` | `boolean` | `false` | Enables slide-level lazy image and video loading. |
|
|
163
|
-
| `lazyLoad.spinner` | `boolean \| ReactNode \| ((args) => ReactNode)` | `true` | `false` disables the built-in spinner. |
|
|
164
|
-
| `lazyLoad.spinnerClassName` | `string` | `""` | Applied to the spinner wrapper. |
|
|
165
|
-
| `lazyLoad.spinnerStyle` | `React.CSSProperties` | `{}` | Inline styles for the spinner wrapper. |
|
|
166
|
-
|
|
167
|
-
### Slider control options
|
|
168
|
-
|
|
169
|
-
| Option | Type | Default | Notes |
|
|
170
|
-
| --- | --- | --- | --- |
|
|
171
|
-
| `controls.arrows.enabled` | `boolean` | `true` | Toggles previous and next arrows. |
|
|
172
|
-
| `controls.arrows.arrow` | `ElementStyle` | `{}` | Shared arrow class and style. |
|
|
173
|
-
| `controls.arrows.prev` | `ElementStyle` | `{}` | Previous-arrow override. |
|
|
174
|
-
| `controls.arrows.next` | `ElementStyle` | `{}` | Next-arrow override. |
|
|
175
|
-
| `controls.arrows.render` | `(args) => ReactNode` | `—` | Custom renderer for both arrows. |
|
|
176
|
-
| `controls.arrows.renderPrev` | `(args) => ReactNode` | `—` | Custom previous arrow. |
|
|
177
|
-
| `controls.arrows.renderNext` | `(args) => ReactNode` | `—` | Custom next arrow. |
|
|
178
|
-
| `controls.dots.enabled` | `boolean` | `true` | Toggles pagination dots. |
|
|
179
|
-
| `controls.dots.root` | `ElementStyle` | `{}` | Dot container class and style. |
|
|
180
|
-
| `controls.dots.dot` | `ElementStyle` | `{}` | Individual dot class and style. |
|
|
181
|
-
| `controls.dots.render` | `(args) => ReactNode` | `—` | Full custom dots UI. |
|
|
182
|
-
| `controls.progress.enabled` | `boolean` | `false` | Toggles the progress bar. |
|
|
183
|
-
| `controls.progress.root` | `ElementStyle` | `{}` | Progress track class and style. |
|
|
184
|
-
| `controls.progress.bar` | `ElementStyle` | `{}` | Progress fill class and style. |
|
|
185
|
-
| `controls.progress.render` | `(args) => ReactNode` | `—` | Full custom progress UI. |
|
|
186
|
-
| `controls.ripple.enabled` | `boolean` | `true` | Toggles control ripple feedback. |
|
|
187
|
-
| `controls.ripple.className` | `string` | `""` | Custom ripple class. |
|
|
188
|
-
|
|
189
|
-
### Slider auto and transition options
|
|
190
|
-
|
|
191
|
-
| Option | Type | Default | Notes |
|
|
192
|
-
| --- | --- | --- | --- |
|
|
193
|
-
| `auto.play.enabled` | `boolean` | `false` | Timed slide changes. |
|
|
194
|
-
| `auto.play.speedMs` | `number` | `3000` | Delay between autoplay advances. |
|
|
195
|
-
| `auto.play.pauseMs` | `number` | `1000` | Delay after interaction before autoplay resumes. |
|
|
196
|
-
| `auto.play.pauseOnHover` | `boolean` | `true` | Pauses autoplay while hovering. |
|
|
197
|
-
| `auto.scroll.enabled` | `boolean` | `false` | Continuous timed scrolling. |
|
|
198
|
-
| `auto.scroll.speedMs` | `number` | `0.3` | Continuous auto-scroll speed. |
|
|
199
|
-
| `auto.scroll.pauseMs` | `number` | `1000` | Delay after interaction before auto-scroll resumes. |
|
|
200
|
-
| `auto.scroll.pauseOnHover` | `boolean` | `true` | Pauses while hovering. |
|
|
201
|
-
| `transitions.loading.enabled` | `boolean` | `—` | Enables the loading skeleton layer. |
|
|
202
|
-
| `transitions.loading.force` | `boolean` | `—` | Forces the loading layer to stay visible. |
|
|
203
|
-
| `transitions.loading.skeletonCount` | `number \| Record<string, number>` | `—` | Responsive skeleton slot count. |
|
|
204
|
-
| `transitions.loading.renderLoading` | `({ count }) => ReactNode` | `—` | Custom loading renderer. |
|
|
205
|
-
| `transitions.loading.skeleton` | `SliderSkeletonSpec` | `—` | Built-in skeleton spec, including per-slot overrides with `layout.slots` and centered peek support via `centering: "first"`. |
|
|
206
|
-
| `transitions.loading.timing.exitMs` | `number` | `600` | Keeps the loading layer mounted for this long after exit starts. |
|
|
207
|
-
| `transitions.loading.timing.minVisibleMs` | `number` | `220` | Minimum time the loading layer stays visible before exit can begin. |
|
|
208
346
|
| `transitions.intro.renderIntro` | `({ active, containerProps }, content) => ReactNode` | `—` | Custom intro wrapper. |
|
|
209
347
|
| `transitions.intro.staggerMs` | `number` | `—` | Delay between item fade-ins. |
|
|
210
348
|
| `transitions.intro.durationMs` | `number` | `—` | Intro fade duration. |
|
|
211
349
|
| `transitions.intro.easing` | `string` | `—` | Intro fade easing. |
|
|
212
350
|
|
|
351
|
+
### Slider plugins
|
|
352
|
+
|
|
353
|
+
Each plugin is imported from its own subpath and passed to `plugins`. There is no aggregate controls or effects helper; this keeps one-feature imports as small as possible.
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { Slider } from "react-motion-gallery/slider";
|
|
357
|
+
import { sliderArrows } from "react-motion-gallery/slider/arrows";
|
|
358
|
+
import { sliderParallax } from "react-motion-gallery/slider/parallax";
|
|
359
|
+
|
|
360
|
+
<Slider plugins={[sliderArrows(), sliderParallax({ bleedPct: "8%" })]}>
|
|
361
|
+
{slides}
|
|
362
|
+
</Slider>;
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
| Import | Factory | Notes |
|
|
366
|
+
| --- | --- | --- |
|
|
367
|
+
| `react-motion-gallery/slider/arrows` | `sliderArrows(options)` | Previous/next arrows. |
|
|
368
|
+
| `react-motion-gallery/slider/dots` | `sliderDots(options)` | Pagination dots. |
|
|
369
|
+
| `react-motion-gallery/slider/progress` | `sliderProgress(options)` | Progress bar or custom progress renderer. |
|
|
370
|
+
| `react-motion-gallery/slider/scrollbar` | `sliderScrollbar(options)` | Range-style position control. |
|
|
371
|
+
| `react-motion-gallery/slider/ripple` | `sliderRipple(options)` | Enables ripple feedback for controls that call `createRipple`. |
|
|
372
|
+
| `react-motion-gallery/slider/auto-play` | `sliderAutoPlay(options)` | Timed slide changes. |
|
|
373
|
+
| `react-motion-gallery/slider/auto-scroll` | `sliderAutoScroll(options)` | Timed continuous advancement. |
|
|
374
|
+
| `react-motion-gallery/slider/auto-height` | `sliderAutoHeight(options)` | Measures active slide height and gates slider readiness until measured. |
|
|
375
|
+
| `react-motion-gallery/slider/lazy-load` | `sliderLazyLoad(options)` | Adds lazy media attributes to slide images and videos. |
|
|
376
|
+
| `react-motion-gallery/slider/parallax` | `sliderParallax(options)` | Parallax slide wrapper. |
|
|
377
|
+
| `react-motion-gallery/slider/scale` | `sliderScale(options)` | Scales non-active slides. |
|
|
378
|
+
| `react-motion-gallery/slider/fade` | `sliderFade(options)` | Fades non-active slides. |
|
|
379
|
+
| `react-motion-gallery/slider/crossfade` | `sliderCrossfade(options)` | Enables crossfade-aware control navigation. |
|
|
380
|
+
| `react-motion-gallery/slider/fullscreen` | `sliderFullscreen()` | Bridges a `GalleryCore layout="slider"` slider to fullscreen. |
|
|
381
|
+
| `react-motion-gallery/slider/loading` | `sliderLoading(options)` | Basic custom loading overlay. Prefer `SliderSkeleton` for structured skeleton and restore. |
|
|
382
|
+
|
|
213
383
|
### Slider loading skeletons
|
|
214
384
|
|
|
215
|
-
`
|
|
385
|
+
Use `SliderSkeleton` to own slider loading. `useSliderReady()` exposes the slider ref plus a settled `ready` flag; `isSlidesBuilt()` remains a lower-level DOM-built signal and is not the right fade-out trigger.
|
|
216
386
|
|
|
217
387
|
`layout.slots` is the per-slide override system. Define the shared placeholder once with `layout.item` and `layout.itemWrapStyle`, then override any individual slot with `slots[index]`. Slot `itemWrapStyle` values merge on top of the base wrap style, while `slot.item` can replace the placeholder node entirely for that slot.
|
|
218
388
|
|
|
219
389
|
`itemWrapStyle` now supports wrapper-only `border` and `boxShadow` values. Wrapper `width`, `height`, and `aspectRatio` are treated as outer border-box dimensions, so the inner placeholder shrinks by the border thickness. Use simple uniform border shorthands such as `1px solid #cbd5e1` when you want the built-in sizing math to account for the border width.
|
|
220
390
|
|
|
221
|
-
`text` nodes render one skeleton bar per `lines` value. `lines` can be a single number or a numeric min-width map such as `{ 0: 3, 767: 2, 1200: 1 }`. Use `
|
|
391
|
+
`text` nodes render one skeleton bar per `lines` value. `barHeight` controls the bar height and can be a single number or a numeric min-width map. `lineHeight` remains the full line-box multiplier and now accepts the same numeric min-width maps. `lines` can be a single number or a numeric min-width map such as `{ 0: 3, 767: 2, 1200: 1 }`. Use `lastBarWidth` to override the shortened trailing bar width; it defaults to `68%` of the text block width and can also be responsive with numeric min-width keys.
|
|
222
392
|
|
|
223
|
-
`centering: "first"` is designed for center-aligned peek sliders. When the real slider uses `align="center"` and the skeleton uses `mode: "peek"` with `layout.kind: "slider"`, the
|
|
393
|
+
`centering: "first"` is designed for center-aligned peek sliders. When the real slider uses `align="center"` and the skeleton uses `mode: "peek"` with `layout.kind: "slider"`, the skeleton renderer inserts the leading spacer needed to center the first visible placeholder. You should not add that spacer manually.
|
|
224
394
|
|
|
225
|
-
When you provide `
|
|
395
|
+
When you provide `SliderSkeleton.timing`, `exitMs` controls both how long the loading layer remains mounted after exit starts and its opacity transition duration.
|
|
226
396
|
|
|
227
397
|
```typescript
|
|
228
|
-
import {
|
|
398
|
+
import { SliderSkeleton } from "react-motion-gallery/skeleton/slider";
|
|
399
|
+
import { Slider } from "react-motion-gallery/slider";
|
|
400
|
+
import { useSliderReady } from "react-motion-gallery/slider/ready";
|
|
229
401
|
|
|
230
402
|
const slides = [
|
|
231
403
|
{ src: "https://picsum.photos/id/1020/660/960", width: 220, height: 320 },
|
|
@@ -234,47 +406,47 @@ const slides = [
|
|
|
234
406
|
];
|
|
235
407
|
|
|
236
408
|
export function VariableWidthSkeletonSlider() {
|
|
409
|
+
const { ref: sliderRef, ready: sliderReady } = useSliderReady();
|
|
410
|
+
|
|
237
411
|
return (
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
height: "100%",
|
|
255
|
-
borderRadius: 12,
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
slots: slides.map((slide) => ({
|
|
259
|
-
itemWrapStyle: {
|
|
260
|
-
width: slide.width,
|
|
261
|
-
height: slide.height,
|
|
262
|
-
},
|
|
263
|
-
})),
|
|
412
|
+
<SliderSkeleton
|
|
413
|
+
ready={sliderReady}
|
|
414
|
+
layout={{
|
|
415
|
+
mode: "peek",
|
|
416
|
+
centering: "first",
|
|
417
|
+
visibleCount: 2,
|
|
418
|
+
layout: {
|
|
419
|
+
kind: "slider",
|
|
420
|
+
direction: "row",
|
|
421
|
+
style: { gap: 20 },
|
|
422
|
+
item: {
|
|
423
|
+
kind: "rect",
|
|
424
|
+
style: {
|
|
425
|
+
width: "100%",
|
|
426
|
+
height: "100%",
|
|
427
|
+
borderRadius: 12,
|
|
264
428
|
},
|
|
265
429
|
},
|
|
430
|
+
slots: slides.map((slide) => ({
|
|
431
|
+
itemWrapStyle: {
|
|
432
|
+
width: slide.width,
|
|
433
|
+
height: slide.height,
|
|
434
|
+
},
|
|
435
|
+
})),
|
|
266
436
|
},
|
|
267
437
|
}}
|
|
268
438
|
>
|
|
269
|
-
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
439
|
+
<Slider ref={sliderRef} align="center">
|
|
440
|
+
{slides.map((slide, index) => (
|
|
441
|
+
<img
|
|
442
|
+
key={slide.src}
|
|
443
|
+
src={slide.src}
|
|
444
|
+
alt={`Slide ${index + 1}`}
|
|
445
|
+
style={{ width: slide.width, height: slide.height, objectFit: "cover" }}
|
|
446
|
+
/>
|
|
447
|
+
))}
|
|
448
|
+
</Slider>
|
|
449
|
+
</SliderSkeleton>
|
|
278
450
|
);
|
|
279
451
|
}
|
|
280
452
|
```
|
|
@@ -285,6 +457,7 @@ export function VariableWidthSkeletonSlider() {
|
|
|
285
457
|
| --- | --- | --- |
|
|
286
458
|
| `mode` | `"fit" \| "peek"` | `"peek"` preserves partial next or previous slide visibility in the loading state. |
|
|
287
459
|
| `centering` | `"first"` | Adds the leading spacer needed for the first visible slot when using the built-in centered peek skeleton flow. |
|
|
460
|
+
| `visibleCount` | `number \| Record<string, number>` | Responsive count of visible skeleton slots. |
|
|
288
461
|
| `className` | `string \| undefined` | Applied to the skeleton overlay root. |
|
|
289
462
|
| `style` | `React.CSSProperties \| undefined` | Inline styles for the skeleton overlay root. |
|
|
290
463
|
| `layout` | `SliderSkeletonNode \| undefined` | Structured placeholder layout tree. Use `kind: "slider"` to model slide tracks. |
|
|
@@ -298,7 +471,7 @@ export function VariableWidthSkeletonSlider() {
|
|
|
298
471
|
| --- | --- | --- |
|
|
299
472
|
| `kind` | `"slider"` | Slider-specific skeleton layout root. |
|
|
300
473
|
| `style` | `SkeletonContainerStyle \| Record<string, SkeletonContainerStyle>` | Track-level container styles such as `gap`, `padding`, `align`, `justify`, `width`, and `maxWidth`. |
|
|
301
|
-
| `count` | `number \| undefined` | Optional explicit slot count for the layout. Falls back to `
|
|
474
|
+
| `count` | `number \| undefined` | Optional explicit slot count for the layout. Falls back to `visibleCount` on the surrounding slider skeleton spec. |
|
|
302
475
|
| `item` | `SkeletonNode` | Default placeholder node rendered in each slot. |
|
|
303
476
|
| `itemWrapStyle` | `SliderSkeletonWrapStyle \| undefined` | Shared wrapper size, margin, border, and box-shadow rules for every slot. Border sizing is border-box. |
|
|
304
477
|
| `slots` | `SliderSkeletonSlot[] \| undefined` | Per-slot overrides for variable widths, heights, aspect ratios, or custom placeholder nodes. |
|
|
@@ -312,23 +485,15 @@ export function VariableWidthSkeletonSlider() {
|
|
|
312
485
|
| `item` | `SkeletonNode \| undefined` | Replaces the base `layout.item` for one slot. |
|
|
313
486
|
| `itemWrapStyle` | `SliderSkeletonWrapStyle \| undefined` | Merges on top of the base `layout.itemWrapStyle` for one slot, including wrapper borders and shadows. |
|
|
314
487
|
|
|
315
|
-
`SkeletonNode` supports these building blocks: `rect`, `square`, `circle`, `text`, `media`, `row`, `col`, and `stack`. `text.lines` controls how many wrapped skeleton rows render for that text block, and `text.
|
|
488
|
+
`SkeletonNode` supports these building blocks: `rect`, `square`, `circle`, `text`, `media`, `row`, `col`, and `stack`. `text.barHeight` controls the bar height, `text.lines` controls how many wrapped skeleton rows render for that text block, and `text.lastBarWidth` controls the trailing bar width.
|
|
316
489
|
|
|
317
|
-
### Slider motion
|
|
490
|
+
### Slider motion options
|
|
318
491
|
|
|
319
492
|
| Option | Type | Default | Notes |
|
|
320
493
|
| --- | --- | --- | --- |
|
|
321
494
|
| `motion.selectDuration` | `number` | `25` | Duration for snapped selection motion. |
|
|
322
495
|
| `motion.freeScrollDuration` | `number` | `43` | Duration for free-scroll settling. |
|
|
323
496
|
| `motion.friction` | `number` | `0.68` | Drag and settling friction. |
|
|
324
|
-
| `effects.parallax.enabled` | `boolean` | `—` | Enables the parallax slide treatment. |
|
|
325
|
-
| `effects.parallax.bleedPct` | `string` | `—` | Extra image bleed around the viewport. |
|
|
326
|
-
| `effects.parallax.borderRadius` | `string` | `—` | Radius for the parallax frame. |
|
|
327
|
-
| `effects.parallax.sideWidth` | `string` | `—` | Side crop width used by the effect. |
|
|
328
|
-
| `effects.scale.enabled` | `boolean` | `—` | Scales neighboring slides. |
|
|
329
|
-
| `effects.scale.amount` | `number` | `—` | Scale multiplier for the scale effect. |
|
|
330
|
-
| `effects.fade.enabled` | `boolean` | `—` | Fades slides based on position. |
|
|
331
|
-
| `effects.fade.minOpacity` | `number` | `0.36` | Minimum opacity used for the fade effect, clamped from `0` to `1`. |
|
|
332
497
|
|
|
333
498
|
### Slider render callback args
|
|
334
499
|
|
|
@@ -387,12 +552,22 @@ export function VariableWidthSkeletonSlider() {
|
|
|
387
552
|
| `onSlidesBuilt` | `(cb: (nodes: HTMLElement[]) => void) => () => void` | Runs when slide nodes are ready. |
|
|
388
553
|
| `whenSlidesBuilt` | `() => Promise<HTMLElement[]>` | Promise form of `onSlidesBuilt`. |
|
|
389
554
|
| `isSlidesBuilt` | `() => boolean` | `true` once the slide list is ready. |
|
|
555
|
+
| `onReady` | `(cb: (nodes: HTMLElement[]) => void) => () => void` | Runs when the slider has built, measured, committed its index, and all plugin ready gates have cleared. |
|
|
556
|
+
| `whenReady` | `() => Promise<HTMLElement[]>` | Promise form of `onReady`. |
|
|
557
|
+
| `isReady` | `() => boolean` | `true` once the settled slider ready signal has fired. |
|
|
390
558
|
| `scrollNext` | `(mode?: IndexMode) => void` | Advances one step. |
|
|
391
559
|
| `scrollPrev` | `(mode?: IndexMode) => void` | Moves backward one step. |
|
|
392
560
|
| `canScrollNext` | `() => boolean` | Whether next navigation is available. |
|
|
393
561
|
| `canScrollPrev` | `() => boolean` | Whether previous navigation is available. |
|
|
394
562
|
| `scrollProgress` | `() => number` | Current progress from `0` to `1`. |
|
|
395
563
|
| `cellsInView` | `() => number[]` | Canonical cell indexes currently visible. |
|
|
564
|
+
| `append` | `(nodes: ReactNode \| ReactNode[]) => number` | Appends nodes and returns the new total count. |
|
|
565
|
+
| `prepend` | `(nodes: ReactNode \| ReactNode[]) => number` | Prepends nodes and returns the new total count. |
|
|
566
|
+
| `insert` | `(index: number, nodes: ReactNode \| ReactNode[]) => number` | Inserts nodes and returns the new total count. |
|
|
567
|
+
| `remove` | `(indexOrPredicate: number \| ((i: number) => boolean)) => number` | Removes items and returns the new total count. |
|
|
568
|
+
| `replace` | `(index: number, node: ReactNode) => void` | Replaces a node at an index. |
|
|
569
|
+
| `setItems` | `(nodes: ReactNode[]) => number` | Replaces all nodes and returns the new total count. |
|
|
570
|
+
| `onIndexChange` | `(cb: (i: number, meta: { mode: IndexMode }) => void) => () => void` | Subscribes to index changes. |
|
|
396
571
|
| `getInternals` | `() => { slides, slider, visibleImages, selectedIndex, sliderX, sliderVelocity, isWrapping }` | Low-level internals used by fullscreen and advanced sync code. |
|
|
397
572
|
|
|
398
573
|
### `createSliderIndexChannel`
|
|
@@ -473,7 +648,7 @@ export function SliderWithThumbnails() {
|
|
|
473
648
|
}
|
|
474
649
|
```
|
|
475
650
|
|
|
476
|
-
The component forwards a ref to its outer thumbnail shell.
|
|
651
|
+
The component forwards a ref to its outer thumbnail shell.
|
|
477
652
|
|
|
478
653
|
### ThumbnailSlider component props
|
|
479
654
|
|
|
@@ -532,7 +707,7 @@ The component forwards a ref to its outer thumbnail shell. The explicit `layout`
|
|
|
532
707
|
| Option | Type | Default | Notes |
|
|
533
708
|
| --- | --- | --- | --- |
|
|
534
709
|
| `transitions.loading.enabled` | `boolean` | `true` | Enables the thumbnail loading layer. |
|
|
535
|
-
| `transitions.loading.force` | `boolean` | `false` | Forces the loading layer to remain visible. |
|
|
710
|
+
| `transitions.loading.force` | `boolean \| { enabled?: boolean; showContent?: boolean; skeletonOpacity?: number }` | `false` | Forces the loading layer to remain visible. Set `showContent: true` to preview the real thumbnails under the skeleton, and tune the loading overlay with `skeletonOpacity`. |
|
|
536
711
|
| `transitions.loading.skeletonCount` | `number \| Record<string, number>` | `—` | Responsive count for the built-in loading placeholders. |
|
|
537
712
|
| `transitions.loading.mode` | `"fit" \| "peek"` | `"peek"` | `"peek"` keeps fixed-size thumbnail placeholders when width or height is explicitly set; `"fit"` divides the rail evenly across the visible count. |
|
|
538
713
|
| `transitions.loading.elements.container` | `ElementStyle` | `—` | Class and inline style for the built-in loading overlay container. |
|
|
@@ -588,44 +763,145 @@ export function BasicGrid() {
|
|
|
588
763
|
|
|
589
764
|
| Option | Type | Default | Notes |
|
|
590
765
|
| --- | --- | --- | --- |
|
|
591
|
-
| `children` | `React.ReactNode` | `—` | Grid items rendered in order. |
|
|
766
|
+
| `children` | `React.ReactNode` | `—` | Grid items rendered in order. Wrap individual cards in `Grid.Item` when they need custom spans or wrapper props. |
|
|
592
767
|
| `breakpoints` | `Record<string, number>` | `xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536` | Used to resolve responsive columns and gaps. |
|
|
593
768
|
| `gridItemBaseClass` | `string` | `"rmg__grid-item"` | Internal item base class override. |
|
|
594
769
|
| `renderMode` | `"wrap" \| "passthrough"` | `"wrap"` | `wrap` adds an item wrapper; `passthrough` keeps child structure closer to the source node. |
|
|
595
770
|
|
|
771
|
+
### Grid.Item props
|
|
772
|
+
|
|
773
|
+
`Grid.Item` is a metadata wrapper. It renders only its children, while Grid reads the wrapper props and applies them to the generated item shell.
|
|
774
|
+
|
|
775
|
+
| Option | Type | Default | Notes |
|
|
776
|
+
| --- | --- | --- | --- |
|
|
777
|
+
| `children` | `React.ReactNode` | `—` | The grid card content. |
|
|
778
|
+
| `span` | `number \| "full" \| Record<string, number \| "full">` | `1` | Per-item track span. `"full"` renders `grid-column: 1 / -1`; numeric values render `grid-column: span n / span n`. |
|
|
779
|
+
| `className` | `string` | `—` | Extra class name merged onto the grid item wrapper. |
|
|
780
|
+
| `style` | `React.CSSProperties` | `—` | Inline styles merged onto the grid item wrapper. |
|
|
781
|
+
|
|
596
782
|
### Grid options
|
|
597
783
|
|
|
598
784
|
| Option | Type | Default | Notes |
|
|
599
785
|
| --- | --- | --- | --- |
|
|
600
786
|
| `columns` | `number \| Record<string, number>` | `—` | Fixed responsive column count. When omitted, Grid auto-fits using `minColumnWidth`. |
|
|
787
|
+
| `templateColumns` | `string \| Record<string, string>` | `—` | Explicit `grid-template-columns` value. Takes precedence over `columns` and `minColumnWidth`. |
|
|
601
788
|
| `minColumnWidth` | `number \| string` | `160` | Minimum width used by auto-fit mode. |
|
|
602
789
|
| `gap` | `number \| Record<string, number>` | `8` | Responsive grid gap. |
|
|
603
790
|
| `rootClassName` | `string` | `—` | Class name for the grid root. |
|
|
604
791
|
| `itemClassName` | `string` | `—` | Class name added to each wrapped grid item. |
|
|
605
792
|
| `fullscreenTrigger` | `"item" \| "media"` | `"media"` | Opens fullscreen from the clicked media node or the entire item shell. |
|
|
606
|
-
| `
|
|
607
|
-
| `lazyLoad.spinner` | `boolean \| ReactNode \| ((args) => ReactNode)` | `—` | Spinner override for lazy items. |
|
|
608
|
-
| `lazyLoad.spinnerClassName` | `string` | `—` | Spinner wrapper class. |
|
|
609
|
-
| `lazyLoad.spinnerStyle` | `React.CSSProperties` | `—` | Spinner wrapper style. |
|
|
610
|
-
| `loading.enabled` | `boolean` | `—` | Enables the loading layer. |
|
|
611
|
-
| `loading.force` | `boolean` | `—` | Keeps the loading layer visible even when media is ready. |
|
|
612
|
-
| `loading.renderLoading` | `({ count }) => ReactNode` | `—` | Custom loading renderer. |
|
|
613
|
-
| `loading.skeleton` | `GridSkeletonSpec` | `—` | Built-in grid skeleton spec. |
|
|
614
|
-
| `loading.timing.exitMs` | `number` | `600` | Keeps the loading layer mounted for this long after exit starts. |
|
|
615
|
-
| `loading.timing.minVisibleMs` | `number` | `220` | Minimum time the loading layer stays visible before exit can begin. |
|
|
793
|
+
| `plugins` | `GridPlugin[]` | `[]` | Explicit first-party Grid features such as lazy-load. |
|
|
616
794
|
| `intro.renderIntro` | `({ active, containerProps }, content) => ReactNode` | `—` | Custom intro wrapper. |
|
|
617
|
-
| `intro.staggerMs` | `number` | `
|
|
618
|
-
| `intro.durationMs` | `number` | `
|
|
795
|
+
| `intro.staggerMs` | `number` | `60` | Reveal stagger for the fade-in. |
|
|
796
|
+
| `intro.durationMs` | `number` | `600` | Intro fade duration. |
|
|
619
797
|
| `intro.easing` | `string` | `"cubic-bezier(.2,.7,.2,1)"` | Intro fade easing. |
|
|
620
798
|
| `intro.staggerLimit` | `number` | `—` | Optional cap on how many items stagger. |
|
|
621
799
|
|
|
622
|
-
|
|
800
|
+
### Grid plugins
|
|
801
|
+
|
|
802
|
+
Import Grid plugins from their own subpaths and pass them to `plugins`.
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
import { Grid } from "react-motion-gallery/grid";
|
|
806
|
+
import { gridLazyLoad } from "react-motion-gallery/grid/lazy-load";
|
|
807
|
+
|
|
808
|
+
<Grid plugins={[gridLazyLoad({ spinner: true })]}>{items}</Grid>;
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
| Import | Factory | Notes |
|
|
812
|
+
| --- | --- | --- |
|
|
813
|
+
| `react-motion-gallery/grid/lazy-load` | `gridLazyLoad(options)` | Rewrites trackable image `src` values into `data-rmg-lazy-src`, reveals them on viewport intersection, then fades them in after decode and spinner exit. |
|
|
814
|
+
|
|
815
|
+
`gridLazyLoad()` enables lazy loading by default. Pass `{ enabled: false }` to make the plugin inert.
|
|
623
816
|
|
|
624
817
|
Grid fullscreen behavior is provided by `GalleryCore` and `useFullscreenController`; Grid itself does not expose a ref-based imperative API.
|
|
625
818
|
|
|
626
|
-
|
|
819
|
+
Wrap a card in `Grid.Item` when it should span tracks or needs wrapper styling:
|
|
820
|
+
|
|
821
|
+
```typescript
|
|
822
|
+
<Grid columns={{ 0: 1, 720: 6, 1100: 12 }} gap={{ 0: 12, 1100: 18 }}>
|
|
823
|
+
<Grid.Item span={{ 0: "full", 720: 3, 1100: 6 }} className="feature-card">
|
|
824
|
+
<FeatureCard />
|
|
825
|
+
</Grid.Item>
|
|
826
|
+
<Grid.Item span={{ 0: "full", 720: 3, 1100: 3 }}>
|
|
827
|
+
<ProductCard />
|
|
828
|
+
</Grid.Item>
|
|
829
|
+
<Grid.Item span="full">
|
|
830
|
+
<WideEditorialCard />
|
|
831
|
+
</Grid.Item>
|
|
832
|
+
</Grid>
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
Grid spans require explicit tracks: use `columns` or `templateColumns`. If Grid is in auto-fit mode through `minColumnWidth`, item spans are ignored because there is no stable track count to span. Responsive span maps use the same breakpoint keys as responsive numeric props, so named keys such as `md` and numeric keys such as `900` are both valid.
|
|
836
|
+
|
|
837
|
+
Use `templateColumns` when the tracks themselves need custom proportions:
|
|
627
838
|
|
|
628
|
-
|
|
839
|
+
```typescript
|
|
840
|
+
<Grid
|
|
841
|
+
templateColumns={{
|
|
842
|
+
0: "1fr",
|
|
843
|
+
900: "minmax(0, 1.4fr) minmax(0, 1fr)",
|
|
844
|
+
1200: "minmax(0, 2fr) repeat(2, minmax(0, 1fr))",
|
|
845
|
+
}}
|
|
846
|
+
gap={{ 0: 12, 1200: 18 }}
|
|
847
|
+
>
|
|
848
|
+
<Grid.Item span={{ 0: "full", 900: 2 }}>
|
|
849
|
+
<FeatureCard />
|
|
850
|
+
</Grid.Item>
|
|
851
|
+
</Grid>
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
Grid no longer owns loading UI. Use `useGridReady` and wrap Grid with `GridSkeleton`, the same composition pattern used by Slider and Masonry.
|
|
855
|
+
|
|
856
|
+
Grid skeletons live in `react-motion-gallery/skeleton/grid`. Their `text` nodes use the same wrapped-line treatment as slider skeletons, including responsive `barHeight` and `lines` maps plus the configurable trailing `lastBarWidth`.
|
|
857
|
+
|
|
858
|
+
Grid skeletons inherit real item spans by default. Slot overrides in the Skeleton layout can change individual placeholder nodes or wrapper styles without losing the span applied by `Grid.Item`.
|
|
859
|
+
|
|
860
|
+
When Grid is wrapped in `GridSkeleton`, `GridSkeleton.timing.exitMs` controls both how long the loading layer stays mounted after exit starts and its opacity transition, and the real grid intro begins as soon as exit starts.
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
import { Grid, useGridReady } from "react-motion-gallery";
|
|
864
|
+
import { GridSkeleton, type GridSkeletonSpec } from "react-motion-gallery/skeleton/grid";
|
|
865
|
+
|
|
866
|
+
const gridSkeleton: GridSkeletonSpec = {
|
|
867
|
+
radius: 14,
|
|
868
|
+
layout: {
|
|
869
|
+
kind: "grid",
|
|
870
|
+
count: 6,
|
|
871
|
+
item: {
|
|
872
|
+
kind: "rect",
|
|
873
|
+
style: { aspectRatio: "4 / 5" },
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
function GridWithSkeleton({ images }: { images: { src: string; alt: string }[] }) {
|
|
879
|
+
const { ref: gridRef, ready: gridReady } = useGridReady();
|
|
880
|
+
|
|
881
|
+
return (
|
|
882
|
+
<GridSkeleton
|
|
883
|
+
layout={gridSkeleton}
|
|
884
|
+
ready={gridReady}
|
|
885
|
+
timing={{ minVisibleMs: 220, exitMs: 600 }}
|
|
886
|
+
grid={{
|
|
887
|
+
count: images.length,
|
|
888
|
+
columns: { 0: 1, 640: 2, 960: 3 },
|
|
889
|
+
gap: { 0: 12, 960: 20 },
|
|
890
|
+
}}
|
|
891
|
+
>
|
|
892
|
+
<Grid
|
|
893
|
+
ref={gridRef}
|
|
894
|
+
columns={{ 0: 1, 640: 2, 960: 3 }}
|
|
895
|
+
gap={{ 0: 12, 960: 20 }}
|
|
896
|
+
>
|
|
897
|
+
{images.map((image) => (
|
|
898
|
+
<img key={image.src} src={image.src} alt={image.alt} />
|
|
899
|
+
))}
|
|
900
|
+
</Grid>
|
|
901
|
+
</GridSkeleton>
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
```
|
|
629
905
|
|
|
630
906
|
## Masonry
|
|
631
907
|
|
|
@@ -654,144 +930,166 @@ export function BasicMasonry() {
|
|
|
654
930
|
|
|
655
931
|
| Option | Type | Default | Notes |
|
|
656
932
|
| --- | --- | --- | --- |
|
|
657
|
-
| `children` | `React.ReactNode` | `—` | Masonry items rendered in order. |
|
|
933
|
+
| `children` | `React.ReactNode` | `—` | Masonry items rendered in order. Wrap individual cards in `Masonry.Item` when they need custom spans or wrapper props. |
|
|
658
934
|
| `breakpoints` | `Record<string, number>` | `xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536` | Used to resolve responsive columns and gaps. |
|
|
659
935
|
|
|
936
|
+
### Masonry.Item props
|
|
937
|
+
|
|
938
|
+
| Option | Type | Default | Notes |
|
|
939
|
+
| --- | --- | --- | --- |
|
|
940
|
+
| `children` | `React.ReactNode` | `—` | The masonry card content. |
|
|
941
|
+
| `span` | `number \| "full" \| Record<string, number \| "full">` | `1` | Per-item track span. `"full"` resolves to the active column count and numeric values clamp to the current track count. |
|
|
942
|
+
| `className` | `string` | `—` | Extra class name merged onto the masonry item wrapper. |
|
|
943
|
+
| `style` | `React.CSSProperties` | `—` | Inline styles merged onto the masonry item wrapper. |
|
|
944
|
+
|
|
660
945
|
### Masonry options
|
|
661
946
|
|
|
662
947
|
| Option | Type | Default | Notes |
|
|
663
948
|
| --- | --- | --- | --- |
|
|
664
949
|
| `columns` | `number \| Record<string, number>` | `—` | Responsive column count. |
|
|
665
950
|
| `gap` | `number \| Record<string, number>` | `—` | Responsive gap between columns and items. |
|
|
666
|
-
| `placement` | `"balanced" \| "roundRobin"` | `"balanced"` | `balanced`
|
|
667
|
-
| `
|
|
951
|
+
| `placement` | `"balanced" \| "roundRobin" \| "horizontalOrder"` | `"balanced"` | `balanced` packs into the shortest fitting column group, `roundRobin` cycles start columns deterministically, and `horizontalOrder` preserves a stronger left-to-right scan when spans are involved. |
|
|
952
|
+
| `fullscreenTrigger` | `"item" \| "media"` | `"media"` | Opens fullscreen from the clicked media node or the entire masonry item shell. |
|
|
668
953
|
| `itemWrapClassName` | `string` | `—` | Class name added to the masonry item wrapper. |
|
|
669
954
|
| `itemWrapStyle` | `React.CSSProperties` | `—` | Inline styles applied to the masonry item wrapper. |
|
|
670
955
|
| `as` | `React.ElementType` | `"div"` | Root HTML element or custom component. |
|
|
671
956
|
| `rootRef` | `React.Ref<HTMLDivElement>` | `—` | Ref to the masonry root. |
|
|
672
957
|
| `classNames.root` | `string` | `—` | Root class name. |
|
|
673
|
-
| `classNames.column` | `string` | `—` |
|
|
958
|
+
| `classNames.column` | `string` | `—` | Retained for backwards compatibility with the legacy column-wrapper renderer. |
|
|
674
959
|
| `classNames.item` | `string` | `—` | Item class name. |
|
|
675
|
-
| `
|
|
676
|
-
| `lazyLoad.spinner` | `boolean \| ReactNode \| ((args) => ReactNode)` | `—` | Spinner override for lazy items. |
|
|
677
|
-
| `lazyLoad.spinnerClassName` | `string` | `—` | Spinner wrapper class. |
|
|
678
|
-
| `lazyLoad.spinnerStyle` | `React.CSSProperties` | `—` | Spinner wrapper style. |
|
|
679
|
-
| `loading.enabled` | `boolean` | `—` | Enables the loading layer. |
|
|
680
|
-
| `loading.force` | `boolean` | `—` | Forces the loading layer to stay visible. |
|
|
681
|
-
| `loading.renderLoading` | `({ count }) => ReactNode` | `—` | Custom loading renderer. |
|
|
682
|
-
| `loading.skeleton` | `MasonrySkeletonSpec` | `—` | Built-in masonry skeleton spec. |
|
|
960
|
+
| `plugins` | `MasonryPlugin[]` | `[]` | Explicit first-party Masonry features such as lazy-load. |
|
|
683
961
|
| `intro.renderIntro` | `({ active, containerProps }, content) => ReactNode` | `—` | Custom intro wrapper. |
|
|
684
|
-
| `intro.staggerMs` | `number` | `
|
|
685
|
-
| `intro.durationMs` | `number` | `
|
|
962
|
+
| `intro.staggerMs` | `number` | `160` | Reveal stagger for the fade-in. |
|
|
963
|
+
| `intro.durationMs` | `number` | `600` | Intro fade duration. |
|
|
686
964
|
| `intro.easing` | `string` | `"cubic-bezier(.2,.7,.2,1)"` | Intro fade easing. |
|
|
687
965
|
| `intro.staggerLimit` | `number` | `—` | Optional cap on how many items stagger. |
|
|
688
966
|
|
|
689
|
-
|
|
967
|
+
### Masonry plugins
|
|
968
|
+
|
|
969
|
+
Import Masonry plugins from their own subpaths and pass them to `plugins`.
|
|
970
|
+
|
|
971
|
+
```typescript
|
|
972
|
+
import { Masonry } from "react-motion-gallery/masonry";
|
|
973
|
+
import { masonryLazyLoad } from "react-motion-gallery/masonry/lazy-load";
|
|
974
|
+
|
|
975
|
+
<Masonry plugins={[masonryLazyLoad({ spinner: true })]}>{items}</Masonry>;
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
| Import | Factory | Notes |
|
|
979
|
+
| --- | --- | --- |
|
|
980
|
+
| `react-motion-gallery/masonry/lazy-load` | `masonryLazyLoad(options)` | Uses the same image shell behavior as Slider: trackable image `src` values move into `data-rmg-lazy-src`, real images load on intersection, and items fade in after decode and spinner exit. |
|
|
690
981
|
|
|
691
|
-
|
|
982
|
+
`masonryLazyLoad()` enables lazy loading by default. Pass `{ enabled: false }` to make the plugin inert.
|
|
692
983
|
|
|
693
|
-
Masonry
|
|
984
|
+
Masonry already accepts arbitrary React children, including text-containing JSX. The wrapper props are only for styling the built-in masonry item shell.
|
|
694
985
|
|
|
695
|
-
|
|
986
|
+
Wrap a card in `Masonry.Item` when it needs its own span, wrapper `className`, or wrapper `style`:
|
|
696
987
|
|
|
697
988
|
```typescript
|
|
698
989
|
<Masonry
|
|
699
|
-
columns={{ 0: 1,
|
|
700
|
-
gap={{ 0: 12,
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
990
|
+
columns={{ 0: 1, 760: 2, 1160: 4 }}
|
|
991
|
+
gap={{ 0: 12, 1160: 18 }}
|
|
992
|
+
placement="horizontalOrder"
|
|
993
|
+
>
|
|
994
|
+
<Masonry.Item span={{ 0: 1, 760: 2, 1160: 2 }}>
|
|
995
|
+
<FeatureCard />
|
|
996
|
+
</Masonry.Item>
|
|
997
|
+
<Masonry.Item span={1}>
|
|
998
|
+
<StandardCard />
|
|
999
|
+
</Masonry.Item>
|
|
1000
|
+
</Masonry>
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
Choose a placement based on what should feel stable:
|
|
1004
|
+
|
|
1005
|
+
- `balanced`: best when visual balance and the shortest overall columns matter most.
|
|
1006
|
+
- `roundRobin`: best when deterministic column assignment matters more than tight packing.
|
|
1007
|
+
- `horizontalOrder`: best when wider cards should still read in a mostly left-to-right order.
|
|
1008
|
+
|
|
1009
|
+
Masonry no longer owns loading UI. Use `useMasonryReady` and wrap Masonry with `MasonrySkeleton`, the same composition pattern used by Slider and Grid.
|
|
1010
|
+
|
|
1011
|
+
Masonry skeletons live in `react-motion-gallery/skeleton/masonry` and can use a structured `layout` spec with the same inner node vocabulary as Grid skeletons, including `text` nodes and `itemWrapStyle`.
|
|
1012
|
+
|
|
1013
|
+
Live Masonry content mounts invisibly until the current item set has completed an initial measurement pass. The Skeleton wrapper stays visible during that handoff, so the first revealed layout is based on measured DOM geometry rather than approximate height hints.
|
|
1014
|
+
|
|
1015
|
+
`layout.slots` gives Masonry the same per-card override escape hatch that slider skeletons have. Use a slot when one card needs a different placeholder tree, wrapper styling, span, or outer height. `slot.span` can override the corresponding `Masonry.Item` span for the placeholder, `slot.ratio` maps to Masonry's card-height rhythm, and `slot.heightPx` lets you pin a specific shell height when you need an exact placeholder.
|
|
1016
|
+
|
|
1017
|
+
```typescript
|
|
1018
|
+
import { Masonry, useMasonryReady } from "react-motion-gallery";
|
|
1019
|
+
import {
|
|
1020
|
+
MasonrySkeleton,
|
|
1021
|
+
type MasonrySkeletonSpec,
|
|
1022
|
+
} from "react-motion-gallery/skeleton/masonry";
|
|
1023
|
+
|
|
1024
|
+
const masonrySkeleton: MasonrySkeletonSpec = {
|
|
1025
|
+
ratios: [118, 126, 102, 146],
|
|
1026
|
+
layout: {
|
|
1027
|
+
kind: "masonry",
|
|
1028
|
+
itemWrapStyle: {
|
|
1029
|
+
padding: 14,
|
|
1030
|
+
borderRadius: 20,
|
|
1031
|
+
boxShadow: "0 18px 36px rgba(15, 23, 42, 0.08)",
|
|
1032
|
+
},
|
|
1033
|
+
item: {
|
|
1034
|
+
kind: "col",
|
|
1035
|
+
style: { gap: 12 },
|
|
1036
|
+
children: [
|
|
1037
|
+
{
|
|
1038
|
+
kind: "rect",
|
|
1039
|
+
style: { width: "100%", height: 180, borderRadius: 16 },
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
kind: "text",
|
|
1043
|
+
barHeight: 14,
|
|
1044
|
+
lineHeight: 1.55,
|
|
1045
|
+
lines: 3,
|
|
1046
|
+
lastBarWidth: "74%",
|
|
1047
|
+
style: { width: "100%" },
|
|
715
1048
|
},
|
|
1049
|
+
],
|
|
1050
|
+
},
|
|
1051
|
+
slots: [
|
|
1052
|
+
{
|
|
1053
|
+
ratio: 182,
|
|
1054
|
+
span: { 0: 1, 1100: 2 },
|
|
716
1055
|
item: {
|
|
717
|
-
kind: "
|
|
718
|
-
style: {
|
|
719
|
-
children: [
|
|
720
|
-
{
|
|
721
|
-
kind: "rect",
|
|
722
|
-
style: { width: "100%", height: 180, borderRadius: 16 },
|
|
723
|
-
},
|
|
724
|
-
{
|
|
725
|
-
kind: "text",
|
|
726
|
-
fontSize: 12,
|
|
727
|
-
lineHeight: 1.4,
|
|
728
|
-
lines: 1,
|
|
729
|
-
lineWidth: "36%",
|
|
730
|
-
style: { width: "34%", borderRadius: 999 },
|
|
731
|
-
},
|
|
732
|
-
{
|
|
733
|
-
kind: "text",
|
|
734
|
-
fontSize: 18,
|
|
735
|
-
lineHeight: 1.35,
|
|
736
|
-
lines: { 0: 2, 900: 1 },
|
|
737
|
-
lineWidth: "64%",
|
|
738
|
-
style: { width: "88%" },
|
|
739
|
-
},
|
|
740
|
-
{
|
|
741
|
-
kind: "text",
|
|
742
|
-
fontSize: 14,
|
|
743
|
-
lineHeight: 1.55,
|
|
744
|
-
lines: 3,
|
|
745
|
-
lineWidth: "74%",
|
|
746
|
-
style: { width: "100%" },
|
|
747
|
-
},
|
|
748
|
-
],
|
|
1056
|
+
kind: "rect",
|
|
1057
|
+
style: { width: "100%", aspectRatio: "3 / 5", borderRadius: 16 },
|
|
749
1058
|
},
|
|
750
|
-
slots: [
|
|
751
|
-
{
|
|
752
|
-
ratio: 182,
|
|
753
|
-
item: {
|
|
754
|
-
kind: "col",
|
|
755
|
-
style: { gap: 12 },
|
|
756
|
-
children: [
|
|
757
|
-
{
|
|
758
|
-
kind: "rect",
|
|
759
|
-
style: { width: "100%", aspectRatio: "3 / 5", borderRadius: 16 },
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
kind: "text",
|
|
763
|
-
fontSize: 12,
|
|
764
|
-
lineHeight: 1.4,
|
|
765
|
-
lines: 1,
|
|
766
|
-
lineWidth: "36%",
|
|
767
|
-
style: { width: "28%", borderRadius: 999 },
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
kind: "text",
|
|
771
|
-
fontSize: 18,
|
|
772
|
-
lineHeight: 1.35,
|
|
773
|
-
lines: 1,
|
|
774
|
-
lineWidth: "64%",
|
|
775
|
-
style: { width: "72%" },
|
|
776
|
-
},
|
|
777
|
-
{
|
|
778
|
-
kind: "text",
|
|
779
|
-
fontSize: 14,
|
|
780
|
-
lineHeight: 1.55,
|
|
781
|
-
lines: 2,
|
|
782
|
-
lineWidth: "78%",
|
|
783
|
-
style: { width: "100%" },
|
|
784
|
-
},
|
|
785
|
-
],
|
|
786
|
-
},
|
|
787
|
-
},
|
|
788
|
-
],
|
|
789
1059
|
},
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1060
|
+
],
|
|
1061
|
+
},
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
function MasonryWithSkeleton({ items }: { items: React.ReactNode[] }) {
|
|
1065
|
+
const { ref: masonryRef, ready: masonryReady } = useMasonryReady();
|
|
1066
|
+
|
|
1067
|
+
return (
|
|
1068
|
+
<MasonrySkeleton
|
|
1069
|
+
layout={masonrySkeleton}
|
|
1070
|
+
ready={masonryReady}
|
|
1071
|
+
timing={{ minVisibleMs: 220, exitMs: 600 }}
|
|
1072
|
+
masonry={{
|
|
1073
|
+
count: items.length,
|
|
1074
|
+
columns: { 0: 1, 700: 2, 1100: 3 },
|
|
1075
|
+
gap: { 0: 12, 1100: 20 },
|
|
1076
|
+
placement: "balanced",
|
|
1077
|
+
}}
|
|
1078
|
+
>
|
|
1079
|
+
<Masonry
|
|
1080
|
+
ref={masonryRef}
|
|
1081
|
+
columns={{ 0: 1, 700: 2, 1100: 3 }}
|
|
1082
|
+
gap={{ 0: 12, 1100: 20 }}
|
|
1083
|
+
itemWrapStyle={{
|
|
1084
|
+
padding: "6px",
|
|
1085
|
+
borderRadius: "28px",
|
|
1086
|
+
}}
|
|
1087
|
+
>
|
|
1088
|
+
{items}
|
|
1089
|
+
</Masonry>
|
|
1090
|
+
</MasonrySkeleton>
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
795
1093
|
```
|
|
796
1094
|
|
|
797
1095
|
## Entries
|
|
@@ -863,6 +1161,16 @@ export function EntryGallery() {
|
|
|
863
1161
|
}
|
|
864
1162
|
```
|
|
865
1163
|
|
|
1164
|
+
### Entry loading, decode, and reveal flow
|
|
1165
|
+
|
|
1166
|
+
When `loading.enabled` is true, entries use two viewport gates instead of one generic fade-in. `loading.nearMargin` marks a row as near the viewport, mounts the real entry content, and starts the entry media work early. `loading.viewMargin` and `loading.threshold` record when the row has actually entered view.
|
|
1167
|
+
|
|
1168
|
+
With `loading.waitForDecode` enabled, an entry does not reveal as soon as it intersects. The built-in gate waits for every trackable media URL in that entry to load and decode; in the current entry-level gate, that means image media in the entry’s `media` array. It falls back after `loading.decodeTimeoutMs`, and entries without image media are decode-ready immediately. The row fades from skeleton to content only after both conditions are true: the row has entered view and the entry media decode gate is ready.
|
|
1169
|
+
|
|
1170
|
+
Reveal timing is assigned when each entry becomes ready, so entries fade in by actual load/decode completion order as well as viewport intersection. A later row that loads quickly can take the next reveal slot while a slower row keeps its skeleton visible until its media is ready.
|
|
1171
|
+
|
|
1172
|
+
Fullscreen close has a matching entry-aware path. If the user closes fullscreen from a slide whose owning entry has not been viewed yet, the runtime resolves the flattened fullscreen index back to the owner entry, shows a temporary loading spinner while that row mounts and decodes, scrolls the owner entry into view, forces the skeleton/content layers to their final revealed state, and then runs the close animation back to the now-visible entry media. This keeps the close animation from landing on an unrevealed skeleton or an offscreen row.
|
|
1173
|
+
|
|
866
1174
|
### `Entries` component props
|
|
867
1175
|
|
|
868
1176
|
| Option | Type | Default | Notes |
|
|
@@ -886,11 +1194,14 @@ export function EntryGallery() {
|
|
|
886
1194
|
| `mediaLayout` | `"slider" \| "grid" \| "masonry"` | `"slider"` | Declares the intended media layout. |
|
|
887
1195
|
| `render.card` | `({ entry, entryIndex, media }) => ReactNode` | `—` | Wraps the media container in custom card UI. |
|
|
888
1196
|
| `render.media` | `({ entry, entryIndex, media, mediaIndex }) => ReactNode` | `—` | Custom media renderer per media item. |
|
|
889
|
-
| `render.overlay` | `({ entry, entryIndex, mediaIndex, link, opacity, fsIndex, style, containerProps }) => ReactNode` | `—` | Renders fullscreen overlay content for the active entry slide. |
|
|
1197
|
+
| `render.overlay` | `({ entry, entryIndex, media, mediaIndex, link, opacity, fsIndex, style, containerProps }) => ReactNode` | `—` | Renders fullscreen overlay content for the active entry slide. |
|
|
890
1198
|
| `render.skeleton` | `({ entry, entryIndex }) => ReactNode` | `—` | Declared in the type, but the current runtime uses `loading.skeleton` instead. |
|
|
891
1199
|
| `overlay` | `ElementStyle` | `—` | Styles the fullscreen overlay container that wraps `render.overlay`. |
|
|
1200
|
+
| `overlay.overlayCrossfadeTarget` | `"content" \| "overlay"` | `"overlay"` | Selects whether fullscreen entry changes fade only the rendered overlay content or the whole overlay layer. |
|
|
1201
|
+
| `overlay.overlayCrossfadeDurationMs` | `number` | `300` | Duration for fullscreen entry overlay crossfades. |
|
|
1202
|
+
| `overlay.overlayCrossfadeEasing` | `string` | `"cubic-bezier(.4,0,.22,1)"` | Easing for fullscreen entry overlay crossfades. |
|
|
892
1203
|
| `loading.enabled` | `boolean` | `—` | Enables entry loading and decode gating. |
|
|
893
|
-
| `loading.force` | `boolean` | `—` | Forces entry skeletons to remain visible. |
|
|
1204
|
+
| `loading.force` | `boolean \| { enabled?: boolean; showContent?: boolean; skeletonOpacity?: number }` | `—` | Forces entry skeletons to remain visible. Set `showContent: true` to preview mounted, ready entry content under the skeleton, and tune the loading overlay with `skeletonOpacity`. |
|
|
894
1205
|
| `loading.skeleton` | `EntrySkeletonSpec \| ((args) => EntrySkeletonSpec \| null \| undefined)` | `—` | Built-in skeleton spec or resolver. |
|
|
895
1206
|
| `loading.minHeight` | `number \| string` | `"260px"` | Minimum reserved height while loading. |
|
|
896
1207
|
| `loading.nearMargin` | `string` | `"700px 0px"` | Preload margin used before entries enter view. |
|
|
@@ -907,7 +1218,7 @@ export function EntryGallery() {
|
|
|
907
1218
|
| `entryList` | `ElementStyle` | `—` | Styles the entry list container. |
|
|
908
1219
|
| `entryRow` | `ElementStyle` | `—` | Styles each entry row container. |
|
|
909
1220
|
|
|
910
|
-
Entry skeleton `text` nodes also render wrapped line bars via `lines`, matching the slider and grid skeleton behavior, including responsive line counts
|
|
1221
|
+
Entry skeleton `text` nodes also render wrapped line bars via `lines`, matching the slider and grid skeleton behavior, including responsive `barHeight` and line counts plus configurable trailing `lastBarWidth`.
|
|
911
1222
|
|
|
912
1223
|
### Entry-related callback and helper types
|
|
913
1224
|
|
|
@@ -941,6 +1252,7 @@ Entry skeleton `text` nodes also render wrapped line bars via `lines`, matching
|
|
|
941
1252
|
| --- | --- | --- |
|
|
942
1253
|
| `entry` | `EntryItem` | Entry owning the active fullscreen slide. |
|
|
943
1254
|
| `entryIndex` | `number` | Entry index. |
|
|
1255
|
+
| `media` | `MediaItem \| null` | Media item for the active fullscreen slide, when available. |
|
|
944
1256
|
| `mediaIndex` | `number \| null` | Media index inside the entry when available. |
|
|
945
1257
|
| `link` | `MediaEntryLink \| null` | Flattened link back to the entry/media pair. |
|
|
946
1258
|
| `opacity` | `number` | Overlay opacity supplied by the runtime. |
|
|
@@ -981,9 +1293,86 @@ Entry skeleton `text` nodes also render wrapped line bars via `lines`, matching
|
|
|
981
1293
|
|
|
982
1294
|
Fullscreen is compositional. `GalleryCore` owns the normalized fullscreen item list, your layout opens slides through that core, and `useFullscreenController` renders the portal UI.
|
|
983
1295
|
|
|
1296
|
+
### Standalone fullscreen
|
|
1297
|
+
|
|
1298
|
+
Use `GalleryCore` without a `layout` prop when your own markup owns the visible surface. Call `openFullscreenAt` with the matching item index, and render the fullscreen portal once inside the core.
|
|
1299
|
+
|
|
1300
|
+
```typescript
|
|
1301
|
+
import * as React from "react";
|
|
1302
|
+
import { GalleryCore, useGalleryCore } from "react-motion-gallery/core";
|
|
1303
|
+
import { useFullscreenController } from "react-motion-gallery/fullscreen";
|
|
1304
|
+
import { fullscreenSlider } from "react-motion-gallery/fullscreen/slider";
|
|
1305
|
+
import { toMediaItems } from "react-motion-gallery/media";
|
|
1306
|
+
|
|
1307
|
+
const images = [
|
|
1308
|
+
{
|
|
1309
|
+
src: "https://picsum.photos/id/1015/1600/900",
|
|
1310
|
+
alt: "Mountain lake",
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
src: "https://picsum.photos/id/1018/1600/900",
|
|
1314
|
+
alt: "Forest path",
|
|
1315
|
+
},
|
|
1316
|
+
];
|
|
1317
|
+
|
|
1318
|
+
const fullscreenItems = toMediaItems(images);
|
|
1319
|
+
|
|
1320
|
+
function FullscreenPortal() {
|
|
1321
|
+
const { fullscreenNode } = useFullscreenController({
|
|
1322
|
+
plugins: [fullscreenSlider()],
|
|
1323
|
+
fullscreen: { enabled: true },
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
return <>{fullscreenNode}</>;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function ImageButton(props: {
|
|
1330
|
+
image: (typeof images)[number];
|
|
1331
|
+
index: number;
|
|
1332
|
+
}) {
|
|
1333
|
+
const gallery = useGalleryCore();
|
|
1334
|
+
|
|
1335
|
+
const open = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
1336
|
+
gallery.openFullscreenAt({
|
|
1337
|
+
index: props.index,
|
|
1338
|
+
event: event.nativeEvent,
|
|
1339
|
+
});
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
return (
|
|
1343
|
+
<button type="button" onClick={open}>
|
|
1344
|
+
<img
|
|
1345
|
+
src={props.image.src}
|
|
1346
|
+
alt={props.image.alt}
|
|
1347
|
+
style={{
|
|
1348
|
+
display: "block",
|
|
1349
|
+
width: 180,
|
|
1350
|
+
aspectRatio: "16 / 9",
|
|
1351
|
+
objectFit: "cover",
|
|
1352
|
+
}}
|
|
1353
|
+
/>
|
|
1354
|
+
</button>
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
export function StandaloneFullscreen() {
|
|
1359
|
+
return (
|
|
1360
|
+
<GalleryCore fullscreenItems={fullscreenItems}>
|
|
1361
|
+
{images.map((image, index) => (
|
|
1362
|
+
<ImageButton key={image.src} image={image} index={index} />
|
|
1363
|
+
))}
|
|
1364
|
+
<FullscreenPortal />
|
|
1365
|
+
</GalleryCore>
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
### Slider fullscreen
|
|
1371
|
+
|
|
984
1372
|
```typescript
|
|
985
1373
|
import * as React from "react";
|
|
986
1374
|
import { GalleryCore, Slider, useFullscreenController } from "react-motion-gallery";
|
|
1375
|
+
import { fullscreenSlider } from "react-motion-gallery/fullscreen/slider";
|
|
987
1376
|
|
|
988
1377
|
const slides = [
|
|
989
1378
|
"https://picsum.photos/id/1015/1600/900",
|
|
@@ -993,6 +1382,7 @@ const slides = [
|
|
|
993
1382
|
|
|
994
1383
|
function FullscreenAddon() {
|
|
995
1384
|
const { fullscreenNode } = useFullscreenController({
|
|
1385
|
+
plugins: [fullscreenSlider()],
|
|
996
1386
|
fullscreen: { enabled: true },
|
|
997
1387
|
});
|
|
998
1388
|
|
|
@@ -1013,13 +1403,28 @@ export function SliderWithFullscreen() {
|
|
|
1013
1403
|
}
|
|
1014
1404
|
```
|
|
1015
1405
|
|
|
1406
|
+
### Fullscreen lazy-load handshake
|
|
1407
|
+
|
|
1408
|
+
Fullscreen keeps the base layout and fullscreen surface as separate render trees joined by one canonical index. The base layout can render thumbnails, cropped images, cards, or entries while `GalleryCore.fullscreenItems` provides the media that fullscreen renders for the same positions.
|
|
1409
|
+
|
|
1410
|
+
That index is also the communication channel for lazy loading. When a base item becomes visible, `GalleryCore` emits a base-visible event. If `fullscreen.lazyLoad.images.enabled` or `fullscreen.lazyLoad.videos.enabled` is active through `fullscreenLazyLoad()`, the fullscreen runtime listens for that event and prewarms the matching fullscreen media: images are fetched and decoded with high priority, and videos can prewarm their poster/source before being force-mounted.
|
|
1411
|
+
|
|
1412
|
+
Once the modal is open, the fullscreen slider index becomes the live gate. `fsSub` changes recompute which canonical image or video is allowed to mount or apply its source, then notify the lazy slide listeners. The active slide is always allowed; decoded images and prepared videos stay warm, and videos that were prewarmed from the base layout remain in the allowed set so navigation can land on prepared media.
|
|
1413
|
+
|
|
1414
|
+
Fullscreen also emits its visible index back through `GalleryCore`. Base media primitives use the core fullscreen state to suspend while fullscreen is active, and can use the visible fullscreen index to prewarm their matching media. Captions, overlays, and thumbnail rails stay synchronized through the same index contract.
|
|
1415
|
+
|
|
1416
|
+
For custom fullscreen images, `fullscreen.renderImage` must render a real descendant `<img>`. With `fullscreenLazyLoad({ images: { enabled: true } })`, that custom renderer participates in the same mount, spinner, load, and decode flow instead of mounting every fullscreen image eagerly.
|
|
1417
|
+
|
|
1016
1418
|
Add fullscreen thumbnails by rendering `FullscreenThumbnailSlider` with the bridge returned from `useFullscreenController`.
|
|
1017
1419
|
|
|
1018
1420
|
```typescript
|
|
1019
1421
|
import { FullscreenThumbnailSlider, useFullscreenController } from "react-motion-gallery";
|
|
1422
|
+
import { fullscreenSlider } from "react-motion-gallery/fullscreen/slider";
|
|
1423
|
+
import { fullscreenThumbnails } from "react-motion-gallery/fullscreen/thumbnails";
|
|
1020
1424
|
|
|
1021
1425
|
function FullscreenWithThumbs({ thumbs }: { thumbs: string[] }) {
|
|
1022
1426
|
const { fullscreenNode, fullscreenThumbnailBridge } = useFullscreenController({
|
|
1427
|
+
plugins: [fullscreenSlider(), fullscreenThumbnails()],
|
|
1023
1428
|
fullscreen: {
|
|
1024
1429
|
enabled: true,
|
|
1025
1430
|
slider: {
|
|
@@ -1047,6 +1452,7 @@ Set `fullscreen.slider.direction` when fullscreen should mirror RTL interaction:
|
|
|
1047
1452
|
|
|
1048
1453
|
```typescript
|
|
1049
1454
|
useFullscreenController({
|
|
1455
|
+
plugins: [fullscreenSlider()],
|
|
1050
1456
|
fullscreen: {
|
|
1051
1457
|
enabled: true,
|
|
1052
1458
|
slider: {
|
|
@@ -1056,22 +1462,52 @@ useFullscreenController({
|
|
|
1056
1462
|
});
|
|
1057
1463
|
```
|
|
1058
1464
|
|
|
1059
|
-
|
|
1465
|
+
Set `fullscreen.slider.gap` to add space between fullscreen slides. It accepts the same responsive number form as the base slider, using the `GalleryCore.breakpoints` map for named breakpoint keys:
|
|
1060
1466
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1467
|
+
```typescript
|
|
1468
|
+
useFullscreenController({
|
|
1469
|
+
plugins: [fullscreenSlider()],
|
|
1470
|
+
fullscreen: {
|
|
1471
|
+
enabled: true,
|
|
1472
|
+
slider: {
|
|
1473
|
+
gap: { 0: 12, md: 20, 1200: 28 },
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
});
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
Import `fullscreenVideo` from `react-motion-gallery/fullscreen/video` for fullscreen video slides. Set `fullscreen.video.playOnOpen` to start a Plyr-backed fullscreen video when fullscreen opens directly onto that video slide:
|
|
1480
|
+
|
|
1481
|
+
```typescript
|
|
1482
|
+
useFullscreenController({
|
|
1483
|
+
plugins: [fullscreenSlider(), fullscreenVideo()],
|
|
1484
|
+
fullscreen: {
|
|
1485
|
+
enabled: true,
|
|
1486
|
+
video: {
|
|
1487
|
+
playOnOpen: true,
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
});
|
|
1491
|
+
```
|
|
1068
1492
|
|
|
1069
1493
|
### `useFullscreenController` args
|
|
1070
1494
|
|
|
1071
1495
|
| Option | Type | Default | Notes |
|
|
1072
1496
|
| --- | --- | --- | --- |
|
|
1497
|
+
| `plugins` | `FullscreenPlugin[]` | `[]` | Explicit first-party fullscreen features. At minimum, import `fullscreenSlider()` to mount the fullscreen runtime. |
|
|
1073
1498
|
| `fullscreen` | `FullscreenOptions` | `—` | Fullscreen behavior and rendering options. |
|
|
1074
1499
|
|
|
1500
|
+
| Import | Factory | Notes |
|
|
1501
|
+
| --- | --- | --- |
|
|
1502
|
+
| `react-motion-gallery/fullscreen/slider` | `fullscreenSlider(options)` | Mounts the fullscreen slider runtime and accepts `fullscreen.slider` options. |
|
|
1503
|
+
| `react-motion-gallery/fullscreen/controls` | `fullscreenControls(options)` | Option plugin for close, arrows, and counter options. Use with `fullscreenSlider()`. |
|
|
1504
|
+
| `react-motion-gallery/fullscreen/captions` | `fullscreenCaptions(options)` | Adds caption rendering, placement, and caption motion runtime. Use with `fullscreenSlider()`. |
|
|
1505
|
+
| `react-motion-gallery/fullscreen/zoom-pan` | `fullscreenZoomPan(options)` | Adds fullscreen click zoom, pan, and pinch runtime. Use with `fullscreenSlider()`. |
|
|
1506
|
+
| `react-motion-gallery/fullscreen/video` | `fullscreenVideo(options)` | Adds fullscreen Plyr rendering, source/options, and `playOnOpen` runtime. Use with `fullscreenSlider()`. |
|
|
1507
|
+
| `react-motion-gallery/fullscreen/lazy-load` | `fullscreenLazyLoad(options)` | Adds fullscreen image and video lazy-load gates. Use with `fullscreenSlider()`. |
|
|
1508
|
+
| `react-motion-gallery/fullscreen/crossfade` | `fullscreenCrossfade(options)` | Option plugin for fullscreen crossfade controls, drag, and wheel behavior. Use with `fullscreenSlider()`. |
|
|
1509
|
+
| `react-motion-gallery/fullscreen/thumbnails` | `fullscreenThumbnails()` | Option-only plugin for fullscreen thumbnail bridge behavior. Use with `fullscreenSlider()`. |
|
|
1510
|
+
|
|
1075
1511
|
### Recommended `useFullscreenController` return values
|
|
1076
1512
|
|
|
1077
1513
|
| Field | Type | Notes |
|
|
@@ -1092,9 +1528,10 @@ The hook returns additional refs and setters for the internal fullscreen runtime
|
|
|
1092
1528
|
| --- | --- | --- | --- |
|
|
1093
1529
|
| `enabled` | `boolean` | `false` | Master switch for fullscreen UI. |
|
|
1094
1530
|
| `items` | `MediaItem[] \| string[]` | `—` | Declared in the type, but current fullscreen media resolution comes from `GalleryCore.fullscreenItems`. |
|
|
1095
|
-
| `renderImage` | `({ item, index, isZoomed, className, baseStyle }) => ReactNode` | `—` | Custom fullscreen image renderer. Must render a real descendant `<img>`. |
|
|
1531
|
+
| `renderImage` | `({ item, index, isZoomed, className, baseStyle }) => ReactNode` | `—` | Custom fullscreen image renderer. Must render a real descendant `<img>`. With `lazyLoad.images.enabled`, the renderer is mounted only when the slide is allowed and the runtime watches that descendant image for load/decode readiness. |
|
|
1096
1532
|
| `video.source` | `(item: MediaItem, index: number) => Plyr.SourceInfo` | `—` | Builds fullscreen Plyr sources for video items. |
|
|
1097
1533
|
| `video.options` | `Plyr.Options \| ((item: MediaItem, index: number) => Plyr.Options)` | `—` | Builds fullscreen Plyr options. |
|
|
1534
|
+
| `video.playOnOpen` | `boolean` | `false` | Attempts to play the fullscreen Plyr video when fullscreen opens directly onto a video slide. Browser autoplay rules still apply. |
|
|
1098
1535
|
| `video.style` | `React.CSSProperties` | `—` | Fullscreen player inline style. |
|
|
1099
1536
|
| `video.className` | `string` | `—` | Fullscreen player class. |
|
|
1100
1537
|
| `controls.close.enabled` | `boolean` | `true` | Toggles the close button. |
|
|
@@ -1120,6 +1557,9 @@ The hook returns additional refs and setters for the internal fullscreen runtime
|
|
|
1120
1557
|
| `caption.breakpoint` | `number` | `—` | Viewport cutoff for switching placement logic. |
|
|
1121
1558
|
| `caption.render` | `({ item, index, isZoomed }) => ReactNode` | `—` | Custom caption renderer. |
|
|
1122
1559
|
| `caption.layout` | `"overlay" \| "slide"` | `—` | Chooses whether the caption overlays the media or lives in the slide layout. |
|
|
1560
|
+
| `caption.overlayCrossfadeTarget` | `"content" \| "overlay"` | `"content"` | Selects whether overlay caption changes fade only the rendered caption content or the whole overlay layer. |
|
|
1561
|
+
| `caption.overlayCrossfadeDurationMs` | `number` | `300` | Duration for fullscreen overlay caption crossfades. |
|
|
1562
|
+
| `caption.overlayCrossfadeEasing` | `string` | `"cubic-bezier(.4,0,.22,1)"` | Easing for fullscreen overlay caption crossfades. |
|
|
1123
1563
|
| `caption.zoomFade` | `boolean` | `true` | Fades captions out on fullscreen zoom-in and back in on zoom-out. |
|
|
1124
1564
|
| `caption.zoomFadeDurationMs` | `number` | `300` | Duration for fullscreen caption zoom fades. |
|
|
1125
1565
|
| `caption.zoomFadeEasing` | `string` | `"cubic-bezier(.4,0,.22,1)"` | Easing for fullscreen caption zoom fades. |
|
|
@@ -1128,6 +1568,7 @@ The hook returns additional refs and setters for the internal fullscreen runtime
|
|
|
1128
1568
|
| `slider.duration` | `number` | `25` | Fullscreen slider motion duration. |
|
|
1129
1569
|
| `slider.friction` | `number` | `0.68` | Fullscreen slider friction. |
|
|
1130
1570
|
| `slider.direction` | `"ltr" \| "rtl"` | `"ltr"` | Fullscreen slider interaction direction. |
|
|
1571
|
+
| `slider.gap` | `number \| Record<string, number>` | `0` | Responsive pixel gap between fullscreen slides. Named keys resolve from `GalleryCore.breakpoints`. |
|
|
1131
1572
|
| `zoom.clickZoomLevel` | `number` | `2.5` | Zoom level used for click-to-zoom. |
|
|
1132
1573
|
| `zoom.maxZoomLevel` | `number` | `3` | Maximum allowed zoom level. |
|
|
1133
1574
|
| `zoom.panDuration` | `number` | `43` | Pan settling duration. |
|
|
@@ -1135,18 +1576,27 @@ The hook returns additional refs and setters for the internal fullscreen runtime
|
|
|
1135
1576
|
| `effects.introDuration` | `number` | `300` | Open animation duration. |
|
|
1136
1577
|
| `effects.introEasing` | `string` | `"cubic-bezier(.4,0,.22,1)"` | Open animation easing. |
|
|
1137
1578
|
| `effects.introFade` | `boolean` | `false` | Forces fade intro behavior. |
|
|
1138
|
-
| `effects.
|
|
1139
|
-
| `effects.
|
|
1140
|
-
| `effects.
|
|
1141
|
-
| `
|
|
1579
|
+
| `effects.crossfade.controls` | `boolean` | `false` | Uses crossfade transitions for fullscreen arrow navigation and animated slide requests. Also enables wheel crossfade unless `effects.crossfade.wheel` is provided. |
|
|
1580
|
+
| `effects.crossfade.drag` | `boolean` | `false` | Scrubs adjacent fullscreen slides with crossfade during drag instead of moving the track. |
|
|
1581
|
+
| `effects.crossfade.wheel` | `boolean \| CrossFadeWheelOptions` | `effects.crossfade.controls` | Uses wheel or touchpad travel as a one-slide-at-a-time fullscreen crossfade gesture. Set `false` to keep arrow crossfades while using normal wheel scrolling. |
|
|
1582
|
+
| `effects.crossfade.wheel.enabled` | `boolean` | `true` when object form is used | Enables or disables fullscreen wheel crossfade when using the object form. |
|
|
1583
|
+
| `effects.crossfade.wheel.sensitivity` | `number` | `5` | Multiplies wheel delta into virtual drag progress. Higher values reach the commit threshold sooner. |
|
|
1584
|
+
| `effects.crossfade.wheel.commitThreshold` | `number` | `0.38` | Progress needed to commit to the previous or next fullscreen slide. Values are clamped from `0` to below `0.5`. |
|
|
1585
|
+
| `effects.crossfade.wheel.durationMs` | `number` | `effects.crossfade.durationMs` | Fade duration after fullscreen wheel crossfade commits. |
|
|
1586
|
+
| `effects.crossfade.wheel.sessionGapMs` | `number` | `24` | Short quiet window used to distinguish same-direction touchpad tail from a fresh fullscreen wheel gesture after a committed wheel crossfade. |
|
|
1587
|
+
| `effects.crossfade.durationMs` | `number` | `120` | Shared fullscreen crossfade duration for controls, drag release, and wheel commit unless wheel overrides it. |
|
|
1588
|
+
| `effects.crossfade.easing` | `string` | `"cubic-bezier(.4,0,.22,1)"` | Shared fullscreen crossfade easing. |
|
|
1589
|
+
| `lazyLoad.images.enabled` | `boolean` | `—` | Enables fullscreen image lazy loading. Base-visible indices predecode matching fullscreen images, and fullscreen index changes allow the active image slide to mount or apply its source. |
|
|
1142
1590
|
| `lazyLoad.images.spinner` | `boolean \| ReactNode \| ((args) => ReactNode)` | `—` | Spinner override for fullscreen images. |
|
|
1143
1591
|
| `lazyLoad.images.spinnerClassName` | `string` | `—` | Spinner class for image slides. |
|
|
1144
1592
|
| `lazyLoad.images.spinnerStyle` | `React.CSSProperties` | `—` | Spinner style for image slides. |
|
|
1145
|
-
| `lazyLoad.videos.enabled` | `boolean` | `—` |
|
|
1593
|
+
| `lazyLoad.videos.enabled` | `boolean` | `—` | Opts fullscreen videos into lazy mounting. Base-visible indices prewarm matching video posters/sources and fullscreen index changes mount the active or already-prepared video slide. By default fullscreen Plyr videos mount eagerly in the hidden fullscreen tree. |
|
|
1146
1594
|
| `lazyLoad.videos.spinner` | `boolean \| ReactNode \| ((args) => ReactNode)` | `—` | Spinner override for fullscreen videos. |
|
|
1147
1595
|
| `lazyLoad.videos.spinnerClassName` | `string` | `—` | Spinner class for video slides. |
|
|
1148
1596
|
| `lazyLoad.videos.spinnerStyle` | `React.CSSProperties` | `—` | Spinner style for video slides. |
|
|
1149
1597
|
|
|
1598
|
+
Fullscreen `effects.crossfade.wheel` uses the same `true`, `false`, or object form as slider wheel crossfade. Its `durationMs` default follows fullscreen `effects.crossfade.durationMs`, which defaults to `120`.
|
|
1599
|
+
|
|
1150
1600
|
### Fullscreen callback and helper types
|
|
1151
1601
|
|
|
1152
1602
|
#### `FsCounterArgs`
|
|
@@ -1255,37 +1705,6 @@ The hook returns additional refs and setters for the internal fullscreen runtime
|
|
|
1255
1705
|
| `fadeDurationMs` | `number \| undefined` | Slot fade duration. |
|
|
1256
1706
|
| `fadeEasing` | `string \| undefined` | Slot fade easing. |
|
|
1257
1707
|
|
|
1258
|
-
### `GalleryApi`
|
|
1259
|
-
|
|
1260
|
-
`GalleryApi` is exported as a type from the package root. The package also exposes `GalleryCore` and `useGalleryCore()` for core-context access, but it does not expose a dedicated hook that returns a `GalleryApi`-typed instance directly.
|
|
1261
|
-
|
|
1262
|
-
| Method | Signature | Notes |
|
|
1263
|
-
| --- | --- | --- |
|
|
1264
|
-
| `rootNode` | `() => HTMLElement \| null` | Gallery root node. |
|
|
1265
|
-
| `containerNode` | `() => HTMLElement \| null` | Moving or content container node. |
|
|
1266
|
-
| `getViewportNode` | `() => HTMLDivElement \| null` | Viewport node. |
|
|
1267
|
-
| `slideNodes` | `() => HTMLElement[]` | Current slide elements. |
|
|
1268
|
-
| `onReady` | `(cb: (nodes: HTMLElement[]) => void) => () => void` | Subscribes to readiness. |
|
|
1269
|
-
| `whenReady` | `() => Promise<HTMLElement[]>` | Promise form of readiness. |
|
|
1270
|
-
| `isReady` | `() => boolean` | `true` after readiness resolves. |
|
|
1271
|
-
| `scrollTo` | `(index: number, jump?: boolean) => void` | Navigates to a slide. |
|
|
1272
|
-
| `scrollNext` | `(jump?: boolean) => void` | Advances to the next slide. |
|
|
1273
|
-
| `scrollPrev` | `(jump?: boolean) => void` | Moves to the previous slide. |
|
|
1274
|
-
| `canScrollNext` | `() => boolean` | Whether next navigation is available. |
|
|
1275
|
-
| `canScrollPrev` | `() => boolean` | Whether previous navigation is available. |
|
|
1276
|
-
| `getIndex` | `() => number` | Current active index. |
|
|
1277
|
-
| `selectCell` | `(index: number, jump?: boolean) => void` | Selects a cell by canonical index. |
|
|
1278
|
-
| `scrollProgress` | `() => number` | Scroll progress from `0` to `1`. |
|
|
1279
|
-
| `cellsInView` | `() => number[]` | Canonical cells currently visible. |
|
|
1280
|
-
| `append` | `(nodes: ReactNode \| ReactNode[]) => number` | Appends nodes and returns the new total count. |
|
|
1281
|
-
| `prepend` | `(nodes: ReactNode \| ReactNode[]) => number` | Prepends nodes and returns the new total count. |
|
|
1282
|
-
| `insert` | `(index: number, nodes: ReactNode \| ReactNode[]) => number` | Inserts nodes and returns the new total count. |
|
|
1283
|
-
| `remove` | `(indexOrPredicate: number \| ((i: number) => boolean)) => number` | Removes items and returns the new total count. |
|
|
1284
|
-
| `replace` | `(index: number, node: ReactNode) => void` | Replaces a node at an index. |
|
|
1285
|
-
| `setItems` | `(nodes: ReactNode[]) => number` | Replaces all nodes and returns the new total count. |
|
|
1286
|
-
| `onIndexChange` | `(cb: (i: number, meta: { mode: IndexMode }) => void) => () => void` | Subscribes to index changes. |
|
|
1287
|
-
| `openFullscreenAt` | `({ index, method?, event? }) => void` | Programmatically opens fullscreen at an index. |
|
|
1288
|
-
|
|
1289
1708
|
## Video
|
|
1290
1709
|
|
|
1291
1710
|
`Video` is the gallery-aware video primitive. It mounts Plyr lazily, syncs with gallery visibility, and can be used inside `Slider`, `Grid`, `Masonry`, `Entries`, and fullscreen flows.
|