@boyernick/standard-ui-react 0.1.1-canary.19 → 0.1.1-canary.20
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/package.json +1 -1
- package/src/breadcrumb.tsx +3 -3
- package/src/command.tsx +9 -0
- package/src/icons.tsx +2 -0
- package/src/image-modal.tsx +440 -49
- package/src/index.ts +9 -0
package/package.json
CHANGED
package/src/breadcrumb.tsx
CHANGED
|
@@ -14,7 +14,7 @@ const breadcrumbSeparatorVariants = cva("text-fg-tertiary", {
|
|
|
14
14
|
variants: {
|
|
15
15
|
variant: {
|
|
16
16
|
chevron: "[&_svg]:size-3.5",
|
|
17
|
-
slash: "
|
|
17
|
+
slash: "",
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultVariants: {
|
|
@@ -39,7 +39,7 @@ export const BreadcrumbList = ({
|
|
|
39
39
|
...props
|
|
40
40
|
}: BreadcrumbListProps) => (
|
|
41
41
|
<ol
|
|
42
|
-
className={cn("flex flex-wrap items-center gap-
|
|
42
|
+
className={cn("flex flex-wrap items-center gap-x-2 gap-y-1.5", className)}
|
|
43
43
|
{...props}
|
|
44
44
|
/>
|
|
45
45
|
)
|
|
@@ -49,7 +49,7 @@ export const BreadcrumbItem = ({
|
|
|
49
49
|
...props
|
|
50
50
|
}: BreadcrumbItemProps) => (
|
|
51
51
|
<li
|
|
52
|
-
className={cn("inline-flex items-center gap-1.5
|
|
52
|
+
className={cn("inline-flex items-center gap-1.5", className)}
|
|
53
53
|
{...props}
|
|
54
54
|
/>
|
|
55
55
|
)
|
package/src/command.tsx
CHANGED
|
@@ -384,6 +384,15 @@ export const CommandTitle = ({ className, children }: CommandTitleProps) => (
|
|
|
384
384
|
<span className={cn("min-w-0 truncate", className)}>{children}</span>
|
|
385
385
|
)
|
|
386
386
|
|
|
387
|
+
export type CommandMetaProps = HTMLAttributes<HTMLSpanElement>
|
|
388
|
+
|
|
389
|
+
export const CommandMeta = ({ className, ...props }: CommandMetaProps) => (
|
|
390
|
+
<span
|
|
391
|
+
className={cn("shrink-0 text-xs font-normal text-fg-tertiary", className)}
|
|
392
|
+
{...props}
|
|
393
|
+
/>
|
|
394
|
+
)
|
|
395
|
+
|
|
387
396
|
export const CommandDialogTitle = ({
|
|
388
397
|
className,
|
|
389
398
|
...props
|
package/src/icons.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import type { CentralIconBaseProps } from "@central-icons-react/round-outlined-radius-2-stroke-2/CentralIconBase"
|
|
4
|
+
import { IconArrowDown as IconArrowDownBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconArrowDown"
|
|
4
5
|
import { IconBell as IconBellBase } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconBell"
|
|
5
6
|
import { IconCalendar1 as IconCalendar1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconCalendar1"
|
|
6
7
|
import { IconChainLink1 as IconChainLink1Base } from "@central-icons-react/round-outlined-radius-2-stroke-2/IconChainLink1"
|
|
@@ -54,6 +55,7 @@ export const withCentralIconDefaults = (
|
|
|
54
55
|
return CentralIcon
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
export const IconArrowDown = withCentralIconDefaults(IconArrowDownBase)
|
|
57
59
|
export const IconBell = withCentralIconDefaults(IconBellBase)
|
|
58
60
|
export const IconCalendar1 = withCentralIconDefaults(IconCalendar1Base)
|
|
59
61
|
export const IconChainLink1 = withCentralIconDefaults(IconChainLink1Base)
|
package/src/image-modal.tsx
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useState,
|
|
10
|
+
type ComponentProps,
|
|
11
|
+
type ImgHTMLAttributes,
|
|
12
|
+
type KeyboardEvent,
|
|
13
|
+
type ReactElement,
|
|
14
|
+
type ReactNode,
|
|
15
|
+
} from "react"
|
|
16
|
+
import { Button, buttonVariants } from "./button"
|
|
17
|
+
import {
|
|
18
|
+
Carousel,
|
|
19
|
+
CarouselContent,
|
|
20
|
+
CarouselItem,
|
|
21
|
+
type CarouselApi,
|
|
22
|
+
} from "./carousel"
|
|
5
23
|
import {
|
|
6
24
|
Dialog,
|
|
7
25
|
DialogBackdrop,
|
|
@@ -11,18 +29,103 @@ import {
|
|
|
11
29
|
DialogTitle,
|
|
12
30
|
DialogTrigger,
|
|
13
31
|
} from "./dialog"
|
|
32
|
+
import {
|
|
33
|
+
IconArrowDown,
|
|
34
|
+
IconChevronRightSmall,
|
|
35
|
+
IconCrossSmall,
|
|
36
|
+
} from "./icons"
|
|
14
37
|
import { cn } from "./lib/cn"
|
|
15
38
|
import { motion } from "./lib/motion"
|
|
39
|
+
import {
|
|
40
|
+
Tooltip,
|
|
41
|
+
TooltipPopup,
|
|
42
|
+
TooltipPortal,
|
|
43
|
+
TooltipPositioner,
|
|
44
|
+
TooltipProvider,
|
|
45
|
+
TooltipTrigger,
|
|
46
|
+
type TooltipPositionerProps,
|
|
47
|
+
} from "./tooltip"
|
|
48
|
+
|
|
49
|
+
const TRACKPAD_LABEL =
|
|
50
|
+
"Swipe, use the arrow keys, or use the controls to navigate"
|
|
51
|
+
|
|
52
|
+
const imageModalControlClassName =
|
|
53
|
+
"border-border-inverted bg-surface-inverted/60 text-fg-inverted shadow-md backdrop-blur-md hover:bg-surface-inverted/80 hover:text-fg-inverted focus-visible:border-ring focus-visible:ring-border-inverted focus-visible:ring-offset-surface-inverted"
|
|
54
|
+
|
|
55
|
+
export const imageModalContentVariants = cva(
|
|
56
|
+
"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",
|
|
57
|
+
{
|
|
58
|
+
variants: {
|
|
59
|
+
variant: {
|
|
60
|
+
default: "[&_[data-slot=image-modal-stage]]:pb-0",
|
|
61
|
+
caption: "[&_[data-slot=image-modal-stage]]:pb-20",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
defaultVariants: {
|
|
65
|
+
variant: "default",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
)
|
|
16
69
|
|
|
17
70
|
export type ImageModalProps = ComponentProps<typeof Dialog>
|
|
18
71
|
export type ImageModalTriggerProps = ComponentProps<typeof DialogTrigger>
|
|
19
|
-
export type ImageModalContentProps =
|
|
72
|
+
export type ImageModalContentProps = Omit<
|
|
73
|
+
ComponentProps<typeof DialogPopup>,
|
|
74
|
+
"children"
|
|
75
|
+
> &
|
|
76
|
+
VariantProps<typeof imageModalContentVariants> & {
|
|
77
|
+
src: string
|
|
78
|
+
alt: string
|
|
79
|
+
caption?: ReactNode
|
|
80
|
+
downloadSrc?: string
|
|
81
|
+
downloadName?: string
|
|
82
|
+
imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">
|
|
83
|
+
children?: ReactNode
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type ImageModalItem = {
|
|
20
87
|
src: string
|
|
21
88
|
alt: string
|
|
22
|
-
caption?:
|
|
89
|
+
caption?: ReactNode
|
|
90
|
+
downloadSrc?: string
|
|
91
|
+
downloadName?: string
|
|
23
92
|
imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">
|
|
24
93
|
}
|
|
25
94
|
|
|
95
|
+
export type ImageModalGalleryProps = Omit<ImageModalProps, "children"> &
|
|
96
|
+
VariantProps<typeof imageModalContentVariants> & {
|
|
97
|
+
images: readonly ImageModalItem[]
|
|
98
|
+
children: ReactNode
|
|
99
|
+
loop?: boolean
|
|
100
|
+
contentClassName?: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type ImageModalGalleryTriggerProps = ImageModalTriggerProps & {
|
|
104
|
+
index: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type ImageModalTriggerClickEvent = Parameters<
|
|
108
|
+
NonNullable<ImageModalTriggerProps["onClick"]>
|
|
109
|
+
>[0]
|
|
110
|
+
|
|
111
|
+
type ImageModalGalleryContextValue = {
|
|
112
|
+
images: readonly ImageModalItem[]
|
|
113
|
+
setActiveIndex: (index: number) => void
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ImageModalGalleryContext =
|
|
117
|
+
createContext<ImageModalGalleryContextValue | null>(null)
|
|
118
|
+
|
|
119
|
+
const useImageModalGallery = () => {
|
|
120
|
+
const context = useContext(ImageModalGalleryContext)
|
|
121
|
+
if (!context) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"ImageModalGalleryTrigger must be used within <ImageModalGallery>",
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
return context
|
|
127
|
+
}
|
|
128
|
+
|
|
26
129
|
export const ImageModal = (props: ImageModalProps) => <Dialog {...props} />
|
|
27
130
|
|
|
28
131
|
export const ImageModalTrigger = ({
|
|
@@ -38,73 +141,361 @@ export const ImageModalTrigger = ({
|
|
|
38
141
|
/>
|
|
39
142
|
)
|
|
40
143
|
|
|
144
|
+
const ImageModalBackdrop = () => (
|
|
145
|
+
<DialogBackdrop className="bg-surface-inverted/95" />
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
const ImageModalBackground = ({ src }: { src: string }) => (
|
|
149
|
+
<div
|
|
150
|
+
aria-hidden="true"
|
|
151
|
+
className="pointer-events-none absolute inset-0 overflow-hidden"
|
|
152
|
+
>
|
|
153
|
+
{/* Consumers may use any image host, so this package deliberately renders
|
|
154
|
+
the platform-neutral image element rather than Next.js Image. */}
|
|
155
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
156
|
+
<img
|
|
157
|
+
src={src}
|
|
158
|
+
alt=""
|
|
159
|
+
className="size-full scale-110 object-cover opacity-25 blur-3xl"
|
|
160
|
+
/>
|
|
161
|
+
<div className="absolute inset-0 bg-surface-inverted/65" />
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const ImageModalImage = ({
|
|
166
|
+
src,
|
|
167
|
+
alt,
|
|
168
|
+
imgProps,
|
|
169
|
+
}: Pick<ImageModalItem, "src" | "alt" | "imgProps">) => {
|
|
170
|
+
const { className, ...props } = imgProps ?? {}
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
174
|
+
<img
|
|
175
|
+
src={src}
|
|
176
|
+
alt={alt}
|
|
177
|
+
draggable={false}
|
|
178
|
+
className={cn(
|
|
179
|
+
"max-h-[82dvh] max-w-[88vw] rounded-lg object-contain shadow-lg select-none max-sm:max-h-[calc(100dvh-2rem)] max-sm:max-w-[calc(100vw-2rem)]",
|
|
180
|
+
className,
|
|
181
|
+
)}
|
|
182
|
+
{...props}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const ImageModalControlTooltip = ({
|
|
188
|
+
label,
|
|
189
|
+
side = "bottom",
|
|
190
|
+
children,
|
|
191
|
+
}: {
|
|
192
|
+
label: string
|
|
193
|
+
side?: TooltipPositionerProps["side"]
|
|
194
|
+
children: ReactElement
|
|
195
|
+
}) => (
|
|
196
|
+
<Tooltip>
|
|
197
|
+
<TooltipTrigger render={children} />
|
|
198
|
+
<TooltipPortal>
|
|
199
|
+
<TooltipPositioner side={side}>
|
|
200
|
+
<TooltipPopup variant="inverted">{label}</TooltipPopup>
|
|
201
|
+
</TooltipPositioner>
|
|
202
|
+
</TooltipPortal>
|
|
203
|
+
</Tooltip>
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
const ImageModalDownload = ({
|
|
207
|
+
src,
|
|
208
|
+
name,
|
|
209
|
+
}: {
|
|
210
|
+
src: string
|
|
211
|
+
name?: string
|
|
212
|
+
}) => (
|
|
213
|
+
<ImageModalControlTooltip label="Download image">
|
|
214
|
+
<a
|
|
215
|
+
href={src}
|
|
216
|
+
download={name ?? ""}
|
|
217
|
+
aria-label="Download image"
|
|
218
|
+
className={cn(
|
|
219
|
+
buttonVariants({
|
|
220
|
+
variant: "ghost",
|
|
221
|
+
size: "md",
|
|
222
|
+
rounded: true,
|
|
223
|
+
iconOnly: true,
|
|
224
|
+
}),
|
|
225
|
+
imageModalControlClassName,
|
|
226
|
+
"absolute top-4 right-4 z-20",
|
|
227
|
+
)}
|
|
228
|
+
>
|
|
229
|
+
<IconArrowDown aria-hidden />
|
|
230
|
+
</a>
|
|
231
|
+
</ImageModalControlTooltip>
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
const ImageModalDismiss = () => (
|
|
235
|
+
<ImageModalControlTooltip label="Close image">
|
|
236
|
+
<DialogClose
|
|
237
|
+
render={
|
|
238
|
+
<Button
|
|
239
|
+
type="button"
|
|
240
|
+
variant="ghost"
|
|
241
|
+
size="md"
|
|
242
|
+
iconOnly
|
|
243
|
+
rounded
|
|
244
|
+
aria-label="Close image"
|
|
245
|
+
className={cn(
|
|
246
|
+
imageModalControlClassName,
|
|
247
|
+
"absolute top-4 left-4 z-20",
|
|
248
|
+
)}
|
|
249
|
+
/>
|
|
250
|
+
}
|
|
251
|
+
>
|
|
252
|
+
<IconCrossSmall aria-hidden />
|
|
253
|
+
</DialogClose>
|
|
254
|
+
</ImageModalControlTooltip>
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
const ImageModalCaption = ({ children }: { children: ReactNode }) => (
|
|
258
|
+
<div className="absolute inset-x-0 bottom-0 z-10 flex min-h-20 items-center justify-center bg-surface-inverted/65 px-6 py-4 text-center text-sm text-fg-inverted-secondary backdrop-blur-md">
|
|
259
|
+
{children}
|
|
260
|
+
</div>
|
|
261
|
+
)
|
|
262
|
+
|
|
41
263
|
export const ImageModalContent = ({
|
|
42
264
|
src,
|
|
43
265
|
alt,
|
|
44
266
|
caption,
|
|
267
|
+
downloadSrc,
|
|
268
|
+
downloadName,
|
|
269
|
+
variant = "default",
|
|
45
270
|
className,
|
|
46
271
|
imgProps,
|
|
47
272
|
children,
|
|
48
273
|
...props
|
|
49
274
|
}: ImageModalContentProps) => (
|
|
50
275
|
<DialogPortal>
|
|
51
|
-
<
|
|
276
|
+
<ImageModalBackdrop />
|
|
52
277
|
<DialogPopup
|
|
53
278
|
data-slot="image-modal-content"
|
|
54
279
|
className={cn(
|
|
55
|
-
|
|
280
|
+
imageModalContentVariants({ variant }),
|
|
56
281
|
motion.popupCenter,
|
|
57
282
|
className,
|
|
58
283
|
)}
|
|
59
284
|
{...props}
|
|
60
285
|
>
|
|
61
|
-
<
|
|
286
|
+
<TooltipProvider delay={250}>
|
|
287
|
+
<ImageModalBackground src={src} />
|
|
62
288
|
<DialogTitle className="sr-only">{alt}</DialogTitle>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
alt={alt}
|
|
67
|
-
className={cn(
|
|
68
|
-
"max-h-[min(80vh,56rem)] w-full object-contain bg-background-secondary",
|
|
69
|
-
imgProps?.className,
|
|
70
|
-
)}
|
|
71
|
-
{...imgProps}
|
|
72
|
-
/>
|
|
73
|
-
<DialogClose
|
|
74
|
-
render={
|
|
75
|
-
<Button
|
|
76
|
-
type="button"
|
|
77
|
-
variant="secondary"
|
|
78
|
-
size="sm"
|
|
79
|
-
iconOnly
|
|
80
|
-
rounded
|
|
81
|
-
aria-label="Close"
|
|
82
|
-
className="absolute top-3 right-3"
|
|
83
|
-
/>
|
|
84
|
-
}
|
|
289
|
+
<div
|
|
290
|
+
data-slot="image-modal-stage"
|
|
291
|
+
className="relative z-10 flex size-full items-center justify-center p-4"
|
|
85
292
|
>
|
|
86
|
-
<
|
|
87
|
-
</DialogClose>
|
|
88
|
-
</div>
|
|
89
|
-
{caption || children ? (
|
|
90
|
-
<div className="border-t border-border-primary px-4 py-3">
|
|
91
|
-
{caption ? (
|
|
92
|
-
<p className="text-sm text-fg-secondary">{caption}</p>
|
|
93
|
-
) : null}
|
|
94
|
-
{children}
|
|
293
|
+
<ImageModalImage src={src} alt={alt} imgProps={imgProps} />
|
|
95
294
|
</div>
|
|
96
|
-
|
|
295
|
+
<ImageModalDismiss />
|
|
296
|
+
<ImageModalDownload
|
|
297
|
+
src={downloadSrc ?? src}
|
|
298
|
+
name={downloadName}
|
|
299
|
+
/>
|
|
300
|
+
{variant === "caption" && (caption || children) ? (
|
|
301
|
+
<ImageModalCaption>{caption ?? children}</ImageModalCaption>
|
|
302
|
+
) : null}
|
|
303
|
+
</TooltipProvider>
|
|
97
304
|
</DialogPopup>
|
|
98
305
|
</DialogPortal>
|
|
99
306
|
)
|
|
100
307
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
308
|
+
export const ImageModalGalleryTrigger = ({
|
|
309
|
+
index,
|
|
310
|
+
onClick,
|
|
311
|
+
...props
|
|
312
|
+
}: ImageModalGalleryTriggerProps) => {
|
|
313
|
+
const { images, setActiveIndex } = useImageModalGallery()
|
|
314
|
+
|
|
315
|
+
const handleClick = (event: ImageModalTriggerClickEvent) => {
|
|
316
|
+
onClick?.(event)
|
|
317
|
+
if (!event.defaultPrevented && images[index]) setActiveIndex(index)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return <ImageModalTrigger onClick={handleClick} {...props} />
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const ImageModalGalleryContent = ({
|
|
324
|
+
images,
|
|
325
|
+
activeIndex,
|
|
326
|
+
setActiveIndex,
|
|
327
|
+
loop,
|
|
328
|
+
variant,
|
|
329
|
+
className,
|
|
330
|
+
}: {
|
|
331
|
+
images: readonly ImageModalItem[]
|
|
332
|
+
activeIndex: number
|
|
333
|
+
setActiveIndex: (index: number) => void
|
|
334
|
+
loop: boolean
|
|
335
|
+
variant: "default" | "caption"
|
|
336
|
+
className?: string
|
|
337
|
+
}) => {
|
|
338
|
+
const [api, setApi] = useState<CarouselApi>()
|
|
339
|
+
const activeImage = images[activeIndex]
|
|
340
|
+
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
api?.scrollTo(activeIndex, true)
|
|
343
|
+
}, [activeIndex, api])
|
|
344
|
+
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
if (!api) return
|
|
347
|
+
const syncIndex = () => setActiveIndex(api.selectedScrollSnap())
|
|
348
|
+
api.on("select", syncIndex)
|
|
349
|
+
api.on("reInit", syncIndex)
|
|
350
|
+
return () => {
|
|
351
|
+
api.off("select", syncIndex)
|
|
352
|
+
api.off("reInit", syncIndex)
|
|
353
|
+
}
|
|
354
|
+
}, [api, setActiveIndex])
|
|
355
|
+
|
|
356
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
357
|
+
if (event.key === "ArrowLeft") {
|
|
358
|
+
event.preventDefault()
|
|
359
|
+
api?.scrollPrev()
|
|
360
|
+
} else if (event.key === "ArrowRight") {
|
|
361
|
+
event.preventDefault()
|
|
362
|
+
api?.scrollNext()
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!activeImage) return null
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<DialogPortal>
|
|
370
|
+
<ImageModalBackdrop />
|
|
371
|
+
<DialogPopup
|
|
372
|
+
data-slot="image-modal-gallery-content"
|
|
373
|
+
aria-describedby={undefined}
|
|
374
|
+
onKeyDownCapture={handleKeyDown}
|
|
375
|
+
className={cn(
|
|
376
|
+
imageModalContentVariants({ variant }),
|
|
377
|
+
motion.popupCenter,
|
|
378
|
+
className,
|
|
379
|
+
)}
|
|
380
|
+
>
|
|
381
|
+
<TooltipProvider delay={250}>
|
|
382
|
+
<ImageModalBackground src={activeImage.src} />
|
|
383
|
+
<DialogTitle className="sr-only">{activeImage.alt}</DialogTitle>
|
|
384
|
+
<p className="sr-only" aria-live="polite">
|
|
385
|
+
Image {activeIndex + 1} of {images.length}. {TRACKPAD_LABEL}.
|
|
386
|
+
</p>
|
|
387
|
+
<Carousel
|
|
388
|
+
setApi={setApi}
|
|
389
|
+
opts={{ loop, startIndex: activeIndex, duration: 24 }}
|
|
390
|
+
data-slot="image-modal-stage"
|
|
391
|
+
className="relative z-10 size-full"
|
|
392
|
+
aria-label="Image gallery"
|
|
393
|
+
>
|
|
394
|
+
<CarouselContent className="h-full -ml-0">
|
|
395
|
+
{images.map((image, index) => (
|
|
396
|
+
<CarouselItem
|
|
397
|
+
key={`${image.src}-${index}`}
|
|
398
|
+
className="flex h-full items-center justify-center pl-0 p-4"
|
|
399
|
+
aria-label={`${index + 1} of ${images.length}`}
|
|
400
|
+
>
|
|
401
|
+
<ImageModalImage {...image} />
|
|
402
|
+
</CarouselItem>
|
|
403
|
+
))}
|
|
404
|
+
</CarouselContent>
|
|
405
|
+
</Carousel>
|
|
406
|
+
<ImageModalDismiss />
|
|
407
|
+
<ImageModalDownload
|
|
408
|
+
src={activeImage.downloadSrc ?? activeImage.src}
|
|
409
|
+
name={activeImage.downloadName}
|
|
410
|
+
/>
|
|
411
|
+
{images.length > 1 ? (
|
|
412
|
+
<>
|
|
413
|
+
<ImageModalControlTooltip label="Previous image" side="right">
|
|
414
|
+
<Button
|
|
415
|
+
type="button"
|
|
416
|
+
variant="ghost"
|
|
417
|
+
size="lg"
|
|
418
|
+
iconOnly
|
|
419
|
+
rounded
|
|
420
|
+
aria-label="Previous image"
|
|
421
|
+
disabled={!loop && !api?.canScrollPrev()}
|
|
422
|
+
onClick={() => api?.scrollPrev()}
|
|
423
|
+
className={cn(
|
|
424
|
+
imageModalControlClassName,
|
|
425
|
+
"absolute top-1/2 left-4 z-20 -translate-y-1/2 active:!-translate-y-1/2",
|
|
426
|
+
)}
|
|
427
|
+
>
|
|
428
|
+
<IconChevronRightSmall className="rotate-180" aria-hidden />
|
|
429
|
+
</Button>
|
|
430
|
+
</ImageModalControlTooltip>
|
|
431
|
+
<ImageModalControlTooltip label="Next image" side="left">
|
|
432
|
+
<Button
|
|
433
|
+
type="button"
|
|
434
|
+
variant="ghost"
|
|
435
|
+
size="lg"
|
|
436
|
+
iconOnly
|
|
437
|
+
rounded
|
|
438
|
+
aria-label="Next image"
|
|
439
|
+
disabled={!loop && !api?.canScrollNext()}
|
|
440
|
+
onClick={() => api?.scrollNext()}
|
|
441
|
+
className={cn(
|
|
442
|
+
imageModalControlClassName,
|
|
443
|
+
"absolute top-1/2 right-4 z-20 -translate-y-1/2 active:!-translate-y-1/2",
|
|
444
|
+
)}
|
|
445
|
+
>
|
|
446
|
+
<IconChevronRightSmall aria-hidden />
|
|
447
|
+
</Button>
|
|
448
|
+
</ImageModalControlTooltip>
|
|
449
|
+
</>
|
|
450
|
+
) : null}
|
|
451
|
+
{variant === "caption" && activeImage.caption ? (
|
|
452
|
+
<ImageModalCaption>{activeImage.caption}</ImageModalCaption>
|
|
453
|
+
) : null}
|
|
454
|
+
</TooltipProvider>
|
|
455
|
+
</DialogPopup>
|
|
456
|
+
</DialogPortal>
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export const ImageModalGallery = ({
|
|
461
|
+
images,
|
|
462
|
+
children,
|
|
463
|
+
loop = true,
|
|
464
|
+
variant = "default",
|
|
465
|
+
contentClassName,
|
|
466
|
+
...props
|
|
467
|
+
}: ImageModalGalleryProps) => {
|
|
468
|
+
const [activeIndex, setActiveIndex] = useState(0)
|
|
469
|
+
const safeIndex = images.length
|
|
470
|
+
? Math.min(activeIndex, images.length - 1)
|
|
471
|
+
: 0
|
|
472
|
+
const setSafeActiveIndex = useCallback(
|
|
473
|
+
(index: number) => {
|
|
474
|
+
if (images[index]) setActiveIndex(index)
|
|
475
|
+
},
|
|
476
|
+
[images],
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
return (
|
|
480
|
+
<Dialog {...props}>
|
|
481
|
+
<ImageModalGalleryContext.Provider
|
|
482
|
+
value={{
|
|
483
|
+
images,
|
|
484
|
+
setActiveIndex: setSafeActiveIndex,
|
|
485
|
+
}}
|
|
486
|
+
>
|
|
487
|
+
{children}
|
|
488
|
+
{images.length ? (
|
|
489
|
+
<ImageModalGalleryContent
|
|
490
|
+
images={images}
|
|
491
|
+
activeIndex={safeIndex}
|
|
492
|
+
setActiveIndex={setSafeActiveIndex}
|
|
493
|
+
loop={loop}
|
|
494
|
+
variant={variant ?? "default"}
|
|
495
|
+
className={contentClassName}
|
|
496
|
+
/>
|
|
497
|
+
) : null}
|
|
498
|
+
</ImageModalGalleryContext.Provider>
|
|
499
|
+
</Dialog>
|
|
500
|
+
)
|
|
501
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -172,6 +172,7 @@ export {
|
|
|
172
172
|
CommandItem,
|
|
173
173
|
CommandEmpty,
|
|
174
174
|
CommandTitle,
|
|
175
|
+
CommandMeta,
|
|
175
176
|
CommandDialogTitle,
|
|
176
177
|
type CommandProps,
|
|
177
178
|
type CommandTriggerProps,
|
|
@@ -191,6 +192,7 @@ export {
|
|
|
191
192
|
type CommandItemProps,
|
|
192
193
|
type CommandEmptyProps,
|
|
193
194
|
type CommandTitleProps,
|
|
195
|
+
type CommandMetaProps,
|
|
194
196
|
type CommandDialogTitleProps,
|
|
195
197
|
} from "./command";
|
|
196
198
|
export {
|
|
@@ -873,9 +875,15 @@ export {
|
|
|
873
875
|
ImageModal,
|
|
874
876
|
ImageModalTrigger,
|
|
875
877
|
ImageModalContent,
|
|
878
|
+
ImageModalGallery,
|
|
879
|
+
ImageModalGalleryTrigger,
|
|
880
|
+
imageModalContentVariants,
|
|
876
881
|
type ImageModalProps,
|
|
877
882
|
type ImageModalTriggerProps,
|
|
878
883
|
type ImageModalContentProps,
|
|
884
|
+
type ImageModalGalleryProps,
|
|
885
|
+
type ImageModalGalleryTriggerProps,
|
|
886
|
+
type ImageModalItem,
|
|
879
887
|
} from "./image-modal";
|
|
880
888
|
export {
|
|
881
889
|
Ticker,
|
|
@@ -922,6 +930,7 @@ export {
|
|
|
922
930
|
CENTRAL_ICON_SIZE,
|
|
923
931
|
withCentralIconDefaults,
|
|
924
932
|
iconGallery,
|
|
933
|
+
IconArrowDown,
|
|
925
934
|
IconBell,
|
|
926
935
|
IconCalendar1,
|
|
927
936
|
IconChainLink1,
|