yet-another-react-lightbox-lite 1.0.0 → 1.1.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 +90 -4
- package/dist/index.d.ts +41 -1
- package/dist/index.js +21 -11
- 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
|
|
@@ -203,6 +203,32 @@ Custom UI labels / translations.
|
|
|
203
203
|
/>
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
### controller
|
|
207
|
+
|
|
208
|
+
Type: `object`
|
|
209
|
+
|
|
210
|
+
Controller settings.
|
|
211
|
+
|
|
212
|
+
- `closeOnPullUp` - if `true`, close the lightbox on pull-up gesture (default:
|
|
213
|
+
`true`)
|
|
214
|
+
- `closeOnPullDown` - if `true`, close the lightbox on pull-down gesture
|
|
215
|
+
(default: `true`)
|
|
216
|
+
- `closeOnBackdropClick` - if `true`, close the lightbox when the backdrop is
|
|
217
|
+
clicked (default: `true`)
|
|
218
|
+
|
|
219
|
+
Usage example:
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<Lightbox
|
|
223
|
+
// ...
|
|
224
|
+
controller={{
|
|
225
|
+
closeOnPullUp: false,
|
|
226
|
+
closeOnPullDown: false,
|
|
227
|
+
closeOnBackdropClick: false,
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
206
232
|
### render
|
|
207
233
|
|
|
208
234
|
Type: `object`
|
|
@@ -315,6 +341,41 @@ Render custom `Next` icon.
|
|
|
315
341
|
|
|
316
342
|
Render custom `Close` icon.
|
|
317
343
|
|
|
344
|
+
### styles
|
|
345
|
+
|
|
346
|
+
Type: `{ [key in Slot]?: SlotCSSProperties }`
|
|
347
|
+
|
|
348
|
+
Customization slots styles allow you to specify custom CSS styles or override
|
|
349
|
+
`--yarll__*` CSS variables by passing your custom styles through to the
|
|
350
|
+
corresponding lightbox elements.
|
|
351
|
+
|
|
352
|
+
Supported customization slots:
|
|
353
|
+
|
|
354
|
+
- `portal` - lightbox portal (root)
|
|
355
|
+
- `carousel` - lightbox carousel
|
|
356
|
+
- `slide` - lightbox slide
|
|
357
|
+
- `image` - lightbox slide image
|
|
358
|
+
- `button` - lightbox button
|
|
359
|
+
- `icon` - lightbox icon
|
|
360
|
+
|
|
361
|
+
Usage example:
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
<Lightbox
|
|
365
|
+
// ...
|
|
366
|
+
styles={{
|
|
367
|
+
portal: { "--yarll__backdrop_color": "rgba(0, 0, 0, 0.6)" },
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### className
|
|
373
|
+
|
|
374
|
+
Type: `string`
|
|
375
|
+
|
|
376
|
+
CSS class of the lightbox root element. You can use this class name to provide
|
|
377
|
+
module-scoped style overrides.
|
|
378
|
+
|
|
318
379
|
## Custom Slide Attributes
|
|
319
380
|
|
|
320
381
|
You can add custom slide attributes with the following module augmentation.
|
|
@@ -420,6 +481,31 @@ export default function App() {
|
|
|
420
481
|
}
|
|
421
482
|
```
|
|
422
483
|
|
|
484
|
+
## Body Scroll Lock
|
|
485
|
+
|
|
486
|
+
By default, the lightbox hides the browser window scrollbar and prevents
|
|
487
|
+
document `<body>` from scrolling underneath the lightbox by assigning the
|
|
488
|
+
`height: 100%; overflow: hidden;` styles to the document `<body>` element.
|
|
489
|
+
|
|
490
|
+
If this behavior causes undesired side effects in your case, and you prefer not
|
|
491
|
+
to use this feature, you can turn it off by assigning the
|
|
492
|
+
`yarll__no_scroll_lock` class to the lightbox.
|
|
493
|
+
|
|
494
|
+
```tsx
|
|
495
|
+
<Lightbox
|
|
496
|
+
// ..
|
|
497
|
+
className="yarll__no_scroll_lock"
|
|
498
|
+
/>
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
However, if you keep the body scroll lock feature on, you may notice a visual
|
|
502
|
+
layout shift of some fixed-positioned page elements when the lightbox opens. To
|
|
503
|
+
address this, you can assign the `yarll__fixed` CSS class to your
|
|
504
|
+
fixed-positioned elements to keep them in place. Please note that the
|
|
505
|
+
fixed-positioned element container should not have its own border or padding
|
|
506
|
+
styles. If that's the case, you can always add an extra wrapper that just
|
|
507
|
+
defines the fixed position without visual styles.
|
|
508
|
+
|
|
423
509
|
## License
|
|
424
510
|
|
|
425
511
|
MIT © 2024 [Igor Danchenko](https://github.com/igordanchenko)
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ interface LightboxProps {
|
|
|
13
13
|
labels?: Labels;
|
|
14
14
|
/** custom render functions */
|
|
15
15
|
render?: Render;
|
|
16
|
+
/** controller settings */
|
|
17
|
+
controller?: Controller;
|
|
18
|
+
/** customization slots styles */
|
|
19
|
+
styles?: SlotStyles;
|
|
20
|
+
/** CSS class of the lightbox root element */
|
|
21
|
+
className?: string;
|
|
16
22
|
}
|
|
17
23
|
/** Slide */
|
|
18
24
|
type Slide = SlideTypes[SlideTypeKey];
|
|
@@ -88,6 +94,40 @@ interface RenderSlideProps {
|
|
|
88
94
|
/** if `true`, the slide is the current slide in the viewport */
|
|
89
95
|
current: boolean;
|
|
90
96
|
}
|
|
97
|
+
/** Controller settings */
|
|
98
|
+
type Controller = {
|
|
99
|
+
/** if `true`, close the lightbox on pull-up gesture (default: `true`) */
|
|
100
|
+
closeOnPullUp?: boolean;
|
|
101
|
+
/** if `true`, close the lightbox on pull-down gesture (default: `true`) */
|
|
102
|
+
closeOnPullDown?: boolean;
|
|
103
|
+
/** if `true`, close the lightbox when the backdrop is clicked (default: `true`) */
|
|
104
|
+
closeOnBackdropClick?: boolean;
|
|
105
|
+
};
|
|
106
|
+
/** Customization slots */
|
|
107
|
+
interface SlotType {
|
|
108
|
+
/** lightbox portal (root) customization slot */
|
|
109
|
+
portal: "portal";
|
|
110
|
+
/** lightbox carousel customization slot */
|
|
111
|
+
carousel: "carousel";
|
|
112
|
+
/** lightbox slide customization slot */
|
|
113
|
+
slide: "slide";
|
|
114
|
+
/** lightbox slide image customization slot */
|
|
115
|
+
image: "image";
|
|
116
|
+
/** lightbox button customization slot */
|
|
117
|
+
button: "button";
|
|
118
|
+
/** lightbox icon customization slot */
|
|
119
|
+
icon: "icon";
|
|
120
|
+
}
|
|
121
|
+
/** Customization slots */
|
|
122
|
+
type Slot = SlotType[keyof SlotType];
|
|
123
|
+
/** Customization slot CSS properties */
|
|
124
|
+
interface SlotCSSProperties extends React.CSSProperties {
|
|
125
|
+
[key: `--yarll__${string}`]: string | number;
|
|
126
|
+
}
|
|
127
|
+
/** Customization slots styles */
|
|
128
|
+
type SlotStyles = {
|
|
129
|
+
[key in Slot]?: SlotCSSProperties;
|
|
130
|
+
};
|
|
91
131
|
/** Rect */
|
|
92
132
|
type Rect = {
|
|
93
133
|
width: number;
|
|
@@ -100,4 +140,4 @@ type RenderFunction<T = void> = [T] extends [void] ? () => React.ReactNode : (pr
|
|
|
100
140
|
|
|
101
141
|
declare function Lightbox({ slides, index, setIndex, ...rest }: LightboxProps): react_jsx_runtime.JSX.Element | null;
|
|
102
142
|
|
|
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 };
|
|
143
|
+
export { type Callback, type Controller, 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, Lightbox as default };
|
package/dist/index.js
CHANGED
|
@@ -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) {
|
|
@@ -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,8 +191,9 @@ function useSensors() {
|
|
|
183
191
|
next();
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
|
-
else if ((deltaY > 50 && deltaY > 1.2 * deltaX) ||
|
|
187
|
-
(
|
|
194
|
+
else if ((deltaY > 50 && deltaY > 1.2 * deltaX && ((closeOnPullUp && dy < 0) || (closeOnPullDown && dy > 0))) ||
|
|
195
|
+
(closeOnBackdropClick &&
|
|
196
|
+
activePointer.current.target instanceof HTMLElement &&
|
|
188
197
|
activePointer.current.target.className.split(" ").includes(cssClass("slide")))) {
|
|
189
198
|
close();
|
|
190
199
|
}
|
|
@@ -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
|
}
|
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__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_close{position:absolute;right:8px;top:8px}.yarll__button_prev{left:8px}.yarll__button_next{right:8px}.yarll__button_next,.yarll__button_prev{padding:var(--yarll__navigation_button_padding,24px 8px);position:absolute}.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)}
|