yet-another-react-lightbox-lite 1.1.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 CHANGED
@@ -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,37 @@ 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
+
206
237
  ### controller
207
238
 
208
239
  Type: `object`
@@ -220,12 +251,12 @@ Usage example:
220
251
 
221
252
  ```tsx
222
253
  <Lightbox
223
- // ...
224
254
  controller={{
225
255
  closeOnPullUp: false,
226
256
  closeOnPullDown: false,
227
257
  closeOnBackdropClick: false,
228
258
  }}
259
+ // ...
229
260
  />
230
261
  ```
231
262
 
@@ -294,7 +325,6 @@ slides counter.
294
325
 
295
326
  ```tsx
296
327
  <Lightbox
297
- // ...
298
328
  render={{
299
329
  controls: () =>
300
330
  index !== undefined && (
@@ -303,29 +333,7 @@ slides counter.
303
333
  </div>
304
334
  ),
305
335
  }}
306
- />
307
- ```
308
-
309
- You can also use the `render.controls` render function to add custom buttons to
310
- the toolbar.
311
-
312
- ```tsx
313
- <Lightbox
314
336
  // ...
315
- render={{
316
- controls: () => (
317
- <button
318
- type="button"
319
- className="yarll__button"
320
- style={{ position: "absolute", top: 8, right: 64 }}
321
- onClick={() => {
322
- // ...
323
- }}
324
- >
325
- <ButtonIcon />
326
- </button>
327
- ),
328
- }}
329
337
  />
330
338
  ```
331
339
 
@@ -355,6 +363,7 @@ Supported customization slots:
355
363
  - `carousel` - lightbox carousel
356
364
  - `slide` - lightbox slide
357
365
  - `image` - lightbox slide image
366
+ - `toolbar` - lightbox toolbar
358
367
  - `button` - lightbox button
359
368
  - `icon` - lightbox icon
360
369
 
@@ -362,10 +371,10 @@ Usage example:
362
371
 
363
372
  ```tsx
364
373
  <Lightbox
365
- // ...
366
374
  styles={{
367
375
  portal: { "--yarll__backdrop_color": "rgba(0, 0, 0, 0.6)" },
368
376
  }}
377
+ // ...
369
378
  />
370
379
  ```
371
380
 
@@ -493,8 +502,8 @@ to use this feature, you can turn it off by assigning the
493
502
 
494
503
  ```tsx
495
504
  <Lightbox
496
- // ..
497
505
  className="yarll__no_scroll_lock"
506
+ // ...
498
507
  />
499
508
  ```
500
509
 
package/dist/index.d.ts CHANGED
@@ -13,8 +13,10 @@ interface LightboxProps {
13
13
  labels?: Labels;
14
14
  /** custom render functions */
15
15
  render?: Render;
16
+ /** toolbar settings */
17
+ toolbar?: ToolbarSettings;
16
18
  /** controller settings */
17
- controller?: Controller;
19
+ controller?: ControllerSettings;
18
20
  /** customization slots styles */
19
21
  styles?: SlotStyles;
20
22
  /** CSS class of the lightbox root element */
@@ -94,15 +96,22 @@ interface RenderSlideProps {
94
96
  /** if `true`, the slide is the current slide in the viewport */
95
97
  current: boolean;
96
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
+ }
97
106
  /** Controller settings */
98
- type Controller = {
107
+ interface ControllerSettings {
99
108
  /** if `true`, close the lightbox on pull-up gesture (default: `true`) */
100
109
  closeOnPullUp?: boolean;
101
110
  /** if `true`, close the lightbox on pull-down gesture (default: `true`) */
102
111
  closeOnPullDown?: boolean;
103
112
  /** if `true`, close the lightbox when the backdrop is clicked (default: `true`) */
104
113
  closeOnBackdropClick?: boolean;
105
- };
114
+ }
106
115
  /** Customization slots */
107
116
  interface SlotType {
108
117
  /** lightbox portal (root) customization slot */
@@ -113,6 +122,8 @@ interface SlotType {
113
122
  slide: "slide";
114
123
  /** lightbox slide image customization slot */
115
124
  image: "image";
125
+ /** lightbox toolbar customization slot */
126
+ toolbar: "toolbar";
116
127
  /** lightbox button customization slot */
117
128
  button: "button";
118
129
  /** lightbox icon customization slot */
@@ -140,4 +151,4 @@ type RenderFunction<T = void> = [T] extends [void] ? () => React.ReactNode : (pr
140
151
 
141
152
  declare function Lightbox({ slides, index, setIndex, ...rest }: LightboxProps): react_jsx_runtime.JSX.Element | null;
142
153
 
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 };
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__";
@@ -137,9 +137,9 @@ const Next = createIcon("Next", jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58
137
137
  const Previous = createIcon("Previous", jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }));
138
138
 
139
139
  function Navigation() {
140
- const { slides, index, render: { iconPrev, iconNext, iconClose, controls } = {} } = useLightboxContext();
141
- const { prev, next, close } = 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 })] })), jsx(Button, { label: "Close", icon: Close, renderIcon: iconClose, onClick: close, className: cssClass("button_close") }), controls?.()] }));
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?.()] }));
143
143
  }
144
144
 
145
145
  function useSensors() {
@@ -193,8 +193,8 @@ function useSensors() {
193
193
  }
194
194
  else if ((deltaY > 50 && deltaY > 1.2 * deltaX && ((closeOnPullUp && dy < 0) || (closeOnPullDown && dy > 0))) ||
195
195
  (closeOnBackdropClick &&
196
- activePointer.current.target instanceof HTMLElement &&
197
- activePointer.current.target.className.split(" ").includes(cssClass("slide")))) {
196
+ activePointer.current.target instanceof Element &&
197
+ Array.from(activePointer.current.target.classList).some((className) => [cssClass("slide"), cssClass("portal")].includes(className)))) {
198
198
  close();
199
199
  }
200
200
  activePointer.current = null;
@@ -325,10 +325,16 @@ function Portal({ children }) {
325
325
  : null;
326
326
  }
327
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
+
328
334
  function Lightbox({ slides, index, setIndex, ...rest }) {
329
335
  if (!Array.isArray(slides) || index === undefined || index < 0 || index >= slides.length)
330
336
  return null;
331
- 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, {})] }) }) }));
332
338
  }
333
339
 
334
340
  export { Lightbox as default };
package/dist/styles.css CHANGED
@@ -1 +1 @@
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)}
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)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yet-another-react-lightbox-lite",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Lightweight React lightbox component",
5
5
  "author": "Igor Danchenko",
6
6
  "license": "MIT",