@jobber/components 6.113.1 → 6.114.1

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.
@@ -0,0 +1,94 @@
1
+ import type { CSSProperties, ReactNode, RefObject } from "react";
2
+ import type { PanInfo } from "framer-motion";
3
+ export interface PresentedImage {
4
+ title?: string;
5
+ caption?: string;
6
+ alt?: string;
7
+ url: string;
8
+ }
9
+ export interface RequestCloseOptions {
10
+ lastPosition: number;
11
+ }
12
+ export interface LightBoxContextType {
13
+ readonly open: boolean;
14
+ readonly images: PresentedImage[];
15
+ readonly currentImageIndex: number;
16
+ readonly mouseIsStationary: boolean;
17
+ readonly boxSizing: CSSProperties["boxSizing"];
18
+ readonly directionRef: RefObject<number>;
19
+ readonly selectedThumbnailRef: RefObject<HTMLDivElement | null>;
20
+ readonly lightboxRef: RefObject<HTMLDivElement | null>;
21
+ readonly mounted: RefObject<boolean>;
22
+ handleMouseMove(): void;
23
+ handleRequestClose(): void;
24
+ handleOnDragEnd(event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo): void;
25
+ handleThumbnailClick(index: number): void;
26
+ debouncedHandleNext(): void;
27
+ debouncedHandlePrevious(): void;
28
+ }
29
+ export interface NavButtonProps {
30
+ readonly onClick: () => void;
31
+ readonly hideButton: boolean;
32
+ readonly className: string;
33
+ }
34
+ export interface LightBoxProps {
35
+ /**
36
+ * Specify if the Lightbox is open or closed.
37
+ */
38
+ readonly open: boolean;
39
+ /**
40
+ * Images is an array of objects defining a LightBox image. This object consists of
41
+ * `title`, `caption`, `alt` and `url`. `title`, `alt` and `caption` are optional, `url` is
42
+ * required, for each image.
43
+ */
44
+ readonly images: PresentedImage[];
45
+ /**
46
+ * Use this to specify which image in `images` to initialize the lightbox with.
47
+ * This is useful when you have a collection of thumbnails as you only need one
48
+ * collection of image urls, order doesn't matter.
49
+ */
50
+ readonly imageIndex?: number;
51
+ /**
52
+ * This function must set open to false in order to close the lightbox. Note there
53
+ * is a 300ms easing animation on lightbox close that occurs before this function
54
+ * is called.
55
+ * This function receives an object as an argument with the key `lastPosition`
56
+ * that has the index of the image the user was on when LightBox was closed.
57
+ */
58
+ onRequestClose(options: RequestCloseOptions): void;
59
+ /**
60
+ * Sets the box-sizing for the thumbnails in the lightbox. This is a solution for a problem where
61
+ * tailwind was setting the box-sizing to `border-box` and causing issues with the lightbox.
62
+ * @default "content-box"
63
+ */
64
+ readonly boxSizing?: CSSProperties["boxSizing"];
65
+ }
66
+ export type LightBoxProviderProps = Omit<LightBoxProps, "onRequestClose"> & {
67
+ /**
68
+ * This function must set open to false in order to close the lightbox. Note there
69
+ * is a 300ms easing animation on lightbox close that occurs before this function
70
+ * is called.
71
+ * This function receives an object as an argument with the key `lastPosition`
72
+ * that has the index of the image the user was on when LightBox was closed.
73
+ */
74
+ onRequestClose?(options: RequestCloseOptions): void;
75
+ /**
76
+ * Callback function that is invoked whenever the current image index changes.
77
+ * This includes when the user navigates to a different image (via arrow keys,
78
+ * navigation buttons, or swipe gestures) or when clicking a thumbnail.
79
+ *
80
+ * @param index - The new current image index (0-based)
81
+ */
82
+ onImageChange?(index: number): void;
83
+ readonly children: ReactNode;
84
+ };
85
+ export interface LightBoxNavigationProps {
86
+ /**
87
+ * The class name to apply to the previous button wrapper.
88
+ */
89
+ readonly prevButtonClassName?: string;
90
+ /**
91
+ * The class name to apply to the next button wrapper.
92
+ */
93
+ readonly nextButtonClassName?: string;
94
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ import type { LightBoxContextType, LightBoxProviderProps } from "./LightBox.types";
3
+ export declare const LightBoxContext: React.Context<LightBoxContextType>;
4
+ export declare function LightBoxProvider({ open, images, imageIndex, onRequestClose, onImageChange, boxSizing, children, }: LightBoxProviderProps): React.JSX.Element;
5
+ export declare function useLightBoxContext(): LightBoxContextType;
@@ -6,6 +6,8 @@ require('framer-motion');
6
6
  require('react-dom');
7
7
  require('@jobber/hooks');
8
8
  require('classnames');
9
+ require('../noop-cjs.js');
10
+ require('../_commonjsHelpers-cjs.js');
9
11
  require('../ButtonDismiss-cjs.js');
10
12
  require('../Button-cjs.js');
11
13
  require('react-router-dom');
@@ -16,7 +18,6 @@ require('../Typography-cjs.js');
16
18
  require('../Text-cjs.js');
17
19
  require('../Heading-cjs.js');
18
20
  require('../AtlantisThemeContext-cjs.js');
19
- require('../_commonjsHelpers-cjs.js');
20
21
  require('../identity-cjs.js');
21
22
  require('../isTypedArray-cjs.js');
22
23
  require('../isObjectLike-cjs.js');
@@ -29,3 +30,4 @@ require('../_setToString-cjs.js');
29
30
 
30
31
 
31
32
  exports.LightBox = LightBox.LightBox;
33
+ exports.useLightBoxContext = LightBox.useLightBoxContext;
@@ -1 +1,3 @@
1
1
  export { LightBox } from "./LightBox";
2
+ export type { LightBoxProps, LightBoxProviderProps, PresentedImage, } from "./LightBox.types";
3
+ export { useLightBoxContext } from "./LightBoxContext";
@@ -1,9 +1,11 @@
1
- export { L as LightBox } from '../LightBox-es.js';
1
+ export { L as LightBox, u as useLightBoxContext } from '../LightBox-es.js';
2
2
  import 'react';
3
3
  import 'framer-motion';
4
4
  import 'react-dom';
5
5
  import '@jobber/hooks';
6
6
  import 'classnames';
7
+ import '../noop-es.js';
8
+ import '../_commonjsHelpers-es.js';
7
9
  import '../ButtonDismiss-es.js';
8
10
  import '../Button-es.js';
9
11
  import 'react-router-dom';
@@ -14,7 +16,6 @@ import '../Typography-es.js';
14
16
  import '../Text-es.js';
15
17
  import '../Heading-es.js';
16
18
  import '../AtlantisThemeContext-es.js';
17
- import '../_commonjsHelpers-es.js';
18
19
  import '../identity-es.js';
19
20
  import '../isTypedArray-es.js';
20
21
  import '../isObjectLike-es.js';
@@ -5,6 +5,7 @@ var framerMotion = require('framer-motion');
5
5
  var ReactDOM = require('react-dom');
6
6
  var jobberHooks = require('@jobber/hooks');
7
7
  var classnames = require('classnames');
8
+ var noop = require('./noop-cjs.js');
8
9
  var ButtonDismiss = require('./ButtonDismiss-cjs.js');
9
10
  var Text = require('./Text-cjs.js');
10
11
  var Button = require('./Button-cjs.js');
@@ -13,6 +14,11 @@ var AtlantisThemeContext = require('./AtlantisThemeContext-cjs.js');
13
14
 
14
15
  var styles = {"backgroundImage":"i9Tw1T65W-k-","next":"Q8amcRaTGf0-","prev":"W9FVb24yJrk-","buttonHidden":"nsN0TPWsBXI-","buttonVisible":"dkLYp7AD2jE-","lightboxWrapper":"_5p2iAj4JfoE-","toolbar":"rMK4cKdOxFw-","closeButton":"_0m6vb11DgiA-","slideNumber":"kCc68gGuTgg-","image":"yYFVVScosfQ-","imageArea":"UskuwLHR6fg-","captionWrapper":"OGjhge-r-U4-","title":"tZU2g-NYdIs-","blurOverlay":"GKIdLTmvcvQ-","thumbnailBar":"_3TfQLQEE3GQ-","thumbnailImage":"eBMzUOlcfQ4-","thumbnail":"eapm2zruLn8-","selected":"PeLn2u-QB0k-","spinning":"_8tDoqjgfLcw-"};
15
16
 
17
+ // A little bit more than the transition's duration
18
+ // We're doing this to prevent a bug from framer-motion
19
+ // https://github.com/framer/motion/issues/1769
20
+ const BUTTON_DEBOUNCE_DELAY = 250;
21
+ const MOVEMENT_DEBOUNCE_DELAY = 1000;
16
22
  const swipeConfidenceThreshold = 10000;
17
23
  const swipePower = (offset, velocity) => {
18
24
  return Math.abs(offset) * velocity;
@@ -31,12 +37,26 @@ const slideVariants = {
31
37
  const imageTransition = {
32
38
  x: { duration: 0.65, ease: [0.42, 0, 0, 1.03] },
33
39
  };
34
- // A little bit more than the transition's duration
35
- // We're doing this to prevent a bug from framer-motion
36
- // https://github.com/framer/motion/issues/1769
37
- const BUTTON_DEBOUNCE_DELAY = 250;
38
- const MOVEMENT_DEBOUNCE_DELAY = 1000;
39
- function LightBox({ boxSizing = "content-box", open, images, imageIndex = 0, onRequestClose, }) {
40
+
41
+ /* eslint-disable max-statements */
42
+ const LightBoxContext = React.createContext({
43
+ open: false,
44
+ images: [],
45
+ currentImageIndex: 0,
46
+ mouseIsStationary: true,
47
+ boxSizing: "content-box",
48
+ directionRef: { current: 0 },
49
+ selectedThumbnailRef: { current: null },
50
+ lightboxRef: { current: null },
51
+ mounted: { current: false },
52
+ handleMouseMove: noop.noop,
53
+ handleRequestClose: noop.noop,
54
+ handleOnDragEnd: noop.noop,
55
+ handleThumbnailClick: noop.noop,
56
+ debouncedHandleNext: noop.noop,
57
+ debouncedHandlePrevious: noop.noop,
58
+ });
59
+ function LightBoxProvider({ open = true, images, imageIndex = 0, onRequestClose = noop.noop, onImageChange = noop.noop, boxSizing = "content-box", children, }) {
40
60
  const [currentImageIndex, setCurrentImageIndex] = React.useState(imageIndex);
41
61
  const directionRef = React.useRef(0);
42
62
  const [mouseIsStationary, setMouseIsStationary] = React.useState(true);
@@ -59,6 +79,7 @@ function LightBox({ boxSizing = "content-box", open, images, imageIndex = 0, onR
59
79
  });
60
80
  React.useEffect(() => {
61
81
  setCurrentImageIndex(imageIndex);
82
+ onImageChange(imageIndex);
62
83
  }, [imageIndex, open]);
63
84
  if (prevOpen.current !== open) {
64
85
  prevOpen.current = open;
@@ -72,57 +93,28 @@ function LightBox({ boxSizing = "content-box", open, images, imageIndex = 0, onR
72
93
  inline: "center",
73
94
  });
74
95
  }, [currentImageIndex]);
75
- const template = (React.createElement(React.Fragment, null, open && (React.createElement("div", { className: styles.lightboxWrapper, tabIndex: 0, "aria-label": "Lightbox", key: "Lightbox", ref: lightboxRef, onMouseMove: () => {
76
- if (mouseIsStationary) {
77
- setMouseIsStationary(false);
78
- }
79
- handleMouseMovement();
80
- } },
81
- React.createElement("div", { className: styles.backgroundImage, style: {
82
- backgroundImage: `url("${images[currentImageIndex].url}")`,
83
- } }),
84
- React.createElement("div", { className: styles.blurOverlay, onClick: handleRequestClose }),
85
- React.createElement(AtlantisThemeContext.AtlantisThemeContextProvider, { dangerouslyOverrideTheme: "dark" },
86
- React.createElement("div", { className: styles.toolbar },
87
- React.createElement("div", { className: styles.slideNumber },
88
- React.createElement(Text.Text, null, `${currentImageIndex + 1}/${images.length}`)),
89
- React.createElement("div", { className: styles.closeButton },
90
- React.createElement(ButtonDismiss.ButtonDismiss, { ariaLabel: "Close", onClick: handleRequestClose })))),
91
- React.createElement("div", { className: styles.imageArea },
92
- React.createElement(framerMotion.AnimatePresence, { initial: false },
93
- React.createElement(framerMotion.motion.img, { key: currentImageIndex, variants: slideVariants, src: images[currentImageIndex].url, custom: directionRef, className: styles.image, initial: "enter", alt: images[currentImageIndex].alt ||
94
- images[currentImageIndex].title ||
95
- "", animate: "center", exit: "exit", transition: imageTransition, drag: "x", dragConstraints: { left: 0, right: 0 }, dragElastic: 1, onDragEnd: handleOnDragEnd }))),
96
- images.length > 1 && (React.createElement(React.Fragment, null,
97
- React.createElement(PreviousButton, { onClick: debouncedHandlePrevious, hideButton: mouseIsStationary }),
98
- React.createElement(NextButton, { onClick: debouncedHandleNext, hideButton: mouseIsStationary }))),
99
- (images[currentImageIndex].title ||
100
- images[currentImageIndex].caption) && (React.createElement("div", { className: styles.captionWrapper },
101
- React.createElement(AtlantisThemeContext.AtlantisThemeContextProvider, { dangerouslyOverrideTheme: "dark" },
102
- images[currentImageIndex].title && (React.createElement("div", { className: styles.title },
103
- React.createElement(Heading.Heading, { level: 4 }, images[currentImageIndex].title))),
104
- images[currentImageIndex].caption && (React.createElement(Text.Text, { size: "large" }, images[currentImageIndex].caption))))),
105
- images.length > 1 && (React.createElement("div", { className: styles.thumbnailBar, style: {
106
- "--lightbox--box-sizing": boxSizing,
107
- }, "data-testid": "ATL-Thumbnail-Bar" }, images.map((image, index) => (React.createElement("div", { key: index, className: classnames(styles.thumbnail, {
108
- [styles.selected]: index === currentImageIndex,
109
- }), onClick: () => handleThumbnailClick(index), ref: index === currentImageIndex ? selectedThumbnailRef : null },
110
- React.createElement("img", { key: index, src: image.url, alt: image.alt || image.title || "", className: styles.thumbnailImage }))))))))));
111
- return mounted.current
112
- ? ReactDOM.createPortal(template, document.body)
113
- : template;
96
+ function handleMouseMove() {
97
+ if (mouseIsStationary) {
98
+ setMouseIsStationary(false);
99
+ }
100
+ handleMouseMovement();
101
+ }
114
102
  function handleMovePrevious() {
115
103
  directionRef.current = -1;
116
- setCurrentImageIndex((currentImageIndex + images.length - 1) % images.length);
104
+ const newIndex = (currentImageIndex + images.length - 1) % images.length;
105
+ setCurrentImageIndex(newIndex);
106
+ onImageChange(newIndex);
117
107
  }
118
108
  function handleMoveNext() {
119
109
  directionRef.current = 1;
120
- setCurrentImageIndex((currentImageIndex + 1) % images.length);
110
+ const newIndex = (currentImageIndex + 1) % images.length;
111
+ setCurrentImageIndex(newIndex);
112
+ onImageChange(newIndex);
121
113
  }
122
114
  function handleRequestClose() {
123
115
  onRequestClose({ lastPosition: currentImageIndex });
124
116
  }
125
- function handleOnDragEnd(event, { offset, velocity }) {
117
+ function handleOnDragEnd(_event, { offset, velocity }) {
126
118
  const swipe = swipePower(offset.x, velocity.x);
127
119
  if (swipe < -swipeConfidenceThreshold) {
128
120
  handleMoveNext();
@@ -139,17 +131,28 @@ function LightBox({ boxSizing = "content-box", open, images, imageIndex = 0, onR
139
131
  directionRef.current = 1;
140
132
  }
141
133
  setCurrentImageIndex(index);
134
+ onImageChange(index);
142
135
  }
136
+ return (React.createElement(LightBoxContext.Provider, { value: {
137
+ open,
138
+ images,
139
+ currentImageIndex,
140
+ mouseIsStationary,
141
+ boxSizing,
142
+ directionRef,
143
+ selectedThumbnailRef,
144
+ lightboxRef,
145
+ mounted,
146
+ handleMouseMove,
147
+ handleRequestClose,
148
+ handleOnDragEnd,
149
+ handleThumbnailClick,
150
+ debouncedHandleNext,
151
+ debouncedHandlePrevious,
152
+ } }, children));
143
153
  }
144
- function PreviousButton({ onClick, hideButton }) {
145
- const { mediumAndUp } = jobberHooks.useBreakpoints();
146
- return (React.createElement("div", { className: `${styles.prev} ${hideButton ? styles.buttonHidden : styles.buttonVisible}` },
147
- React.createElement(Button.Button, { size: mediumAndUp ? "large" : "small", variation: "subtle", type: "secondary", icon: "arrowLeft", ariaLabel: "Previous image", onClick: onClick })));
148
- }
149
- function NextButton({ onClick, hideButton }) {
150
- const { mediumAndUp } = jobberHooks.useBreakpoints();
151
- return (React.createElement("div", { className: `${styles.next} ${hideButton ? styles.buttonHidden : styles.buttonVisible}` },
152
- React.createElement(Button.Button, { size: mediumAndUp ? "large" : "small", variation: "subtle", type: "secondary", icon: "arrowRight", ariaLabel: "Next image", onClick: onClick })));
154
+ function useLightBoxContext() {
155
+ return React.useContext(LightBoxContext);
153
156
  }
154
157
  function togglePrintStyles(open) {
155
158
  try {
@@ -165,4 +168,189 @@ function togglePrintStyles(open) {
165
168
  }
166
169
  }
167
170
 
171
+ function LightBoxContent() {
172
+ const { open, lightboxRef, handleMouseMove } = useLightBoxContext();
173
+ const mounted = jobberHooks.useIsMounted();
174
+ const template = (React.createElement(React.Fragment, null, open && (React.createElement("div", { className: styles.lightboxWrapper, tabIndex: 0, "aria-label": "Lightbox", key: "Lightbox", ref: lightboxRef, onMouseMove: handleMouseMove },
175
+ React.createElement(LightBoxBackground, null),
176
+ React.createElement(LightBoxOverlay, null),
177
+ React.createElement(LightBoxToolbar, null),
178
+ React.createElement(LightBoxSlides, null),
179
+ React.createElement(LightBoxNavigation, null),
180
+ React.createElement(LightBoxCaption, null),
181
+ React.createElement(LightBoxThumbnails, null)))));
182
+ return mounted.current
183
+ ? ReactDOM.createPortal(template, document.body)
184
+ : template;
185
+ }
186
+ function PreviousButton({ onClick, hideButton, className }) {
187
+ const { mediumAndUp } = jobberHooks.useBreakpoints();
188
+ return (React.createElement("div", { className: `${className} ${hideButton ? styles.buttonHidden : styles.buttonVisible}` },
189
+ React.createElement(Button.Button, { size: mediumAndUp ? "large" : "small", variation: "subtle", type: "secondary", icon: "arrowLeft", ariaLabel: "Previous image", onClick: onClick })));
190
+ }
191
+ function NextButton({ onClick, hideButton, className }) {
192
+ const { mediumAndUp } = jobberHooks.useBreakpoints();
193
+ return (React.createElement("div", { className: `${className} ${hideButton ? styles.buttonHidden : styles.buttonVisible}` },
194
+ React.createElement(Button.Button, { size: mediumAndUp ? "large" : "small", variation: "subtle", type: "secondary", icon: "arrowRight", ariaLabel: "Next image", onClick: onClick })));
195
+ }
196
+ /**
197
+ * Blurred, desaturated copy of the current image rendered as a full-bleed
198
+ * background behind the lightbox. Pass `className` to apply additional styles.
199
+ */
200
+ function LightBoxBackground({ className }) {
201
+ const { images, currentImageIndex } = useLightBoxContext();
202
+ return (React.createElement("div", { className: classnames(styles.backgroundImage, className), style: {
203
+ backgroundImage: `url("${images[currentImageIndex].url}")`,
204
+ } }));
205
+ }
206
+ /**
207
+ * Semi-transparent blur backdrop. Clicking it calls `onRequestClose`.
208
+ * Pass `className` to apply additional styles.
209
+ */
210
+ function LightBoxOverlay({ className }) {
211
+ const { handleRequestClose } = useLightBoxContext();
212
+ return (React.createElement("div", { className: classnames(styles.blurOverlay, className), onClick: handleRequestClose }));
213
+ }
214
+ /**
215
+ * Top bar showing the current image counter (`1/3`) and a close button.
216
+ * Styled for dark backgrounds.
217
+ */
218
+ function LightBoxToolbar() {
219
+ const { images, currentImageIndex, handleRequestClose } = useLightBoxContext();
220
+ return (React.createElement(AtlantisThemeContext.AtlantisThemeContextProvider, { dangerouslyOverrideTheme: "dark" },
221
+ React.createElement("div", { className: styles.toolbar },
222
+ React.createElement("div", { className: styles.slideNumber },
223
+ React.createElement(Text.Text, null, `${currentImageIndex + 1}/${images.length}`)),
224
+ React.createElement("div", { className: styles.closeButton },
225
+ React.createElement(ButtonDismiss.ButtonDismiss, { ariaLabel: "Close", onClick: handleRequestClose })))));
226
+ }
227
+ /**
228
+ * The animated hero image with swipe-to-navigate and slide animation.
229
+ *
230
+ * Pass `className` to add styles to the image wrapper. Supports
231
+ * swipe-to-navigate (drag). Keyboard arrow navigation is handled by
232
+ * `LightBox.Provider`.
233
+ *
234
+ * @example
235
+ * ```tsx
236
+ * <LightBox.Slides className={styles.imageArea} />
237
+ * <LightBox.Navigation
238
+ * prevButtonClassName={styles.prev}
239
+ * nextButtonClassName={styles.next}
240
+ * />
241
+ * ```
242
+ */
243
+ function LightBoxSlides({ className }) {
244
+ const { images, currentImageIndex, directionRef, handleOnDragEnd } = useLightBoxContext();
245
+ return (React.createElement("div", { className: classnames(styles.imageArea, className) },
246
+ React.createElement(framerMotion.AnimatePresence, { initial: false },
247
+ React.createElement(framerMotion.motion.img, { key: currentImageIndex, variants: slideVariants, src: images[currentImageIndex].url, custom: directionRef, className: styles.image, initial: "enter", alt: images[currentImageIndex].alt ||
248
+ images[currentImageIndex].title ||
249
+ "", animate: "center", exit: "exit", transition: imageTransition, drag: "x", dragConstraints: { left: 0, right: 0 }, dragElastic: 1, onDragEnd: handleOnDragEnd }))));
250
+ }
251
+ /**
252
+ * Previous and next navigation buttons. Returns `null` when the image set
253
+ * has only one image.
254
+ *
255
+ * Use `prevButtonClassName` and `nextButtonClassName` to override the styles
256
+ * on each button's wrapper for custom layouts.
257
+ *
258
+ * @example
259
+ * ```tsx
260
+ * <LightBox.Navigation
261
+ * prevButtonClassName={styles.prev}
262
+ * nextButtonClassName={styles.next}
263
+ * />
264
+ * ```
265
+ */
266
+ function LightBoxNavigation({ prevButtonClassName, nextButtonClassName, }) {
267
+ const { images, mouseIsStationary, debouncedHandleNext, debouncedHandlePrevious, } = useLightBoxContext();
268
+ if (images.length <= 1)
269
+ return null;
270
+ return (React.createElement(React.Fragment, null,
271
+ React.createElement(PreviousButton, { onClick: debouncedHandlePrevious, hideButton: mouseIsStationary, className: classnames(styles.prev, prevButtonClassName) }),
272
+ React.createElement(NextButton, { onClick: debouncedHandleNext, hideButton: mouseIsStationary, className: classnames(styles.next, nextButtonClassName) })));
273
+ }
274
+ /**
275
+ * Title and caption text for the current image. Only renders when the current
276
+ * image has a `title` or `caption`. Styled for dark backgrounds.
277
+ */
278
+ function LightBoxCaption() {
279
+ const { images, currentImageIndex } = useLightBoxContext();
280
+ const { title, caption } = images[currentImageIndex];
281
+ if (!title && !caption)
282
+ return null;
283
+ return (React.createElement("div", { className: styles.captionWrapper },
284
+ React.createElement(AtlantisThemeContext.AtlantisThemeContextProvider, { dangerouslyOverrideTheme: "dark" },
285
+ title && (React.createElement("div", { className: styles.title },
286
+ React.createElement(Heading.Heading, { level: 4 }, title))),
287
+ caption && React.createElement(Text.Text, { size: "large" }, caption))));
288
+ }
289
+ /**
290
+ * Scrollable thumbnail strip. Only renders when there are two or more images.
291
+ */
292
+ function LightBoxThumbnails() {
293
+ const { images, currentImageIndex, boxSizing, selectedThumbnailRef, handleThumbnailClick, } = useLightBoxContext();
294
+ if (images.length <= 1)
295
+ return null;
296
+ return (React.createElement("div", { className: styles.thumbnailBar, style: { "--lightbox--box-sizing": boxSizing }, "data-testid": "ATL-Thumbnail-Bar" }, images.map((image, index) => (React.createElement("div", { key: index, className: classnames(styles.thumbnail, {
297
+ [styles.selected]: index === currentImageIndex,
298
+ }), onClick: () => handleThumbnailClick(index), ref: index === currentImageIndex ? selectedThumbnailRef : null },
299
+ React.createElement("img", { key: index, src: image.url, alt: image.alt || image.title || "", className: styles.thumbnailImage }))))));
300
+ }
301
+ /**
302
+ * LightBox displays images in a fullscreen overlay.
303
+ *
304
+ * **Self-contained (legacy) usage:**
305
+ * ```tsx
306
+ * <LightBox
307
+ * open={isOpen}
308
+ * images={images}
309
+ * imageIndex={imageIndex}
310
+ * onRequestClose={({ lastPosition }) => { setIsOpen(false); }}
311
+ * />
312
+ * ```
313
+ *
314
+ * **Full composable (fullscreen) usage:**
315
+ * ```tsx
316
+ * <LightBox.Provider open={isOpen} images={images} onRequestClose={onClose}>
317
+ * <LightBox.Content />
318
+ * </LightBox.Provider>
319
+ * ```
320
+ *
321
+ * **Inline gallery usage (no overlay, no close):**
322
+ * ```tsx
323
+ * <LightBox.Provider
324
+ * open={true}
325
+ * images={images}
326
+ * imageIndex={activeIndex}
327
+ * onImageChange={onImageChange}
328
+ * >
329
+ * <div className={styles.lightboxWrapper} onMouseMove={handleMouseMove}>
330
+ * <LightBox.Background className={styles.backgroundImage} />
331
+ * <LightBox.Overlay className={styles.blurOverlay} />
332
+ * <LightBox.Slides className={styles.imageArea} />
333
+ * <LightBox.Navigation
334
+ * prevButtonClassName={styles.prev}
335
+ * nextButtonClassName={styles.next}
336
+ * />
337
+ * </div>
338
+ * </LightBox.Provider>
339
+ * ```
340
+ */
341
+ function LightBox(props) {
342
+ return (React.createElement(LightBoxProvider, Object.assign({}, props),
343
+ React.createElement(LightBoxContent, null)));
344
+ }
345
+ LightBox.Provider = LightBoxProvider;
346
+ LightBox.Content = LightBoxContent;
347
+ LightBox.Background = LightBoxBackground;
348
+ LightBox.Overlay = LightBoxOverlay;
349
+ LightBox.Toolbar = LightBoxToolbar;
350
+ LightBox.Slides = LightBoxSlides;
351
+ LightBox.Navigation = LightBoxNavigation;
352
+ LightBox.Caption = LightBoxCaption;
353
+ LightBox.Thumbnails = LightBoxThumbnails;
354
+
168
355
  exports.LightBox = LightBox;
356
+ exports.useLightBoxContext = useLightBoxContext;