yet-another-react-lightbox-lite 1.0.0 → 1.2.0
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/README.md +123 -28
- package/dist/index.d.ts +52 -1
- package/dist/index.js +33 -17
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Lightweight React lightbox component. This is a trimmed-down version of the
|
|
4
4
|
[yet-another-react-lightbox](https://github.com/igordanchenko/yet-another-react-lightbox)
|
|
5
|
-
that provides essential lightbox features and slick UX with just
|
|
5
|
+
that provides essential lightbox features and slick UX with just 3KB bundle
|
|
6
6
|
size.
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
[](https://www.npmjs.com/package/yet-another-react-lightbox)
|
|
11
|
-
[](https://bundlephobia.com/package/yet-another-react-lightbox)
|
|
12
|
-
[](https://github.com/igordanchenko/yet-another-react-lightbox/blob/main/LICENSE)
|
|
10
|
+
[](https://www.npmjs.com/package/yet-another-react-lightbox-lite)
|
|
11
|
+
[](https://bundlephobia.com/package/yet-another-react-lightbox-lite)
|
|
12
|
+
[](https://github.com/igordanchenko/yet-another-react-lightbox-lite/blob/main/LICENSE)
|
|
13
13
|
|
|
14
14
|
- **Built for React:** works with React 18+
|
|
15
15
|
- **UX:** supports keyboard, mouse, touchpad and touchscreen navigation
|
|
@@ -110,7 +110,6 @@ with `next/image` with the following `render.slide` render function.
|
|
|
110
110
|
|
|
111
111
|
```tsx
|
|
112
112
|
<Lightbox
|
|
113
|
-
// ...
|
|
114
113
|
render={{
|
|
115
114
|
slide: ({ slide, rect }) => {
|
|
116
115
|
const width =
|
|
@@ -147,6 +146,7 @@ with `next/image` with the following `render.slide` render function.
|
|
|
147
146
|
);
|
|
148
147
|
},
|
|
149
148
|
}}
|
|
149
|
+
// ...
|
|
150
150
|
/>
|
|
151
151
|
```
|
|
152
152
|
|
|
@@ -203,6 +203,63 @@ Custom UI labels / translations.
|
|
|
203
203
|
/>
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
### toolbar
|
|
207
|
+
|
|
208
|
+
Type: `object`
|
|
209
|
+
|
|
210
|
+
Toolbar settings.
|
|
211
|
+
|
|
212
|
+
- `buttons` - custom toolbar buttons (type: `ReactNode[]`)
|
|
213
|
+
- `fixed` - if `true`, the toolbar is positioned statically above the carousel
|
|
214
|
+
|
|
215
|
+
Usage example:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
<Lightbox
|
|
219
|
+
toolbar={{
|
|
220
|
+
fixed: true,
|
|
221
|
+
buttons: [
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
className="yarll__button"
|
|
225
|
+
onClick={() => {
|
|
226
|
+
// ...
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<ButtonIcon />
|
|
230
|
+
</button>,
|
|
231
|
+
],
|
|
232
|
+
}}
|
|
233
|
+
// ...
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### controller
|
|
238
|
+
|
|
239
|
+
Type: `object`
|
|
240
|
+
|
|
241
|
+
Controller settings.
|
|
242
|
+
|
|
243
|
+
- `closeOnPullUp` - if `true`, close the lightbox on pull-up gesture (default:
|
|
244
|
+
`true`)
|
|
245
|
+
- `closeOnPullDown` - if `true`, close the lightbox on pull-down gesture
|
|
246
|
+
(default: `true`)
|
|
247
|
+
- `closeOnBackdropClick` - if `true`, close the lightbox when the backdrop is
|
|
248
|
+
clicked (default: `true`)
|
|
249
|
+
|
|
250
|
+
Usage example:
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
<Lightbox
|
|
254
|
+
controller={{
|
|
255
|
+
closeOnPullUp: false,
|
|
256
|
+
closeOnPullDown: false,
|
|
257
|
+
closeOnBackdropClick: false,
|
|
258
|
+
}}
|
|
259
|
+
// ...
|
|
260
|
+
/>
|
|
261
|
+
```
|
|
262
|
+
|
|
206
263
|
### render
|
|
207
264
|
|
|
208
265
|
Type: `object`
|
|
@@ -268,7 +325,6 @@ slides counter.
|
|
|
268
325
|
|
|
269
326
|
```tsx
|
|
270
327
|
<Lightbox
|
|
271
|
-
// ...
|
|
272
328
|
render={{
|
|
273
329
|
controls: () =>
|
|
274
330
|
index !== undefined && (
|
|
@@ -277,29 +333,7 @@ slides counter.
|
|
|
277
333
|
</div>
|
|
278
334
|
),
|
|
279
335
|
}}
|
|
280
|
-
/>
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
You can also use the `render.controls` render function to add custom buttons to
|
|
284
|
-
the toolbar.
|
|
285
|
-
|
|
286
|
-
```tsx
|
|
287
|
-
<Lightbox
|
|
288
336
|
// ...
|
|
289
|
-
render={{
|
|
290
|
-
controls: () => (
|
|
291
|
-
<button
|
|
292
|
-
type="button"
|
|
293
|
-
className="yarll__button"
|
|
294
|
-
style={{ position: "absolute", top: 8, right: 64 }}
|
|
295
|
-
onClick={() => {
|
|
296
|
-
// ...
|
|
297
|
-
}}
|
|
298
|
-
>
|
|
299
|
-
<ButtonIcon />
|
|
300
|
-
</button>
|
|
301
|
-
),
|
|
302
|
-
}}
|
|
303
337
|
/>
|
|
304
338
|
```
|
|
305
339
|
|
|
@@ -315,6 +349,42 @@ Render custom `Next` icon.
|
|
|
315
349
|
|
|
316
350
|
Render custom `Close` icon.
|
|
317
351
|
|
|
352
|
+
### styles
|
|
353
|
+
|
|
354
|
+
Type: `{ [key in Slot]?: SlotCSSProperties }`
|
|
355
|
+
|
|
356
|
+
Customization slots styles allow you to specify custom CSS styles or override
|
|
357
|
+
`--yarll__*` CSS variables by passing your custom styles through to the
|
|
358
|
+
corresponding lightbox elements.
|
|
359
|
+
|
|
360
|
+
Supported customization slots:
|
|
361
|
+
|
|
362
|
+
- `portal` - lightbox portal (root)
|
|
363
|
+
- `carousel` - lightbox carousel
|
|
364
|
+
- `slide` - lightbox slide
|
|
365
|
+
- `image` - lightbox slide image
|
|
366
|
+
- `toolbar` - lightbox toolbar
|
|
367
|
+
- `button` - lightbox button
|
|
368
|
+
- `icon` - lightbox icon
|
|
369
|
+
|
|
370
|
+
Usage example:
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
<Lightbox
|
|
374
|
+
styles={{
|
|
375
|
+
portal: { "--yarll__backdrop_color": "rgba(0, 0, 0, 0.6)" },
|
|
376
|
+
}}
|
|
377
|
+
// ...
|
|
378
|
+
/>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### className
|
|
382
|
+
|
|
383
|
+
Type: `string`
|
|
384
|
+
|
|
385
|
+
CSS class of the lightbox root element. You can use this class name to provide
|
|
386
|
+
module-scoped style overrides.
|
|
387
|
+
|
|
318
388
|
## Custom Slide Attributes
|
|
319
389
|
|
|
320
390
|
You can add custom slide attributes with the following module augmentation.
|
|
@@ -420,6 +490,31 @@ export default function App() {
|
|
|
420
490
|
}
|
|
421
491
|
```
|
|
422
492
|
|
|
493
|
+
## Body Scroll Lock
|
|
494
|
+
|
|
495
|
+
By default, the lightbox hides the browser window scrollbar and prevents
|
|
496
|
+
document `<body>` from scrolling underneath the lightbox by assigning the
|
|
497
|
+
`height: 100%; overflow: hidden;` styles to the document `<body>` element.
|
|
498
|
+
|
|
499
|
+
If this behavior causes undesired side effects in your case, and you prefer not
|
|
500
|
+
to use this feature, you can turn it off by assigning the
|
|
501
|
+
`yarll__no_scroll_lock` class to the lightbox.
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
<Lightbox
|
|
505
|
+
className="yarll__no_scroll_lock"
|
|
506
|
+
// ...
|
|
507
|
+
/>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
However, if you keep the body scroll lock feature on, you may notice a visual
|
|
511
|
+
layout shift of some fixed-positioned page elements when the lightbox opens. To
|
|
512
|
+
address this, you can assign the `yarll__fixed` CSS class to your
|
|
513
|
+
fixed-positioned elements to keep them in place. Please note that the
|
|
514
|
+
fixed-positioned element container should not have its own border or padding
|
|
515
|
+
styles. If that's the case, you can always add an extra wrapper that just
|
|
516
|
+
defines the fixed position without visual styles.
|
|
517
|
+
|
|
423
518
|
## License
|
|
424
519
|
|
|
425
520
|
MIT © 2024 [Igor Danchenko](https://github.com/igordanchenko)
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,14 @@ interface LightboxProps {
|
|
|
13
13
|
labels?: Labels;
|
|
14
14
|
/** custom render functions */
|
|
15
15
|
render?: Render;
|
|
16
|
+
/** toolbar settings */
|
|
17
|
+
toolbar?: ToolbarSettings;
|
|
18
|
+
/** controller settings */
|
|
19
|
+
controller?: ControllerSettings;
|
|
20
|
+
/** customization slots styles */
|
|
21
|
+
styles?: SlotStyles;
|
|
22
|
+
/** CSS class of the lightbox root element */
|
|
23
|
+
className?: string;
|
|
16
24
|
}
|
|
17
25
|
/** Slide */
|
|
18
26
|
type Slide = SlideTypes[SlideTypeKey];
|
|
@@ -88,6 +96,49 @@ interface RenderSlideProps {
|
|
|
88
96
|
/** if `true`, the slide is the current slide in the viewport */
|
|
89
97
|
current: boolean;
|
|
90
98
|
}
|
|
99
|
+
/** Toolbar settings */
|
|
100
|
+
interface ToolbarSettings {
|
|
101
|
+
/** custom toolbar buttons */
|
|
102
|
+
buttons?: React.ReactNode[];
|
|
103
|
+
/** if `true`, the toolbar is positioned statically above the carousel */
|
|
104
|
+
fixed?: boolean;
|
|
105
|
+
}
|
|
106
|
+
/** Controller settings */
|
|
107
|
+
interface ControllerSettings {
|
|
108
|
+
/** if `true`, close the lightbox on pull-up gesture (default: `true`) */
|
|
109
|
+
closeOnPullUp?: boolean;
|
|
110
|
+
/** if `true`, close the lightbox on pull-down gesture (default: `true`) */
|
|
111
|
+
closeOnPullDown?: boolean;
|
|
112
|
+
/** if `true`, close the lightbox when the backdrop is clicked (default: `true`) */
|
|
113
|
+
closeOnBackdropClick?: boolean;
|
|
114
|
+
}
|
|
115
|
+
/** Customization slots */
|
|
116
|
+
interface SlotType {
|
|
117
|
+
/** lightbox portal (root) customization slot */
|
|
118
|
+
portal: "portal";
|
|
119
|
+
/** lightbox carousel customization slot */
|
|
120
|
+
carousel: "carousel";
|
|
121
|
+
/** lightbox slide customization slot */
|
|
122
|
+
slide: "slide";
|
|
123
|
+
/** lightbox slide image customization slot */
|
|
124
|
+
image: "image";
|
|
125
|
+
/** lightbox toolbar customization slot */
|
|
126
|
+
toolbar: "toolbar";
|
|
127
|
+
/** lightbox button customization slot */
|
|
128
|
+
button: "button";
|
|
129
|
+
/** lightbox icon customization slot */
|
|
130
|
+
icon: "icon";
|
|
131
|
+
}
|
|
132
|
+
/** Customization slots */
|
|
133
|
+
type Slot = SlotType[keyof SlotType];
|
|
134
|
+
/** Customization slot CSS properties */
|
|
135
|
+
interface SlotCSSProperties extends React.CSSProperties {
|
|
136
|
+
[key: `--yarll__${string}`]: string | number;
|
|
137
|
+
}
|
|
138
|
+
/** Customization slots styles */
|
|
139
|
+
type SlotStyles = {
|
|
140
|
+
[key in Slot]?: SlotCSSProperties;
|
|
141
|
+
};
|
|
91
142
|
/** Rect */
|
|
92
143
|
type Rect = {
|
|
93
144
|
width: number;
|
|
@@ -100,4 +151,4 @@ type RenderFunction<T = void> = [T] extends [void] ? () => React.ReactNode : (pr
|
|
|
100
151
|
|
|
101
152
|
declare function Lightbox({ slides, index, setIndex, ...rest }: LightboxProps): react_jsx_runtime.JSX.Element | null;
|
|
102
153
|
|
|
103
|
-
export { type Callback, type GenericSlide, type ImageSource, type Label, type Labels, type LightboxProps, type Rect, type Render, type RenderFunction, type RenderSlideProps, type Slide, type SlideImage, type SlideTypeKey, type SlideTypes, Lightbox as default };
|
|
154
|
+
export { type Callback, type ControllerSettings, type GenericSlide, type ImageSource, type Label, type Labels, type LightboxProps, type Rect, type Render, type RenderFunction, type RenderSlideProps, type Slide, type SlideImage, type SlideTypeKey, type SlideTypes, type Slot, type SlotStyles, type SlotType, type ToolbarSettings, Lightbox as default };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { useContext, createContext, useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
|
2
|
+
import { useContext, createContext, useState, useRef, useCallback, useMemo, useEffect, isValidElement, cloneElement } from 'react';
|
|
3
3
|
import { flushSync, createPortal } from 'react-dom';
|
|
4
4
|
|
|
5
5
|
const cssPrefix = "yarll__";
|
|
@@ -41,6 +41,7 @@ function LightboxContextProvider({ children, ...props }) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function ImageSlide({ slide, rect }) {
|
|
44
|
+
const { styles } = useLightboxContext();
|
|
44
45
|
const { width, height } = slide.srcSet?.[0] ?? slide;
|
|
45
46
|
const imageAspectRatio = width && height ? width / height : undefined;
|
|
46
47
|
const srcSet = slide.srcSet
|
|
@@ -50,11 +51,11 @@ function ImageSlide({ slide, rect }) {
|
|
|
50
51
|
const sizes = imageAspectRatio
|
|
51
52
|
? `${imageAspectRatio < rect.width / rect.height ? Math.round(imageAspectRatio * rect.height) : rect.width}px`
|
|
52
53
|
: undefined;
|
|
53
|
-
return (jsx("img", { draggable: false, className: cssClass("slide_image"), srcSet: srcSet, sizes: sizes, src: slide.src, alt: slide.alt }));
|
|
54
|
+
return (jsx("img", { draggable: false, style: styles?.image, className: cssClass("slide_image"), srcSet: srcSet, sizes: sizes, src: slide.src, alt: slide.alt }));
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
function Carousel() {
|
|
57
|
-
const { slides, index, render: { slide: renderSlide, slideHeader, slideFooter } = {} } = useLightboxContext();
|
|
58
|
+
const { slides, index, styles, render: { slide: renderSlide, slideHeader, slideFooter } = {} } = useLightboxContext();
|
|
58
59
|
const [rect, setRect] = useState();
|
|
59
60
|
const observer = useRef();
|
|
60
61
|
const handleRef = useCallback((node) => {
|
|
@@ -69,7 +70,7 @@ function Carousel() {
|
|
|
69
70
|
updateRect();
|
|
70
71
|
}
|
|
71
72
|
}, []);
|
|
72
|
-
return (jsx("div", { ref: handleRef, className: cssClass("carousel"), children: rect &&
|
|
73
|
+
return (jsx("div", { ref: handleRef, style: styles?.carousel, className: cssClass("carousel"), children: rect &&
|
|
73
74
|
Array.from({ length: 5 }).map((_, i) => {
|
|
74
75
|
const slideIndex = index - 2 + i;
|
|
75
76
|
if (slideIndex < 0 || slideIndex >= slides.length)
|
|
@@ -77,7 +78,7 @@ function Carousel() {
|
|
|
77
78
|
const slide = slides[slideIndex];
|
|
78
79
|
const current = slideIndex === index;
|
|
79
80
|
const context = { slide, rect, current };
|
|
80
|
-
return (jsxs("div", { role: "group", "aria-roledescription": "slide", className: cssClass("slide"), hidden: !current, children: [slideHeader?.(context), renderSlide?.(context) ?? jsx(ImageSlide, { ...context }), slideFooter?.(context)] }, slide.key ?? `${slideIndex}-${slide.src}`));
|
|
81
|
+
return (jsxs("div", { role: "group", "aria-roledescription": "slide", className: cssClass("slide"), hidden: !current, style: styles?.slide, children: [slideHeader?.(context), renderSlide?.(context) ?? jsx(ImageSlide, { ...context }), slideFooter?.(context)] }, slide.key ?? `${slideIndex}-${slide.src}`));
|
|
81
82
|
}) }));
|
|
82
83
|
}
|
|
83
84
|
|
|
@@ -115,9 +116,9 @@ function Controller({ setIndex, children }) {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
function Button({ icon: Icon, renderIcon, label, onClick, disabled, className }) {
|
|
118
|
-
const { labels } = useLightboxContext();
|
|
119
|
+
const { labels, styles } = useLightboxContext();
|
|
119
120
|
const buttonLabel = translateLabel(labels, label);
|
|
120
|
-
return (jsx("button", { type: "button", title: buttonLabel, "aria-label": buttonLabel, onClick: onClick, disabled: disabled, className: clsx(cssClass("button"), className), children: renderIcon?.() ?? jsx(Icon, { className: cssClass("icon") }) }));
|
|
121
|
+
return (jsx("button", { type: "button", title: buttonLabel, "aria-label": buttonLabel, onClick: onClick, disabled: disabled, style: styles?.button, className: clsx(cssClass("button"), className), children: renderIcon?.() ?? jsx(Icon, { style: styles?.icon, className: cssClass("icon") }) }));
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
function svgIcon(name, children) {
|
|
@@ -136,9 +137,9 @@ const Next = createIcon("Next", jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58
|
|
|
136
137
|
const Previous = createIcon("Previous", jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }));
|
|
137
138
|
|
|
138
139
|
function Navigation() {
|
|
139
|
-
const { slides, index, render: { iconPrev, iconNext,
|
|
140
|
-
const { prev, next
|
|
141
|
-
return (jsxs(Fragment, { children: [slides.length > 1 && (jsxs(Fragment, { children: [jsx(Button, { label: "Previous", icon: Previous, renderIcon: iconPrev, onClick: prev, className: cssClass("button_prev"), disabled: index <= 0 }), jsx(Button, { label: "Next", icon: Next, renderIcon: iconNext, onClick: next, className: cssClass("button_next"), disabled: index >= slides.length - 1 })] })),
|
|
140
|
+
const { slides, index, render: { iconPrev, iconNext, controls } = {} } = useLightboxContext();
|
|
141
|
+
const { prev, next } = useController();
|
|
142
|
+
return (jsxs(Fragment, { children: [slides.length > 1 && (jsxs(Fragment, { children: [jsx(Button, { label: "Previous", icon: Previous, renderIcon: iconPrev, onClick: prev, className: cssClass("button_prev"), disabled: index <= 0 }), jsx(Button, { label: "Next", icon: Next, renderIcon: iconNext, onClick: next, className: cssClass("button_next"), disabled: index >= slides.length - 1 })] })), controls?.()] }));
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
function useSensors() {
|
|
@@ -147,6 +148,12 @@ function useSensors() {
|
|
|
147
148
|
const wheelCooldownMomentum = useRef(null);
|
|
148
149
|
const activePointer = useRef(null);
|
|
149
150
|
const { prev, next, close } = useController();
|
|
151
|
+
const { closeOnPullUp, closeOnPullDown, closeOnBackdropClick } = {
|
|
152
|
+
closeOnPullUp: true,
|
|
153
|
+
closeOnPullDown: true,
|
|
154
|
+
closeOnBackdropClick: true,
|
|
155
|
+
...useLightboxContext().controller,
|
|
156
|
+
};
|
|
150
157
|
return useMemo(() => {
|
|
151
158
|
const onKeyDown = (event) => {
|
|
152
159
|
switch (event.key) {
|
|
@@ -173,8 +180,9 @@ function useSensors() {
|
|
|
173
180
|
const onPointerUp = (event) => {
|
|
174
181
|
if (event.pointerId === activePointer.current?.pointerId) {
|
|
175
182
|
const dx = event.clientX - activePointer.current.clientX;
|
|
183
|
+
const dy = event.clientY - activePointer.current.clientY;
|
|
176
184
|
const deltaX = Math.abs(dx);
|
|
177
|
-
const deltaY = Math.abs(
|
|
185
|
+
const deltaY = Math.abs(dy);
|
|
178
186
|
if (deltaX > 50 && deltaX > 1.2 * deltaY) {
|
|
179
187
|
if (dx > 0) {
|
|
180
188
|
prev();
|
|
@@ -183,9 +191,10 @@ function useSensors() {
|
|
|
183
191
|
next();
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
|
-
else if ((deltaY > 50 && deltaY > 1.2 * deltaX) ||
|
|
187
|
-
(
|
|
188
|
-
activePointer.current.target
|
|
194
|
+
else if ((deltaY > 50 && deltaY > 1.2 * deltaX && ((closeOnPullUp && dy < 0) || (closeOnPullDown && dy > 0))) ||
|
|
195
|
+
(closeOnBackdropClick &&
|
|
196
|
+
activePointer.current.target instanceof Element &&
|
|
197
|
+
Array.from(activePointer.current.target.classList).some((className) => [cssClass("slide"), cssClass("portal")].includes(className)))) {
|
|
189
198
|
close();
|
|
190
199
|
}
|
|
191
200
|
activePointer.current = null;
|
|
@@ -229,7 +238,7 @@ function useSensors() {
|
|
|
229
238
|
onPointerCancel: onPointerUp,
|
|
230
239
|
onWheel,
|
|
231
240
|
};
|
|
232
|
-
}, [prev, next, close]);
|
|
241
|
+
}, [prev, next, close, closeOnPullUp, closeOnPullDown, closeOnBackdropClick]);
|
|
233
242
|
}
|
|
234
243
|
|
|
235
244
|
function setAttribute(element, attribute, value) {
|
|
@@ -245,6 +254,7 @@ function setAttribute(element, attribute, value) {
|
|
|
245
254
|
};
|
|
246
255
|
}
|
|
247
256
|
function Portal({ children }) {
|
|
257
|
+
const { styles, className } = useLightboxContext();
|
|
248
258
|
const cleanup = useRef([]);
|
|
249
259
|
const [mounted, setMounted] = useState(false);
|
|
250
260
|
const [visible, setVisible] = useState(false);
|
|
@@ -307,7 +317,7 @@ function Portal({ children }) {
|
|
|
307
317
|
}
|
|
308
318
|
}, [handleCleanup]);
|
|
309
319
|
return mounted
|
|
310
|
-
? createPortal(jsx("div", { "aria-modal": true, role: "dialog", "aria-roledescription": "carousel", tabIndex: -1, ref: handleRef, className: clsx(cssClass("portal"), !visible && cssClass("portal_closed")), onTransitionEnd: onTransitionEnd.current, onFocus: (event) => {
|
|
320
|
+
? createPortal(jsx("div", { "aria-modal": true, role: "dialog", "aria-roledescription": "carousel", tabIndex: -1, ref: handleRef, style: styles?.portal, className: clsx(cssClass("portal"), !visible && cssClass("portal_closed"), className), onTransitionEnd: onTransitionEnd.current, onFocus: (event) => {
|
|
311
321
|
if (!restoreFocus.current) {
|
|
312
322
|
restoreFocus.current = event.relatedTarget;
|
|
313
323
|
}
|
|
@@ -315,10 +325,16 @@ function Portal({ children }) {
|
|
|
315
325
|
: null;
|
|
316
326
|
}
|
|
317
327
|
|
|
328
|
+
function Toolbar() {
|
|
329
|
+
const { render: { iconClose } = {}, toolbar: { buttons, fixed } = {}, styles } = useLightboxContext();
|
|
330
|
+
const { close } = useController();
|
|
331
|
+
return (jsxs("div", { style: styles?.toolbar, className: clsx(cssClass("toolbar"), fixed && cssClass("toolbar_fixed")), children: [buttons?.map((button, key) => (isValidElement(button) && !button.key ? cloneElement(button, { key }) : button)), jsx(Button, { label: "Close", icon: Close, renderIcon: iconClose, onClick: close })] }));
|
|
332
|
+
}
|
|
333
|
+
|
|
318
334
|
function Lightbox({ slides, index, setIndex, ...rest }) {
|
|
319
335
|
if (!Array.isArray(slides) || index === undefined || index < 0 || index >= slides.length)
|
|
320
336
|
return null;
|
|
321
|
-
return (jsx(LightboxContextProvider, { slides, index, ...rest, children: jsx(Controller, { setIndex, children: jsxs(Portal, { children: [jsx(Carousel, {}), jsx(Navigation, {})] }) }) }));
|
|
337
|
+
return (jsx(LightboxContextProvider, { slides, index, ...rest, children: jsx(Controller, { setIndex, children: jsxs(Portal, { children: [jsx(Toolbar, {}), jsx(Carousel, {}), jsx(Navigation, {})] }) }) }));
|
|
322
338
|
}
|
|
323
339
|
|
|
324
340
|
export { Lightbox as default };
|
package/dist/styles.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
body:has(>.yarll__portal){overscroll-behavior:none}body:has(>.yarll__portal
|
|
1
|
+
body:has(>.yarll__portal){overscroll-behavior:none}body:has(>.yarll__portal:not(.yarll__no_scroll_lock)){height:100%;overflow:hidden;padding-right:var(--yarll__scrollbar-width,0)}body:has(>.yarll__portal:not(.yarll__no_scroll_lock)) .yarll_fixed{padding-right:var(--yarll__scrollbar-width,0)}.yarll__portal{align-items:center;display:flex;flex-direction:column;inset:0;justify-content:center;outline:none;overflow:hidden;overscroll-behavior:none;position:fixed;touch-action:none;-moz-user-select:none;user-select:none;-webkit-user-select:none;z-index:var(--yarll__portal_zindex,9999);-webkit-touch-callout:none;background-color:var(--yarll__backdrop_color,#000);color:var(--yarll__color,#fff);opacity:1;transition:var(--yarll__fade_transition,opacity .3s ease)}.yarll__portal_closed{opacity:0}.yarll__portal *{box-sizing:border-box}.yarll__carousel{align-self:stretch;flex:1;margin:var(--yarll__carousel_margin,16px);position:relative}.yarll__slide{align-items:center;display:flex;flex-direction:column;inset:0;justify-content:center;position:absolute}.yarll__slide[hidden]{display:none}.yarll__slide_image{display:block;flex:1;max-height:100%;max-width:100%;min-height:0;min-width:0;-o-object-fit:contain;object-fit:contain}.yarll__toolbar{align-items:center;display:flex;position:absolute;right:var(--yarll__toolbar_margin,8px);top:var(--yarll__toolbar_margin,8px);z-index:1}.yarll__toolbar_fixed{align-self:flex-end;margin-inline-end:var(--yarll__toolbar_margin,8px);position:static;z-index:unset}.yarll__toolbar_fixed,.yarll__toolbar_fixed+.yarll__carousel{margin-block-start:var(--yarll__toolbar_margin,8px)}.yarll__button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--yarll__button_background_color,transparent);border:0;color:var(--yarll__button_color,var(--yarll__button_color,hsla(0,0%,100%,.8)));cursor:pointer;filter:var(--yarll__button_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));margin:0;padding:var(--yarll__button_padding,8px)}.yarll__button:focus-visible{box-shadow:var(--yarll__button_focus_box_shadow,0 0 0 4px #fff);color:var(--yarll__button_color_active,#fff);outline:var(--yarll__button_focus_outline,6px double #000)}@supports not selector(:focus-visible){.yarll__button:focus{box-shadow:var(--yarll__button_focus_box_shadow,0 0 0 4px #fff);color:var(--yarll__button_color_active,#fff);outline:var(--yarll__button_focus_outline,6px double #000)}}@media (hover:hover){.yarll__button:focus-visible:hover,.yarll__button:focus:hover,.yarll__button:hover{color:var(--yarll__button_color_active,#fff)}}.yarll__button_next,.yarll__button_prev{padding:var(--yarll__navigation_button_padding,24px 8px);position:absolute}.yarll__button_prev{left:8px}.yarll__button_next{right:8px}.yarll__button:disabled{color:var(--yarll__button_color_disabled,var(--yarll__button_color_disabled,hsla(0,0%,100%,.4)));cursor:default}.yarll__icon{display:block;height:var(--yarll__icon_size,32px);width:var(--yarll__icon_size,32px)}
|