yet-another-react-lightbox-lite 1.0.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/LICENSE +20 -0
- package/README.md +425 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +324 -0
- package/dist/styles.css +1 -0
- package/dist/styles.css.d.ts +2 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Igor Danchenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# Yet Another React Lightbox Lite
|
|
2
|
+
|
|
3
|
+
Lightweight React lightbox component. This is a trimmed-down version of the
|
|
4
|
+
[yet-another-react-lightbox](https://github.com/igordanchenko/yet-another-react-lightbox)
|
|
5
|
+
that provides essential lightbox features and slick UX with just 2.8KB bundle
|
|
6
|
+
size.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
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)
|
|
13
|
+
|
|
14
|
+
- **Built for React:** works with React 18+
|
|
15
|
+
- **UX:** supports keyboard, mouse, touchpad and touchscreen navigation
|
|
16
|
+
- **Performance:** preloads a fixed number of images without compromising
|
|
17
|
+
performance or UX
|
|
18
|
+
- **Responsive:** responsive images with automatic resolution switching are
|
|
19
|
+
supported out of the box
|
|
20
|
+
- **Customization:** customize any UI element or add your own custom slides
|
|
21
|
+
- **No bloat:** supports only essential lightbox features
|
|
22
|
+
- **TypeScript:** type definitions come built-in in the package
|
|
23
|
+
|
|
24
|
+
## Documentation
|
|
25
|
+
|
|
26
|
+
[https://github.com/igordanchenko/yet-another-react-lightbox-lite](https://github.com/igordanchenko/yet-another-react-lightbox-lite)
|
|
27
|
+
|
|
28
|
+
## Live Demo
|
|
29
|
+
|
|
30
|
+
[https://stackblitz.com/edit/yet-another-react-lightbox-lite](https://stackblitz.com/edit/yet-another-react-lightbox-lite)
|
|
31
|
+
|
|
32
|
+
## Changelog
|
|
33
|
+
|
|
34
|
+
[https://github.com/igordanchenko/yet-another-react-lightbox-lite/releases](https://github.com/igordanchenko/yet-another-react-lightbox-lite/releases)
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- React 18+
|
|
39
|
+
- Node 18+
|
|
40
|
+
- modern ESM-compatible bundler
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
npm install yet-another-react-lightbox-lite
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Minimal Setup Example
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { useState } from "react";
|
|
52
|
+
import Lightbox from "yet-another-react-lightbox-lite";
|
|
53
|
+
import "yet-another-react-lightbox-lite/styles.css";
|
|
54
|
+
|
|
55
|
+
export default function App() {
|
|
56
|
+
const [index, setIndex] = useState<number>();
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
<button type="button" onClick={() => setIndex(0)}>
|
|
61
|
+
Open Lightbox
|
|
62
|
+
</button>
|
|
63
|
+
|
|
64
|
+
<Lightbox
|
|
65
|
+
slides={[
|
|
66
|
+
{ src: "/image1.jpg" },
|
|
67
|
+
{ src: "/image2.jpg" },
|
|
68
|
+
{ src: "/image3.jpg" },
|
|
69
|
+
]}
|
|
70
|
+
index={index}
|
|
71
|
+
setIndex={setIndex}
|
|
72
|
+
/>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Responsive Images
|
|
79
|
+
|
|
80
|
+
To utilize responsive images with automatic resolution switching, provide
|
|
81
|
+
`srcset` images in the slide `srcSet` array.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<Lightbox
|
|
85
|
+
slides={[
|
|
86
|
+
{
|
|
87
|
+
src: "/image1x3840.jpg",
|
|
88
|
+
srcSet: [
|
|
89
|
+
{ src: "/image1x320.jpg", width: 320, height: 213 },
|
|
90
|
+
{ src: "/image1x640.jpg", width: 640, height: 427 },
|
|
91
|
+
{ src: "/image1x1200.jpg", width: 1200, height: 800 },
|
|
92
|
+
{ src: "/image1x2048.jpg", width: 2048, height: 1365 },
|
|
93
|
+
{ src: "/image1x3840.jpg", width: 3840, height: 2560 },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
// ...
|
|
97
|
+
]}
|
|
98
|
+
// ...
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Next.js Image
|
|
103
|
+
|
|
104
|
+
If your project is based on [Next.js](https://nextjs.org/), you may want to take
|
|
105
|
+
advantage of the
|
|
106
|
+
[next/image](https://nextjs.org/docs/pages/api-reference/components/image)
|
|
107
|
+
component. The `next/image` component provides a more efficient way to handle
|
|
108
|
+
images in your Next.js project. You can replace the standard `<img>` element
|
|
109
|
+
with `next/image` with the following `render.slide` render function.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<Lightbox
|
|
113
|
+
// ...
|
|
114
|
+
render={{
|
|
115
|
+
slide: ({ slide, rect }) => {
|
|
116
|
+
const width =
|
|
117
|
+
slide.width && slide.height
|
|
118
|
+
? Math.round(
|
|
119
|
+
Math.min(rect.width, (rect.height / slide.height) * slide.width),
|
|
120
|
+
)
|
|
121
|
+
: rect.width;
|
|
122
|
+
|
|
123
|
+
const height =
|
|
124
|
+
slide.width && slide.height
|
|
125
|
+
? Math.round(
|
|
126
|
+
Math.min(rect.height, (rect.width / slide.width) * slide.height),
|
|
127
|
+
)
|
|
128
|
+
: rect.height;
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Image
|
|
132
|
+
src={slide.src}
|
|
133
|
+
alt={slide.alt || ""}
|
|
134
|
+
width={width}
|
|
135
|
+
height={height}
|
|
136
|
+
loading="eager"
|
|
137
|
+
draggable={false}
|
|
138
|
+
blurDataURL={(slide as any).blurDataURL}
|
|
139
|
+
style={{
|
|
140
|
+
minWidth: 0,
|
|
141
|
+
minHeight: 0,
|
|
142
|
+
maxWidth: "100%",
|
|
143
|
+
maxHeight: "100%",
|
|
144
|
+
objectFit: "contain",
|
|
145
|
+
}}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API
|
|
154
|
+
|
|
155
|
+
Yet Another React Lightbox Lite comes with CSS stylesheet that needs to be
|
|
156
|
+
imported in your app.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import "yet-another-react-lightbox-lite/styles.css";
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The lightbox component accepts the following props.
|
|
163
|
+
|
|
164
|
+
### slides
|
|
165
|
+
|
|
166
|
+
Type: `Slide[]`
|
|
167
|
+
|
|
168
|
+
An array of slides to display in the lightbox. This prop is required. By
|
|
169
|
+
default, the lightbox supports only image slides. You can add support for custom
|
|
170
|
+
slides through a custom render function (see example below).
|
|
171
|
+
|
|
172
|
+
Image slide props:
|
|
173
|
+
|
|
174
|
+
- `src` - image source (required)
|
|
175
|
+
- `alt` - image `alt` attribute
|
|
176
|
+
|
|
177
|
+
### index
|
|
178
|
+
|
|
179
|
+
Type: `number | undefined`
|
|
180
|
+
|
|
181
|
+
Current slide index. This prop is required.
|
|
182
|
+
|
|
183
|
+
### setIndex
|
|
184
|
+
|
|
185
|
+
Type: `(index: number | undefined) => void`
|
|
186
|
+
|
|
187
|
+
A callback to update current slide index state. This prop is required.
|
|
188
|
+
|
|
189
|
+
### labels
|
|
190
|
+
|
|
191
|
+
Type: `{ [key: string]: string }`
|
|
192
|
+
|
|
193
|
+
Custom UI labels / translations.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
<Lightbox
|
|
197
|
+
labels={{
|
|
198
|
+
Previous: t("Previous"),
|
|
199
|
+
Next: t("Next"),
|
|
200
|
+
Close: t("Close"),
|
|
201
|
+
}}
|
|
202
|
+
// ...
|
|
203
|
+
/>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### render
|
|
207
|
+
|
|
208
|
+
Type: `object`
|
|
209
|
+
|
|
210
|
+
An object providing custom render functions.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<Lightbox
|
|
214
|
+
render={{
|
|
215
|
+
slide: ({ slide, rect, current }) => (
|
|
216
|
+
<CustomSlide {...{ slide, rect, current }} />
|
|
217
|
+
),
|
|
218
|
+
slideHeader: ({ slide, rect, current }) => (
|
|
219
|
+
<SlideHeader {...{ slide, rect, current }} />
|
|
220
|
+
),
|
|
221
|
+
slideFooter: ({ slide, rect, current }) => (
|
|
222
|
+
<SlideFooter {...{ slide, rect, current }} />
|
|
223
|
+
),
|
|
224
|
+
controls: () => <CustomControls />,
|
|
225
|
+
iconPrev: () => <IconPrev />,
|
|
226
|
+
iconNext: () => <IconNext />,
|
|
227
|
+
iconClose: () => <IconClose />,
|
|
228
|
+
}}
|
|
229
|
+
// ...
|
|
230
|
+
/>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### slide: ({ slide, rect, current }) => ReactNode
|
|
234
|
+
|
|
235
|
+
Render custom slide type, or override the default image slide implementation.
|
|
236
|
+
|
|
237
|
+
#### slideHeader: ({ slide, rect, current }) => ReactNode
|
|
238
|
+
|
|
239
|
+
Render custom elements above each slide.
|
|
240
|
+
|
|
241
|
+
#### slideFooter: ({ slide, rect, current }) => ReactNode
|
|
242
|
+
|
|
243
|
+
Render custom elements below or over each slide. By default, the content is
|
|
244
|
+
rendered right under the slide. Alternatively, you can use
|
|
245
|
+
`position: "absolute"` to position the extra elements relative to the slide.
|
|
246
|
+
|
|
247
|
+
For example, you can use the `slideFooter` render function to add slides
|
|
248
|
+
descriptions.
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
<Lightbox
|
|
252
|
+
render={{
|
|
253
|
+
slideFooter: ({ slide }) => (
|
|
254
|
+
<div style={{ marginBlockStart: 16 }}>{slide.description}</div>
|
|
255
|
+
),
|
|
256
|
+
}}
|
|
257
|
+
// ...
|
|
258
|
+
/>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### controls: () => ReactNode
|
|
262
|
+
|
|
263
|
+
Render custom controls or additional elements in the lightbox (use absolute
|
|
264
|
+
positioning).
|
|
265
|
+
|
|
266
|
+
For example, you can use the `render.controls` render function to implement
|
|
267
|
+
slides counter.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<Lightbox
|
|
271
|
+
// ...
|
|
272
|
+
render={{
|
|
273
|
+
controls: () =>
|
|
274
|
+
index !== undefined && (
|
|
275
|
+
<div style={{ position: "absolute", top: 16, left: 16 }}>
|
|
276
|
+
{index + 1} of {slides.length}
|
|
277
|
+
</div>
|
|
278
|
+
),
|
|
279
|
+
}}
|
|
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
|
+
// ...
|
|
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
|
+
/>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### iconPrev: () => ReactNode
|
|
307
|
+
|
|
308
|
+
Render custom `Previous` icon.
|
|
309
|
+
|
|
310
|
+
#### iconNext: () => ReactNode
|
|
311
|
+
|
|
312
|
+
Render custom `Next` icon.
|
|
313
|
+
|
|
314
|
+
#### iconClose: () => ReactNode
|
|
315
|
+
|
|
316
|
+
Render custom `Close` icon.
|
|
317
|
+
|
|
318
|
+
## Custom Slide Attributes
|
|
319
|
+
|
|
320
|
+
You can add custom slide attributes with the following module augmentation.
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
declare module "yet-another-react-lightbox-lite" {
|
|
324
|
+
interface GenericSlide {
|
|
325
|
+
description?: string;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Custom Slides
|
|
331
|
+
|
|
332
|
+
You can add custom slide types through module augmentation and render them with
|
|
333
|
+
the `render.slide` render function.
|
|
334
|
+
|
|
335
|
+
Here is an example demonstrating video slides implementation.
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
declare module "yet-another-react-lightbox-lite" {
|
|
339
|
+
interface SlideVideo extends GenericSlide {
|
|
340
|
+
type: "video";
|
|
341
|
+
src: string;
|
|
342
|
+
poster: string;
|
|
343
|
+
width: number;
|
|
344
|
+
height: number;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
interface SlideTypes {
|
|
348
|
+
video: SlideVideo;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ...
|
|
353
|
+
|
|
354
|
+
<Lightbox
|
|
355
|
+
slides={[
|
|
356
|
+
{
|
|
357
|
+
type: "video",
|
|
358
|
+
src: "/media/video.mp4",
|
|
359
|
+
poster: "/media/poster.jpg",
|
|
360
|
+
width: 1280,
|
|
361
|
+
height: 720,
|
|
362
|
+
},
|
|
363
|
+
]}
|
|
364
|
+
render={{
|
|
365
|
+
slide: ({ slide }) =>
|
|
366
|
+
slide.type === "video" ? (
|
|
367
|
+
<video
|
|
368
|
+
controls
|
|
369
|
+
playsInline
|
|
370
|
+
poster={slide.poster}
|
|
371
|
+
width={slide.width}
|
|
372
|
+
height={slide.height}
|
|
373
|
+
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
|
374
|
+
>
|
|
375
|
+
<source type="video/mp4" src={slide.src} />
|
|
376
|
+
</video>
|
|
377
|
+
) : undefined,
|
|
378
|
+
}}
|
|
379
|
+
// ...
|
|
380
|
+
/>;
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Code Splitting (Suspense)
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
// Lightbox.tsx
|
|
387
|
+
import LightboxComponent, {
|
|
388
|
+
LightboxProps,
|
|
389
|
+
} from "yet-another-react-lightbox-lite";
|
|
390
|
+
import "yet-another-react-lightbox-lite/styles.css";
|
|
391
|
+
|
|
392
|
+
export default function Lightbox(props: LightboxProps) {
|
|
393
|
+
return <LightboxComponent {...props} />;
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
// App.tsx
|
|
399
|
+
import { lazy, Suspense, useState } from "react";
|
|
400
|
+
import slides from "./slides";
|
|
401
|
+
|
|
402
|
+
const Lightbox = lazy(() => import("./Lightbox"));
|
|
403
|
+
|
|
404
|
+
export default function App() {
|
|
405
|
+
const [index, setIndex] = useState<number>();
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<>
|
|
409
|
+
<button type="button" onClick={() => setIndex(0)}>
|
|
410
|
+
Open Lightbox
|
|
411
|
+
</button>
|
|
412
|
+
|
|
413
|
+
{index !== undefined && (
|
|
414
|
+
<Suspense>
|
|
415
|
+
<Lightbox slides={slides} index={index} setIndex={setIndex} />
|
|
416
|
+
</Suspense>
|
|
417
|
+
)}
|
|
418
|
+
</>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## License
|
|
424
|
+
|
|
425
|
+
MIT © 2024 [Igor Danchenko](https://github.com/igordanchenko)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/** Lightbox props */
|
|
5
|
+
interface LightboxProps {
|
|
6
|
+
/** slides to display in the lightbox */
|
|
7
|
+
slides: Slide[];
|
|
8
|
+
/** slide index */
|
|
9
|
+
index: number | undefined;
|
|
10
|
+
/** slide index change callback */
|
|
11
|
+
setIndex: (index: number | undefined) => void;
|
|
12
|
+
/** custom UI labels / translations */
|
|
13
|
+
labels?: Labels;
|
|
14
|
+
/** custom render functions */
|
|
15
|
+
render?: Render;
|
|
16
|
+
}
|
|
17
|
+
/** Slide */
|
|
18
|
+
type Slide = SlideTypes[SlideTypeKey];
|
|
19
|
+
/** Supported slide types */
|
|
20
|
+
interface SlideTypes {
|
|
21
|
+
/** image slide type */
|
|
22
|
+
image: SlideImage;
|
|
23
|
+
}
|
|
24
|
+
/** Slide type key */
|
|
25
|
+
type SlideTypeKey = keyof SlideTypes;
|
|
26
|
+
/** Generic slide */
|
|
27
|
+
interface GenericSlide {
|
|
28
|
+
/** slide key */
|
|
29
|
+
key?: React.Key;
|
|
30
|
+
/** slide type */
|
|
31
|
+
type?: SlideTypeKey;
|
|
32
|
+
}
|
|
33
|
+
/** Image slide properties */
|
|
34
|
+
interface SlideImage extends GenericSlide {
|
|
35
|
+
/** image slide type */
|
|
36
|
+
type?: "image";
|
|
37
|
+
/** image URL */
|
|
38
|
+
src: string;
|
|
39
|
+
/** image width in pixels */
|
|
40
|
+
width?: number;
|
|
41
|
+
/** image height in pixels */
|
|
42
|
+
height?: number;
|
|
43
|
+
/** image 'alt' attribute */
|
|
44
|
+
alt?: string;
|
|
45
|
+
/** alternative images to be passed to the 'srcSet' */
|
|
46
|
+
srcSet?: ImageSource[];
|
|
47
|
+
}
|
|
48
|
+
/** Image source */
|
|
49
|
+
interface ImageSource {
|
|
50
|
+
/** image URL */
|
|
51
|
+
src: string;
|
|
52
|
+
/** image width in pixels */
|
|
53
|
+
width: number;
|
|
54
|
+
/** image height in pixels */
|
|
55
|
+
height: number;
|
|
56
|
+
}
|
|
57
|
+
/** Custom UI labels / translations */
|
|
58
|
+
interface Labels {
|
|
59
|
+
Previous?: string;
|
|
60
|
+
Next?: string;
|
|
61
|
+
Close?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Label key */
|
|
64
|
+
type Label = keyof Labels;
|
|
65
|
+
/** Custom render functions. */
|
|
66
|
+
interface Render {
|
|
67
|
+
/** render custom slide type, or override the default image slide */
|
|
68
|
+
slide?: RenderFunction<RenderSlideProps>;
|
|
69
|
+
/** render custom elements above each slide */
|
|
70
|
+
slideHeader?: RenderFunction<RenderSlideProps>;
|
|
71
|
+
/** render custom elements below or over each slide */
|
|
72
|
+
slideFooter?: RenderFunction<RenderSlideProps>;
|
|
73
|
+
/** render custom controls or additional elements in the lightbox (use absolute positioning) */
|
|
74
|
+
controls?: RenderFunction;
|
|
75
|
+
/** render custom Prev icon */
|
|
76
|
+
iconPrev?: RenderFunction;
|
|
77
|
+
/** render custom Next icon */
|
|
78
|
+
iconNext?: RenderFunction;
|
|
79
|
+
/** render custom Close icon */
|
|
80
|
+
iconClose?: RenderFunction;
|
|
81
|
+
}
|
|
82
|
+
/** `render.slide` render function props */
|
|
83
|
+
interface RenderSlideProps {
|
|
84
|
+
/** slide */
|
|
85
|
+
slide: Slide;
|
|
86
|
+
/** slide */
|
|
87
|
+
rect: Rect;
|
|
88
|
+
/** if `true`, the slide is the current slide in the viewport */
|
|
89
|
+
current: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** Rect */
|
|
92
|
+
type Rect = {
|
|
93
|
+
width: number;
|
|
94
|
+
height: number;
|
|
95
|
+
};
|
|
96
|
+
/** Generic callback function */
|
|
97
|
+
type Callback<T = void> = () => T;
|
|
98
|
+
/** Render function */
|
|
99
|
+
type RenderFunction<T = void> = [T] extends [void] ? () => React.ReactNode : (props: T) => React.ReactNode;
|
|
100
|
+
|
|
101
|
+
declare function Lightbox({ slides, index, setIndex, ...rest }: LightboxProps): react_jsx_runtime.JSX.Element | null;
|
|
102
|
+
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { useContext, createContext, useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
|
3
|
+
import { flushSync, createPortal } from 'react-dom';
|
|
4
|
+
|
|
5
|
+
const cssPrefix = "yarll__";
|
|
6
|
+
function cssClass(name) {
|
|
7
|
+
return `${cssPrefix}${name}`;
|
|
8
|
+
}
|
|
9
|
+
function cssVar(name) {
|
|
10
|
+
return `--${cssPrefix}${name}`;
|
|
11
|
+
}
|
|
12
|
+
function clsx(...classes) {
|
|
13
|
+
return [...classes].filter(Boolean).join(" ");
|
|
14
|
+
}
|
|
15
|
+
function transition(callback) {
|
|
16
|
+
if (document.startViewTransition) {
|
|
17
|
+
document.startViewTransition(() => {
|
|
18
|
+
flushSync(callback);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
callback();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function translateLabel(labels, label) {
|
|
26
|
+
return labels?.[label] ?? label;
|
|
27
|
+
}
|
|
28
|
+
function makeUseContext(context) {
|
|
29
|
+
return () => {
|
|
30
|
+
const ctx = useContext(context);
|
|
31
|
+
if (!ctx)
|
|
32
|
+
throw new Error();
|
|
33
|
+
return ctx;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const LightboxContext = createContext(null);
|
|
38
|
+
const useLightboxContext = makeUseContext(LightboxContext);
|
|
39
|
+
function LightboxContextProvider({ children, ...props }) {
|
|
40
|
+
return jsx(LightboxContext.Provider, { value: props, children: children });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ImageSlide({ slide, rect }) {
|
|
44
|
+
const { width, height } = slide.srcSet?.[0] ?? slide;
|
|
45
|
+
const imageAspectRatio = width && height ? width / height : undefined;
|
|
46
|
+
const srcSet = slide.srcSet
|
|
47
|
+
?.sort((a, b) => a.width - b.width)
|
|
48
|
+
.map((image) => `${image.src} ${image.width}w`)
|
|
49
|
+
.join(", ");
|
|
50
|
+
const sizes = imageAspectRatio
|
|
51
|
+
? `${imageAspectRatio < rect.width / rect.height ? Math.round(imageAspectRatio * rect.height) : rect.width}px`
|
|
52
|
+
: undefined;
|
|
53
|
+
return (jsx("img", { draggable: false, className: cssClass("slide_image"), srcSet: srcSet, sizes: sizes, src: slide.src, alt: slide.alt }));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Carousel() {
|
|
57
|
+
const { slides, index, render: { slide: renderSlide, slideHeader, slideFooter } = {} } = useLightboxContext();
|
|
58
|
+
const [rect, setRect] = useState();
|
|
59
|
+
const observer = useRef();
|
|
60
|
+
const handleRef = useCallback((node) => {
|
|
61
|
+
observer.current?.disconnect();
|
|
62
|
+
observer.current = undefined;
|
|
63
|
+
const updateRect = () => setRect(node ? { width: node.clientWidth, height: node.clientHeight } : undefined);
|
|
64
|
+
if (node && typeof ResizeObserver !== "undefined") {
|
|
65
|
+
observer.current = new ResizeObserver(updateRect);
|
|
66
|
+
observer.current.observe(node);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
updateRect();
|
|
70
|
+
}
|
|
71
|
+
}, []);
|
|
72
|
+
return (jsx("div", { ref: handleRef, className: cssClass("carousel"), children: rect &&
|
|
73
|
+
Array.from({ length: 5 }).map((_, i) => {
|
|
74
|
+
const slideIndex = index - 2 + i;
|
|
75
|
+
if (slideIndex < 0 || slideIndex >= slides.length)
|
|
76
|
+
return null;
|
|
77
|
+
const slide = slides[slideIndex];
|
|
78
|
+
const current = slideIndex === index;
|
|
79
|
+
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
|
+
}) }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const ControllerContext = createContext(null);
|
|
85
|
+
const useController = makeUseContext(ControllerContext);
|
|
86
|
+
function Controller({ setIndex, children }) {
|
|
87
|
+
const { slides, index } = useLightboxContext();
|
|
88
|
+
const exitHooks = useRef([]);
|
|
89
|
+
const context = useMemo(() => {
|
|
90
|
+
const prev = () => {
|
|
91
|
+
if (index > 0)
|
|
92
|
+
transition(() => setIndex(index - 1));
|
|
93
|
+
};
|
|
94
|
+
const next = () => {
|
|
95
|
+
if (index < slides.length - 1)
|
|
96
|
+
transition(() => setIndex(index + 1));
|
|
97
|
+
};
|
|
98
|
+
const close = () => {
|
|
99
|
+
Promise.all(exitHooks.current.map((hook) => hook()))
|
|
100
|
+
.catch(() => { })
|
|
101
|
+
.then(() => {
|
|
102
|
+
exitHooks.current = [];
|
|
103
|
+
setIndex(-1);
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const addExitHook = (hook) => {
|
|
107
|
+
exitHooks.current.push(hook);
|
|
108
|
+
return () => {
|
|
109
|
+
exitHooks.current.splice(exitHooks.current.indexOf(hook), 1);
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
return { prev, next, close, addExitHook };
|
|
113
|
+
}, [slides.length, index, setIndex]);
|
|
114
|
+
return jsx(ControllerContext.Provider, { value: context, children: children });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function Button({ icon: Icon, renderIcon, label, onClick, disabled, className }) {
|
|
118
|
+
const { labels } = useLightboxContext();
|
|
119
|
+
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
|
+
}
|
|
122
|
+
|
|
123
|
+
function svgIcon(name, children) {
|
|
124
|
+
const icon = (props) => (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", focusable: "false", ...props, children: children }));
|
|
125
|
+
icon.displayName = name;
|
|
126
|
+
return icon;
|
|
127
|
+
}
|
|
128
|
+
function createIcon(name, glyph) {
|
|
129
|
+
return svgIcon(name, jsxs("g", { fill: "currentColor", children: [jsx("path", { d: "M0 0h24v24H0z", fill: "none" }), glyph] }));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const Close = createIcon("Close", jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }));
|
|
133
|
+
|
|
134
|
+
const Next = createIcon("Next", jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }));
|
|
135
|
+
|
|
136
|
+
const Previous = createIcon("Previous", jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }));
|
|
137
|
+
|
|
138
|
+
function Navigation() {
|
|
139
|
+
const { slides, index, render: { iconPrev, iconNext, iconClose, controls } = {} } = useLightboxContext();
|
|
140
|
+
const { prev, next, close } = useController();
|
|
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 })] })), jsx(Button, { label: "Close", icon: Close, renderIcon: iconClose, onClick: close, className: cssClass("button_close") }), controls?.()] }));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function useSensors() {
|
|
145
|
+
const wheelEvents = useRef([]);
|
|
146
|
+
const wheelCooldown = useRef(null);
|
|
147
|
+
const wheelCooldownMomentum = useRef(null);
|
|
148
|
+
const activePointer = useRef(null);
|
|
149
|
+
const { prev, next, close } = useController();
|
|
150
|
+
return useMemo(() => {
|
|
151
|
+
const onKeyDown = (event) => {
|
|
152
|
+
switch (event.key) {
|
|
153
|
+
case "Escape":
|
|
154
|
+
close();
|
|
155
|
+
break;
|
|
156
|
+
case "ArrowLeft":
|
|
157
|
+
prev();
|
|
158
|
+
break;
|
|
159
|
+
case "ArrowRight":
|
|
160
|
+
next();
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const onPointerDown = (event) => {
|
|
165
|
+
if (!activePointer.current) {
|
|
166
|
+
event.persist();
|
|
167
|
+
activePointer.current = event;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
activePointer.current = null;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const onPointerUp = (event) => {
|
|
174
|
+
if (event.pointerId === activePointer.current?.pointerId) {
|
|
175
|
+
const dx = event.clientX - activePointer.current.clientX;
|
|
176
|
+
const deltaX = Math.abs(dx);
|
|
177
|
+
const deltaY = Math.abs(event.clientY - activePointer.current.clientY);
|
|
178
|
+
if (deltaX > 50 && deltaX > 1.2 * deltaY) {
|
|
179
|
+
if (dx > 0) {
|
|
180
|
+
prev();
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
next();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if ((deltaY > 50 && deltaY > 1.2 * deltaX) ||
|
|
187
|
+
(activePointer.current.target instanceof HTMLElement &&
|
|
188
|
+
activePointer.current.target.className.split(" ").includes(cssClass("slide")))) {
|
|
189
|
+
close();
|
|
190
|
+
}
|
|
191
|
+
activePointer.current = null;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const onWheel = (event) => {
|
|
195
|
+
if (wheelCooldown.current && wheelCooldownMomentum.current) {
|
|
196
|
+
if (event.deltaX * wheelCooldownMomentum.current > 0 &&
|
|
197
|
+
(event.timeStamp <= wheelCooldown.current + 500 ||
|
|
198
|
+
(event.timeStamp <= wheelCooldown.current + 1000 &&
|
|
199
|
+
Math.abs(event.deltaX) < 1.2 * Math.abs(wheelCooldownMomentum.current)))) {
|
|
200
|
+
wheelCooldownMomentum.current = event.deltaX;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
wheelCooldown.current = null;
|
|
204
|
+
wheelCooldownMomentum.current = null;
|
|
205
|
+
}
|
|
206
|
+
event.persist();
|
|
207
|
+
wheelEvents.current = wheelEvents.current.filter((e) => e.timeStamp > event.timeStamp - 3000);
|
|
208
|
+
wheelEvents.current.push(event);
|
|
209
|
+
const dx = wheelEvents.current.map((e) => e.deltaX).reduce((a, b) => a + b, 0);
|
|
210
|
+
const deltaX = Math.abs(dx);
|
|
211
|
+
const deltaY = Math.abs(wheelEvents.current.map((e) => e.deltaY).reduce((a, b) => a + b, 0));
|
|
212
|
+
if (deltaX > 100 && deltaX > 1.2 * deltaY) {
|
|
213
|
+
if (dx < 0) {
|
|
214
|
+
prev();
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
next();
|
|
218
|
+
}
|
|
219
|
+
wheelEvents.current = [];
|
|
220
|
+
wheelCooldown.current = event.timeStamp;
|
|
221
|
+
wheelCooldownMomentum.current = event.deltaX;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
return {
|
|
225
|
+
onKeyDown,
|
|
226
|
+
onPointerDown,
|
|
227
|
+
onPointerUp,
|
|
228
|
+
onPointerLeave: onPointerUp,
|
|
229
|
+
onPointerCancel: onPointerUp,
|
|
230
|
+
onWheel,
|
|
231
|
+
};
|
|
232
|
+
}, [prev, next, close]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function setAttribute(element, attribute, value) {
|
|
236
|
+
const previousValue = element.getAttribute(attribute);
|
|
237
|
+
element.setAttribute(attribute, value);
|
|
238
|
+
return () => {
|
|
239
|
+
if (previousValue) {
|
|
240
|
+
element.setAttribute(attribute, previousValue);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
element.removeAttribute(attribute);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function Portal({ children }) {
|
|
248
|
+
const cleanup = useRef([]);
|
|
249
|
+
const [mounted, setMounted] = useState(false);
|
|
250
|
+
const [visible, setVisible] = useState(false);
|
|
251
|
+
const onTransitionEnd = useRef();
|
|
252
|
+
const restoreFocus = useRef(null);
|
|
253
|
+
const sensors = useSensors();
|
|
254
|
+
const { addExitHook } = useController();
|
|
255
|
+
const handleCleanup = useCallback(() => {
|
|
256
|
+
cleanup.current.forEach((cleaner) => cleaner());
|
|
257
|
+
cleanup.current = [];
|
|
258
|
+
}, []);
|
|
259
|
+
useEffect(() => addExitHook(() => new Promise((resolve) => {
|
|
260
|
+
onTransitionEnd.current = () => {
|
|
261
|
+
onTransitionEnd.current = undefined;
|
|
262
|
+
resolve();
|
|
263
|
+
};
|
|
264
|
+
handleCleanup();
|
|
265
|
+
setVisible(false);
|
|
266
|
+
})), [addExitHook, handleCleanup]);
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
const property = cssVar("scrollbar-width");
|
|
269
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
270
|
+
if (scrollbarWidth > 0) {
|
|
271
|
+
document.documentElement.style.setProperty(property, `${scrollbarWidth}px`);
|
|
272
|
+
}
|
|
273
|
+
return () => {
|
|
274
|
+
if (scrollbarWidth > 0) {
|
|
275
|
+
document.documentElement.style.removeProperty(property);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}, []);
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
setMounted(true);
|
|
281
|
+
return () => setMounted(false);
|
|
282
|
+
}, []);
|
|
283
|
+
const handleRef = useCallback((node) => {
|
|
284
|
+
if (node) {
|
|
285
|
+
node.focus();
|
|
286
|
+
const preventWheelDefaults = (event) => event.preventDefault();
|
|
287
|
+
node.addEventListener("wheel", preventWheelDefaults, { passive: false });
|
|
288
|
+
cleanup.current.push(() => {
|
|
289
|
+
node.removeEventListener("wheel", preventWheelDefaults);
|
|
290
|
+
});
|
|
291
|
+
const elements = node.parentNode?.children ?? [];
|
|
292
|
+
for (let i = 0; i < elements.length; i += 1) {
|
|
293
|
+
const element = elements[i];
|
|
294
|
+
if (!["TEMPLATE", "SCRIPT", "STYLE"].includes(element.tagName) && element !== node) {
|
|
295
|
+
cleanup.current.push(setAttribute(element, "inert", "true"));
|
|
296
|
+
cleanup.current.push(setAttribute(element, "aria-hidden", "true"));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
cleanup.current.push(() => {
|
|
300
|
+
restoreFocus.current?.focus?.();
|
|
301
|
+
restoreFocus.current = null;
|
|
302
|
+
});
|
|
303
|
+
setVisible(true);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
handleCleanup();
|
|
307
|
+
}
|
|
308
|
+
}, [handleCleanup]);
|
|
309
|
+
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) => {
|
|
311
|
+
if (!restoreFocus.current) {
|
|
312
|
+
restoreFocus.current = event.relatedTarget;
|
|
313
|
+
}
|
|
314
|
+
}, ...sensors, children: children }), document.body)
|
|
315
|
+
: null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function Lightbox({ slides, index, setIndex, ...rest }) {
|
|
319
|
+
if (!Array.isArray(slides) || index === undefined || index < 0 || index >= slides.length)
|
|
320
|
+
return null;
|
|
321
|
+
return (jsx(LightboxContextProvider, { slides, index, ...rest, children: jsx(Controller, { setIndex, children: jsxs(Portal, { children: [jsx(Carousel, {}), jsx(Navigation, {})] }) }) }));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { Lightbox as default };
|
package/dist/styles.css
ADDED
|
@@ -0,0 +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)}.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)}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yet-another-react-lightbox-lite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight React lightbox component",
|
|
5
|
+
"author": "Igor Danchenko",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./styles.css": {
|
|
16
|
+
"types": "./dist/styles.css.d.ts",
|
|
17
|
+
"default": "./dist/styles.css"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": [
|
|
24
|
+
"*.css"
|
|
25
|
+
],
|
|
26
|
+
"homepage": "https://github.com/igordanchenko/yet-another-react-lightbox-lite",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/igordanchenko/yet-another-react-lightbox-lite.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/igordanchenko/yet-another-react-lightbox-lite/issues"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"provenance": true
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@types/react": ">=18",
|
|
43
|
+
"@types/react-dom": ">=18",
|
|
44
|
+
"react": ">=18",
|
|
45
|
+
"react-dom": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@types/react": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@types/react-dom": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"react",
|
|
57
|
+
"lightbox",
|
|
58
|
+
"react lightbox",
|
|
59
|
+
"react lightbox lite",
|
|
60
|
+
"lightweight react lightbox"
|
|
61
|
+
]
|
|
62
|
+
}
|