@boyernick/standard-ui-react 0.1.1-canary.9 → 0.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.
Files changed (82) hide show
  1. package/package.json +5 -3
  2. package/src/accordion.tsx +5 -2
  3. package/src/alert-dialog.tsx +1 -1
  4. package/src/attachment.tsx +5 -2
  5. package/src/autocomplete.tsx +4 -3
  6. package/src/block-editor.tsx +1747 -0
  7. package/src/brand.tsx +2 -2
  8. package/src/breadcrumb.tsx +14 -7
  9. package/src/button.tsx +22 -11
  10. package/src/calendar.tsx +6 -3
  11. package/src/carousel.tsx +220 -53
  12. package/src/checkbox.tsx +3 -2
  13. package/src/code-block.tsx +66 -5
  14. package/src/collapsible.tsx +4 -2
  15. package/src/combobox.tsx +4 -3
  16. package/src/command.tsx +27 -12
  17. package/src/dialog.tsx +33 -10
  18. package/src/empty.tsx +4 -4
  19. package/src/field.tsx +4 -3
  20. package/src/filter-group.tsx +143 -0
  21. package/src/icons.tsx +28 -1
  22. package/src/illustrations.tsx +219 -141
  23. package/src/index.ts +238 -18
  24. package/src/input.tsx +8 -5
  25. package/src/kbd.tsx +10 -4
  26. package/src/lib/focus.ts +41 -0
  27. package/src/lib/popup.ts +1 -1
  28. package/src/lifeline/company-icon.tsx +71 -0
  29. package/src/lifeline/index.ts +42 -0
  30. package/src/lifeline/lifeline-data.ts +97 -0
  31. package/src/lifeline/lifeline-desktop.tsx +204 -0
  32. package/src/lifeline/lifeline-event.tsx +108 -0
  33. package/src/lifeline/lifeline-fireworks.tsx +302 -0
  34. package/src/lifeline/lifeline-hover-image.tsx +269 -0
  35. package/src/lifeline/lifeline-icons.tsx +39 -0
  36. package/src/lifeline/lifeline-intro-timing.ts +105 -0
  37. package/src/lifeline/lifeline-labels.tsx +24 -0
  38. package/src/lifeline/lifeline-layout.ts +1 -0
  39. package/src/lifeline/lifeline-legend.tsx +31 -0
  40. package/src/lifeline/lifeline-lightbox.tsx +309 -0
  41. package/src/lifeline/lifeline-marker.tsx +183 -0
  42. package/src/lifeline/lifeline-people.tsx +99 -0
  43. package/src/lifeline/lifeline-photos.tsx +366 -0
  44. package/src/lifeline/lifeline-shell.tsx +135 -0
  45. package/src/lifeline/lifeline-utils.ts +83 -0
  46. package/src/lifeline/lifeline-vertical.tsx +482 -0
  47. package/src/lifeline/lifeline.tsx +69 -0
  48. package/src/lifeline/types.ts +112 -0
  49. package/src/lifeline/use-lifeline-intro.ts +130 -0
  50. package/src/lifeline/use-lifeline-scroll.ts +1011 -0
  51. package/src/lifeline/use-lifeline-vertical-scroll.ts +271 -0
  52. package/src/menubar.tsx +9 -2
  53. package/src/minimap.tsx +296 -0
  54. package/src/modal.tsx +608 -0
  55. package/src/navigation-menu.tsx +338 -67
  56. package/src/number-field.tsx +336 -61
  57. package/src/otp-field.tsx +4 -3
  58. package/src/pagination.tsx +262 -42
  59. package/src/password-protection.tsx +345 -0
  60. package/src/popover.tsx +1 -1
  61. package/src/progress.tsx +5 -0
  62. package/src/questionnaire.tsx +284 -0
  63. package/src/radio.tsx +4 -2
  64. package/src/scroll-area.tsx +4 -1
  65. package/src/select.tsx +5 -2
  66. package/src/sidebar.tsx +113 -28
  67. package/src/slider.tsx +66 -6
  68. package/src/sounds.tsx +6 -10
  69. package/src/spinner.tsx +105 -32
  70. package/src/switch.tsx +4 -1
  71. package/src/table.tsx +82 -15
  72. package/src/tabs.tsx +189 -38
  73. package/src/text-animate.tsx +71 -28
  74. package/src/textarea.tsx +6 -1
  75. package/src/timeline.tsx +378 -0
  76. package/src/toast.tsx +214 -35
  77. package/src/toggle.tsx +4 -2
  78. package/src/toolbar.tsx +12 -7
  79. package/src/tooltip.tsx +43 -3
  80. package/src/video-player.tsx +396 -127
  81. package/src/image-modal.tsx +0 -110
  82. package/src/markdown-editor.tsx +0 -302
package/src/modal.tsx ADDED
@@ -0,0 +1,608 @@
1
+ "use client"
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import {
5
+ createContext,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useRef,
10
+ useState,
11
+ type ComponentProps,
12
+ type ImgHTMLAttributes,
13
+ type KeyboardEvent,
14
+ type ReactElement,
15
+ type ReactNode,
16
+ } from "react"
17
+ import { Button, buttonVariants } from "./button"
18
+ import {
19
+ Carousel,
20
+ CarouselContent,
21
+ CarouselItem,
22
+ type CarouselApi,
23
+ } from "./carousel"
24
+ import {
25
+ Dialog,
26
+ DialogBackdrop,
27
+ DialogClose,
28
+ DialogPopup,
29
+ DialogPortal,
30
+ DialogTitle,
31
+ DialogTrigger,
32
+ } from "./dialog"
33
+ import {
34
+ IconArrowDown,
35
+ IconChevronRightSmall,
36
+ IconCrossSmall,
37
+ } from "./icons"
38
+ import { cn } from "./lib/cn"
39
+ import { focusRing, focusRingBorder } from "./lib/focus"
40
+ import { motion } from "./lib/motion"
41
+ import {
42
+ Tooltip,
43
+ TooltipPopup,
44
+ TooltipPortal,
45
+ TooltipPositioner,
46
+ TooltipProvider,
47
+ TooltipTrigger,
48
+ type TooltipPositionerProps,
49
+ } from "./tooltip"
50
+
51
+ const TRACKPAD_LABEL =
52
+ "Swipe, use the arrow keys, or use the controls to navigate"
53
+
54
+ const modalControlClassName =
55
+ "border-border-inverted bg-surface-inverted/60 text-fg-inverted shadow-md backdrop-blur-md hover:bg-surface-inverted/80 hover:text-fg-inverted outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-surface-inverted focus-visible:ring-ring/20"
56
+
57
+ export const modalContentVariants = cva(
58
+ "h-dvh max-h-dvh w-screen max-w-none gap-0 overflow-hidden rounded-none border-0 bg-transparent p-0 shadow-none max-sm:max-w-none",
59
+ {
60
+ variants: {
61
+ variant: {
62
+ default: "",
63
+ caption: "",
64
+ },
65
+ },
66
+ defaultVariants: {
67
+ variant: "default",
68
+ },
69
+ },
70
+ )
71
+
72
+ export type ModalProps = ComponentProps<typeof Dialog>
73
+ export type ModalTriggerProps = ComponentProps<typeof DialogTrigger>
74
+ export type ModalContentProps = Omit<
75
+ ComponentProps<typeof DialogPopup>,
76
+ "children"
77
+ > &
78
+ VariantProps<typeof modalContentVariants> & {
79
+ src: string
80
+ alt: string
81
+ caption?: ReactNode
82
+ downloadSrc?: string
83
+ downloadName?: string
84
+ imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">
85
+ children?: ReactNode
86
+ }
87
+
88
+ export type GalleryItem = {
89
+ src: string
90
+ alt: string
91
+ caption?: ReactNode
92
+ downloadSrc?: string
93
+ downloadName?: string
94
+ imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">
95
+ }
96
+
97
+ export type GalleryProps = Omit<ModalProps, "children"> &
98
+ VariantProps<typeof modalContentVariants> & {
99
+ images: readonly GalleryItem[]
100
+ children: ReactNode
101
+ loop?: boolean
102
+ contentClassName?: string
103
+ }
104
+
105
+ export type GalleryTriggerProps = ModalTriggerProps & {
106
+ index: number
107
+ }
108
+
109
+ type ModalTriggerClickEvent = Parameters<
110
+ NonNullable<ModalTriggerProps["onClick"]>
111
+ >[0]
112
+
113
+ type GalleryContextValue = {
114
+ images: readonly GalleryItem[]
115
+ setActiveIndex: (index: number) => void
116
+ }
117
+
118
+ const GalleryContext =
119
+ createContext<GalleryContextValue | null>(null)
120
+
121
+ const useGallery = () => {
122
+ const context = useContext(GalleryContext)
123
+ if (!context) {
124
+ throw new Error(
125
+ "GalleryTrigger must be used within <Gallery>",
126
+ )
127
+ }
128
+ return context
129
+ }
130
+
131
+ export const Modal = (props: ModalProps) => <Dialog {...props} />
132
+
133
+ export const ModalTrigger = ({
134
+ className,
135
+ ...props
136
+ }: ModalTriggerProps) => (
137
+ <DialogTrigger
138
+ className={cn(
139
+ "cursor-pointer rounded-xl",
140
+ focusRingBorder,
141
+ focusRing,
142
+ className,
143
+ )}
144
+ {...props}
145
+ />
146
+ )
147
+
148
+ const ModalBackdrop = () => (
149
+ <DialogBackdrop className="bg-surface-inverted/95" />
150
+ )
151
+
152
+ const ModalBackground = ({ src }: { src: string }) => (
153
+ <div
154
+ aria-hidden="true"
155
+ className="pointer-events-none absolute inset-0 overflow-hidden"
156
+ >
157
+ {/* Consumers may use any image host, so this package deliberately renders
158
+ the platform-neutral image element rather than Next.js Image. */}
159
+ {/* eslint-disable-next-line @next/next/no-img-element */}
160
+ <img
161
+ src={src}
162
+ alt=""
163
+ className="size-full scale-110 object-cover opacity-25 blur-3xl"
164
+ />
165
+ <div className="absolute inset-0 bg-surface-inverted/65" />
166
+ </div>
167
+ )
168
+
169
+ const ModalImage = ({
170
+ src,
171
+ alt,
172
+ imgProps,
173
+ fit = "viewport",
174
+ }: Pick<GalleryItem, "src" | "alt" | "imgProps"> & {
175
+ fit?: "viewport" | "contain"
176
+ }) => {
177
+ const { className, ...props } = imgProps ?? {}
178
+
179
+ return (
180
+ // eslint-disable-next-line @next/next/no-img-element
181
+ <img
182
+ src={src}
183
+ alt={alt}
184
+ draggable={false}
185
+ className={cn(
186
+ "rounded-lg object-contain shadow-lg select-none",
187
+ fit === "contain"
188
+ ? "max-h-full max-w-full"
189
+ : "max-h-[82dvh] max-w-[88vw] max-sm:max-h-[calc(100dvh-2rem)] max-sm:max-w-[calc(100vw-2rem)]",
190
+ className,
191
+ )}
192
+ {...props}
193
+ />
194
+ )
195
+ }
196
+
197
+ const ModalControlTooltip = ({
198
+ label,
199
+ side = "bottom",
200
+ children,
201
+ }: {
202
+ label: string
203
+ side?: TooltipPositionerProps["side"]
204
+ children: ReactElement
205
+ }) => (
206
+ <Tooltip>
207
+ <TooltipTrigger render={children} />
208
+ <TooltipPortal>
209
+ <TooltipPositioner side={side}>
210
+ <TooltipPopup variant="inverted">{label}</TooltipPopup>
211
+ </TooltipPositioner>
212
+ </TooltipPortal>
213
+ </Tooltip>
214
+ )
215
+
216
+ const ModalDownload = ({
217
+ src,
218
+ name,
219
+ }: {
220
+ src: string
221
+ name?: string
222
+ }) => (
223
+ <ModalControlTooltip label="Download image">
224
+ <a
225
+ href={src}
226
+ download={name ?? ""}
227
+ aria-label="Download image"
228
+ className={cn(
229
+ buttonVariants({
230
+ variant: "ghost",
231
+ size: "md",
232
+ rounded: true,
233
+ iconOnly: true,
234
+ }),
235
+ modalControlClassName,
236
+ "absolute top-4 right-4 z-20",
237
+ )}
238
+ >
239
+ <IconArrowDown aria-hidden />
240
+ </a>
241
+ </ModalControlTooltip>
242
+ )
243
+
244
+ const ModalDismiss = () => (
245
+ <ModalControlTooltip label="Close image">
246
+ <DialogClose
247
+ render={
248
+ <Button
249
+ type="button"
250
+ variant="ghost"
251
+ size="md"
252
+ iconOnly
253
+ rounded
254
+ aria-label="Close image"
255
+ className={cn(
256
+ modalControlClassName,
257
+ "absolute top-4 left-4 z-20",
258
+ )}
259
+ />
260
+ }
261
+ >
262
+ <IconCrossSmall aria-hidden />
263
+ </DialogClose>
264
+ </ModalControlTooltip>
265
+ )
266
+
267
+ const ModalCaption = ({ children }: { children: ReactNode }) => (
268
+ <p className="mt-3 max-w-[min(40rem,88vw)] shrink-0 px-4 text-center text-sm text-fg-inverted-secondary">
269
+ {children}
270
+ </p>
271
+ )
272
+
273
+ export const ModalContent = ({
274
+ src,
275
+ alt,
276
+ caption,
277
+ downloadSrc,
278
+ downloadName,
279
+ variant = "default",
280
+ className,
281
+ imgProps,
282
+ children,
283
+ ...props
284
+ }: ModalContentProps) => (
285
+ <DialogPortal>
286
+ <ModalBackdrop />
287
+ <DialogPopup
288
+ data-slot="modal-content"
289
+ className={cn(
290
+ modalContentVariants({ variant }),
291
+ motion.popupCenter,
292
+ className,
293
+ )}
294
+ {...props}
295
+ >
296
+ <TooltipProvider delay={250}>
297
+ <ModalBackground src={src} />
298
+ <DialogTitle className="sr-only">{alt}</DialogTitle>
299
+ <div
300
+ data-slot="modal-stage"
301
+ className="relative z-10 flex size-full flex-col items-center justify-center p-4"
302
+ >
303
+ <ModalImage src={src} alt={alt} imgProps={imgProps} />
304
+ {variant === "caption" && (caption || children) ? (
305
+ <ModalCaption>{caption ?? children}</ModalCaption>
306
+ ) : null}
307
+ </div>
308
+ <ModalDismiss />
309
+ <ModalDownload
310
+ src={downloadSrc ?? src}
311
+ name={downloadName}
312
+ />
313
+ </TooltipProvider>
314
+ </DialogPopup>
315
+ </DialogPortal>
316
+ )
317
+
318
+ export const GalleryTrigger = ({
319
+ index,
320
+ onClick,
321
+ ...props
322
+ }: GalleryTriggerProps) => {
323
+ const { images, setActiveIndex } = useGallery()
324
+
325
+ const handleClick = (event: ModalTriggerClickEvent) => {
326
+ onClick?.(event)
327
+ if (!event.defaultPrevented && images[index]) setActiveIndex(index)
328
+ }
329
+
330
+ return <ModalTrigger {...props} onClick={handleClick} />
331
+ }
332
+
333
+ const GalleryFilmstrip = ({
334
+ images,
335
+ activeIndex,
336
+ onSelect,
337
+ }: {
338
+ images: readonly GalleryItem[]
339
+ activeIndex: number
340
+ onSelect: (index: number) => void
341
+ }) => {
342
+ const listRef = useRef<HTMLDivElement>(null)
343
+
344
+ useEffect(() => {
345
+ const list = listRef.current
346
+ if (!list) return
347
+ const active = list.querySelector<HTMLElement>('[aria-selected="true"]')
348
+ active?.scrollIntoView({
349
+ behavior: "smooth",
350
+ block: "nearest",
351
+ inline: "center",
352
+ })
353
+ }, [activeIndex])
354
+
355
+ if (images.length < 2) return null
356
+
357
+ return (
358
+ <div
359
+ data-slot="modal-filmstrip"
360
+ className="relative z-10 w-full shrink-0 overflow-visible"
361
+ >
362
+ <div
363
+ ref={listRef}
364
+ role="tablist"
365
+ aria-label="Gallery images"
366
+ className="flex w-full justify-center gap-2 overflow-x-auto p-1 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
367
+ >
368
+ {images.map((image, index) => {
369
+ const isActive = index === activeIndex
370
+
371
+ return (
372
+ <button
373
+ key={`${image.src}-${index}`}
374
+ type="button"
375
+ role="tab"
376
+ aria-selected={isActive}
377
+ aria-label={`Show image ${index + 1}: ${image.alt}`}
378
+ onClick={() => onSelect(index)}
379
+ className={cn(
380
+ "size-14 shrink-0 cursor-pointer overflow-hidden rounded-md outline-none",
381
+ "transition-[opacity,box-shadow] duration-[var(--duration-sm)] ease-enter motion-reduce:transition-none",
382
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-surface-inverted focus-visible:ring-ring/20",
383
+ focusRingBorder,
384
+ isActive
385
+ ? "opacity-100 ring-2 ring-fg-inverted"
386
+ : "opacity-45 hover:opacity-80",
387
+ )}
388
+ >
389
+ {/* eslint-disable-next-line @next/next/no-img-element */}
390
+ <img
391
+ src={image.src}
392
+ alt=""
393
+ draggable={false}
394
+ className="size-full object-cover"
395
+ />
396
+ </button>
397
+ )
398
+ })}
399
+ </div>
400
+ </div>
401
+ )
402
+ }
403
+
404
+ const GalleryContent = ({
405
+ images,
406
+ activeIndex,
407
+ setActiveIndex,
408
+ loop,
409
+ variant,
410
+ className,
411
+ }: {
412
+ images: readonly GalleryItem[]
413
+ activeIndex: number
414
+ setActiveIndex: (index: number) => void
415
+ loop: boolean
416
+ variant: "default" | "caption"
417
+ className?: string
418
+ }) => {
419
+ const [api, setApi] = useState<CarouselApi>()
420
+ const activeImage = images[activeIndex]
421
+
422
+ useEffect(() => {
423
+ api?.scrollTo(activeIndex, true)
424
+ }, [activeIndex, api])
425
+
426
+ useEffect(() => {
427
+ if (!api) return
428
+ const syncIndex = () => setActiveIndex(api.selectedScrollSnap())
429
+ api.on("select", syncIndex)
430
+ api.on("reInit", syncIndex)
431
+ return () => {
432
+ api.off("select", syncIndex)
433
+ api.off("reInit", syncIndex)
434
+ }
435
+ }, [api, setActiveIndex])
436
+
437
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
438
+ if (event.key === "ArrowLeft") {
439
+ event.preventDefault()
440
+ api?.scrollPrev()
441
+ } else if (event.key === "ArrowRight") {
442
+ event.preventDefault()
443
+ api?.scrollNext()
444
+ }
445
+ }
446
+
447
+ const handleSelect = (index: number) => {
448
+ setActiveIndex(index)
449
+ api?.scrollTo(index)
450
+ }
451
+
452
+ if (!activeImage) return null
453
+
454
+ return (
455
+ <DialogPortal>
456
+ <ModalBackdrop />
457
+ <DialogPopup
458
+ data-slot="modal-gallery-content"
459
+ aria-describedby={undefined}
460
+ onKeyDownCapture={handleKeyDown}
461
+ className={cn(
462
+ modalContentVariants({ variant }),
463
+ motion.popupCenter,
464
+ className,
465
+ )}
466
+ >
467
+ <TooltipProvider delay={250}>
468
+ <ModalBackground src={activeImage.src} />
469
+ <DialogTitle className="sr-only">{activeImage.alt}</DialogTitle>
470
+ <p className="sr-only" aria-live="polite">
471
+ Image {activeIndex + 1} of {images.length}. {TRACKPAD_LABEL}.
472
+ </p>
473
+ <div className="relative z-10 flex size-full flex-col gap-4 p-4">
474
+ <Carousel
475
+ setApi={setApi}
476
+ opts={{ loop, startIndex: activeIndex, duration: 24, dragFree: false }}
477
+ data-slot="modal-stage"
478
+ className="relative min-h-0 w-full min-w-0 flex-1 [&_[data-slot=carousel-viewport]]:h-full [&_[data-slot=carousel-viewport]]:min-h-0"
479
+ aria-label="Gallery"
480
+ >
481
+ <CarouselContent className="h-full !ml-0 !pr-0">
482
+ {images.map((image, index) => (
483
+ <CarouselItem
484
+ key={`${image.src}-${index}`}
485
+ className="!basis-full flex h-full min-h-0 flex-col items-center justify-center !pl-0"
486
+ aria-label={`${index + 1} of ${images.length}`}
487
+ >
488
+ <div className="flex max-h-full min-h-0 w-full flex-col items-center justify-center">
489
+ <div className="flex min-h-0 w-full items-center justify-center overflow-hidden">
490
+ <ModalImage
491
+ {...image}
492
+ fit="contain"
493
+ imgProps={{
494
+ ...image.imgProps,
495
+ className: cn(
496
+ "max-w-[min(94vw,72rem)]",
497
+ image.imgProps?.className,
498
+ ),
499
+ }}
500
+ />
501
+ </div>
502
+ {variant === "caption" && image.caption ? (
503
+ <ModalCaption>{image.caption}</ModalCaption>
504
+ ) : null}
505
+ </div>
506
+ </CarouselItem>
507
+ ))}
508
+ </CarouselContent>
509
+ </Carousel>
510
+ <GalleryFilmstrip
511
+ images={images}
512
+ activeIndex={activeIndex}
513
+ onSelect={handleSelect}
514
+ />
515
+ </div>
516
+ <ModalDismiss />
517
+ <ModalDownload
518
+ src={activeImage.downloadSrc ?? activeImage.src}
519
+ name={activeImage.downloadName}
520
+ />
521
+ {images.length > 1 ? (
522
+ <>
523
+ <ModalControlTooltip label="Previous image" side="right">
524
+ <Button
525
+ type="button"
526
+ variant="ghost"
527
+ size="lg"
528
+ iconOnly
529
+ rounded
530
+ aria-label="Previous image"
531
+ disabled={!loop && !api?.canScrollPrev()}
532
+ onClick={() => api?.scrollPrev()}
533
+ className={cn(
534
+ modalControlClassName,
535
+ "absolute top-1/2 left-4 z-20 -translate-y-1/2 active:!-translate-y-1/2",
536
+ )}
537
+ >
538
+ <IconChevronRightSmall className="rotate-180" aria-hidden />
539
+ </Button>
540
+ </ModalControlTooltip>
541
+ <ModalControlTooltip label="Next image" side="left">
542
+ <Button
543
+ type="button"
544
+ variant="ghost"
545
+ size="lg"
546
+ iconOnly
547
+ rounded
548
+ aria-label="Next image"
549
+ disabled={!loop && !api?.canScrollNext()}
550
+ onClick={() => api?.scrollNext()}
551
+ className={cn(
552
+ modalControlClassName,
553
+ "absolute top-1/2 right-4 z-20 -translate-y-1/2 active:!-translate-y-1/2",
554
+ )}
555
+ >
556
+ <IconChevronRightSmall aria-hidden />
557
+ </Button>
558
+ </ModalControlTooltip>
559
+ </>
560
+ ) : null}
561
+ </TooltipProvider>
562
+ </DialogPopup>
563
+ </DialogPortal>
564
+ )
565
+ }
566
+
567
+ export const Gallery = ({
568
+ images,
569
+ children,
570
+ loop = true,
571
+ variant = "default",
572
+ contentClassName,
573
+ ...props
574
+ }: GalleryProps) => {
575
+ const [activeIndex, setActiveIndex] = useState(0)
576
+ const safeIndex = images.length
577
+ ? Math.min(activeIndex, images.length - 1)
578
+ : 0
579
+ const setSafeActiveIndex = useCallback(
580
+ (index: number) => {
581
+ if (images[index]) setActiveIndex(index)
582
+ },
583
+ [images],
584
+ )
585
+
586
+ return (
587
+ <Dialog {...props}>
588
+ <GalleryContext.Provider
589
+ value={{
590
+ images,
591
+ setActiveIndex: setSafeActiveIndex,
592
+ }}
593
+ >
594
+ {children}
595
+ {images.length ? (
596
+ <GalleryContent
597
+ images={images}
598
+ activeIndex={safeIndex}
599
+ setActiveIndex={setSafeActiveIndex}
600
+ loop={loop}
601
+ variant={variant ?? "default"}
602
+ className={contentClassName}
603
+ />
604
+ ) : null}
605
+ </GalleryContext.Provider>
606
+ </Dialog>
607
+ )
608
+ }