@dust-tt/sparkle 0.7.0 → 0.7.2
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/dist/cjs/index.js +10 -10
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/components/Citation.d.ts +7 -1
- package/dist/esm/components/Citation.d.ts.map +1 -1
- package/dist/esm/components/Citation.js +24 -13
- package/dist/esm/components/Citation.js.map +1 -1
- package/dist/esm/components/ImagePreview.d.ts +28 -0
- package/dist/esm/components/ImagePreview.d.ts.map +1 -0
- package/dist/esm/components/ImagePreview.js +100 -0
- package/dist/esm/components/ImagePreview.js.map +1 -0
- package/dist/esm/components/ImageZoomDialog.d.ts +26 -0
- package/dist/esm/components/ImageZoomDialog.d.ts.map +1 -0
- package/dist/esm/components/ImageZoomDialog.js +46 -0
- package/dist/esm/components/ImageZoomDialog.js.map +1 -0
- package/dist/esm/components/InteractiveImageGrid.d.ts.map +1 -1
- package/dist/esm/components/InteractiveImageGrid.js +25 -67
- package/dist/esm/components/InteractiveImageGrid.js.map +1 -1
- package/dist/esm/components/index.d.ts +5 -1
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/esm/lottie/dragArea.d.ts +32 -32
- package/dist/esm/lottie/spinnerColor.d.ts +64 -64
- package/dist/esm/lottie/spinnerColorLG.d.ts +133 -133
- package/dist/esm/lottie/spinnerColorXS.d.ts +11 -11
- package/dist/esm/lottie/spinnerDark.d.ts +72 -72
- package/dist/esm/lottie/spinnerDarkLG.d.ts +134 -134
- package/dist/esm/lottie/spinnerDarkXS.d.ts +22 -22
- package/dist/esm/lottie/spinnerLight.d.ts +9 -9
- package/dist/esm/lottie/spinnerLightLG.d.ts +10 -10
- package/dist/esm/lottie/spinnerLightXS.d.ts +22 -22
- package/dist/esm/stories/Citation.stories.d.ts.map +1 -1
- package/dist/esm/stories/Citation.stories.js +7 -15
- package/dist/esm/stories/Citation.stories.js.map +1 -1
- package/dist/esm/stories/ImagePreview.stories.d.ts +45 -0
- package/dist/esm/stories/ImagePreview.stories.d.ts.map +1 -0
- package/dist/esm/stories/ImagePreview.stories.js +83 -0
- package/dist/esm/stories/ImagePreview.stories.js.map +1 -0
- package/dist/sparkle.css +28 -28
- package/package.json +1 -1
- package/src/components/Citation.tsx +52 -41
- package/src/components/ImagePreview.tsx +244 -0
- package/src/components/ImageZoomDialog.tsx +151 -0
- package/src/components/InteractiveImageGrid.tsx +60 -267
- package/src/components/index.ts +16 -1
|
@@ -1,140 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
Dialog,
|
|
6
|
-
DialogClose,
|
|
7
|
-
DialogContent,
|
|
8
|
-
DialogTrigger,
|
|
9
|
-
Spinner,
|
|
10
|
-
} from "@sparkle/components/";
|
|
11
|
-
import {
|
|
12
|
-
ArrowDownOnSquareIcon,
|
|
13
|
-
ChevronLeftIcon,
|
|
14
|
-
ChevronRightIcon,
|
|
15
|
-
XMarkIcon,
|
|
16
|
-
} from "@sparkle/icons/app";
|
|
3
|
+
import { ImagePreview } from "@sparkle/components/ImagePreview";
|
|
4
|
+
import { ImageZoomDialog } from "@sparkle/components/ImageZoomDialog";
|
|
17
5
|
import { cn } from "@sparkle/lib/utils";
|
|
18
6
|
|
|
19
|
-
interface ImageLoadingStateProps {
|
|
20
|
-
className?: string;
|
|
21
|
-
size?: "sm" | "md" | "lg";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const ImageLoadingState = React.forwardRef<
|
|
25
|
-
HTMLDivElement,
|
|
26
|
-
ImageLoadingStateProps
|
|
27
|
-
>(({ className, size = "lg" }, ref) => {
|
|
28
|
-
return (
|
|
29
|
-
<div
|
|
30
|
-
ref={ref}
|
|
31
|
-
className={cn(
|
|
32
|
-
"s-mx-auto s-flex s-aspect-square s-w-full s-min-w-[50vh]",
|
|
33
|
-
"s-max-w-[80vh] s-items-center s-justify-center",
|
|
34
|
-
"s-bg-muted-background dark:s-bg-muted-background-night",
|
|
35
|
-
className
|
|
36
|
-
)}
|
|
37
|
-
>
|
|
38
|
-
<Spinner variant="dark" size={size} />
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
ImageLoadingState.displayName = "ImageLoadingState";
|
|
44
|
-
interface ImagePreviewProps {
|
|
45
|
-
image: {
|
|
46
|
-
alt: string;
|
|
47
|
-
downloadUrl?: string;
|
|
48
|
-
imageUrl?: string;
|
|
49
|
-
isLoading?: boolean;
|
|
50
|
-
title: string;
|
|
51
|
-
};
|
|
52
|
-
onClick: () => void;
|
|
53
|
-
onDownload: (e: React.MouseEvent) => Promise<void>;
|
|
54
|
-
onClose?: (e: React.MouseEvent) => void;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const ImagePreview = React.forwardRef<HTMLDivElement, ImagePreviewProps>(
|
|
58
|
-
({ image, onClick, onDownload, onClose }, ref) => {
|
|
59
|
-
return (
|
|
60
|
-
<div
|
|
61
|
-
ref={ref}
|
|
62
|
-
onClick={onClick}
|
|
63
|
-
className={cn(
|
|
64
|
-
"s-group/preview s-relative s-aspect-square",
|
|
65
|
-
"s-cursor-pointer s-overflow-hidden s-rounded-2xl",
|
|
66
|
-
"s-bg-muted-background dark:s-bg-muted-background-night"
|
|
67
|
-
)}
|
|
68
|
-
>
|
|
69
|
-
{image.isLoading ? (
|
|
70
|
-
<div className="s-flex s-h-full s-w-full s-items-center s-justify-center">
|
|
71
|
-
<Spinner variant="dark" size="md" />
|
|
72
|
-
</div>
|
|
73
|
-
) : (
|
|
74
|
-
<>
|
|
75
|
-
<img
|
|
76
|
-
src={image.imageUrl}
|
|
77
|
-
alt={image.alt}
|
|
78
|
-
className="s-h-full s-w-full s-rounded-2xl s-object-cover"
|
|
79
|
-
/>
|
|
80
|
-
{/* Blur overlay with filename - hidden by default, shown on hover */}
|
|
81
|
-
<div
|
|
82
|
-
className={cn(
|
|
83
|
-
"s-absolute s-inset-0 s-z-10",
|
|
84
|
-
"s-flex s-items-center s-justify-center",
|
|
85
|
-
"s-bg-primary-100/80 dark:s-bg-primary-100-night/80",
|
|
86
|
-
"s-backdrop-blur-sm",
|
|
87
|
-
"s-opacity-0 s-transition s-duration-200",
|
|
88
|
-
"group-hover/preview:s-opacity-100"
|
|
89
|
-
)}
|
|
90
|
-
>
|
|
91
|
-
<span
|
|
92
|
-
className={cn(
|
|
93
|
-
"s-max-w-[90%] s-truncate s-px-2 s-text-center",
|
|
94
|
-
"s-text-sm s-font-medium",
|
|
95
|
-
"s-text-foreground dark:s-text-foreground-night"
|
|
96
|
-
)}
|
|
97
|
-
>
|
|
98
|
-
{image.title}
|
|
99
|
-
</span>
|
|
100
|
-
</div>
|
|
101
|
-
<div
|
|
102
|
-
className={cn(
|
|
103
|
-
"s-absolute s-right-1 s-top-1 s-z-10 s-flex",
|
|
104
|
-
"s-opacity-0 s-transition-opacity s-duration-200",
|
|
105
|
-
"group-hover/preview:s-opacity-100"
|
|
106
|
-
)}
|
|
107
|
-
>
|
|
108
|
-
{onClose && (
|
|
109
|
-
<Button
|
|
110
|
-
variant="ghost"
|
|
111
|
-
size="xs"
|
|
112
|
-
icon={XMarkIcon}
|
|
113
|
-
className="s-text-white dark:s-text-white"
|
|
114
|
-
tooltip="Remove"
|
|
115
|
-
onClick={onClose}
|
|
116
|
-
/>
|
|
117
|
-
)}
|
|
118
|
-
{!onClose && (
|
|
119
|
-
<Button
|
|
120
|
-
variant="ghost"
|
|
121
|
-
size="xs"
|
|
122
|
-
icon={ArrowDownOnSquareIcon}
|
|
123
|
-
className="s-text-white dark:s-text-white"
|
|
124
|
-
tooltip="Download"
|
|
125
|
-
onClick={onDownload}
|
|
126
|
-
/>
|
|
127
|
-
)}
|
|
128
|
-
</div>
|
|
129
|
-
</>
|
|
130
|
-
)}
|
|
131
|
-
</div>
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
ImagePreview.displayName = "ImagePreview";
|
|
137
|
-
|
|
138
7
|
const SIZE_CLASSES = {
|
|
139
8
|
sm: "s-h-24 s-w-24",
|
|
140
9
|
md: "s-h-48 s-w-48",
|
|
@@ -165,7 +34,6 @@ function InteractiveImageGrid({
|
|
|
165
34
|
const [currentImageIndex, setCurrentImageIndex] = React.useState<
|
|
166
35
|
number | null
|
|
167
36
|
>(null);
|
|
168
|
-
const [imageLoaded, setImageLoaded] = React.useState(false);
|
|
169
37
|
|
|
170
38
|
const handleNext = React.useCallback(() => {
|
|
171
39
|
if (currentImageIndex === null) {
|
|
@@ -183,29 +51,12 @@ function InteractiveImageGrid({
|
|
|
183
51
|
);
|
|
184
52
|
}, [currentImageIndex, images.length]);
|
|
185
53
|
|
|
186
|
-
|
|
187
|
-
async (downloadUrl?: string, title?: string) => {
|
|
188
|
-
if (!downloadUrl || !title) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Create a hidden link and click it.
|
|
193
|
-
const link = document.createElement("a");
|
|
194
|
-
link.href = downloadUrl;
|
|
195
|
-
link.download = title;
|
|
196
|
-
document.body.appendChild(link);
|
|
197
|
-
link.click();
|
|
198
|
-
document.body.removeChild(link);
|
|
199
|
-
},
|
|
200
|
-
[]
|
|
201
|
-
);
|
|
202
|
-
|
|
54
|
+
// Keyboard navigation for the zoomed image
|
|
203
55
|
React.useEffect(() => {
|
|
204
56
|
if (currentImageIndex === null) {
|
|
205
57
|
return;
|
|
206
58
|
}
|
|
207
59
|
|
|
208
|
-
// Only handle keyboard events if the image is zoomed.
|
|
209
60
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
210
61
|
if (e.key === "ArrowRight") {
|
|
211
62
|
handleNext();
|
|
@@ -218,126 +69,68 @@ function InteractiveImageGrid({
|
|
|
218
69
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
219
70
|
}, [currentImageIndex, handleNext, handlePrevious]);
|
|
220
71
|
|
|
72
|
+
const currentImage =
|
|
73
|
+
currentImageIndex !== null ? images[currentImageIndex] : null;
|
|
74
|
+
|
|
221
75
|
return (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
76
|
+
<>
|
|
77
|
+
<div className={cn("s-@container", className)}>
|
|
78
|
+
{images.length === 1 ? (
|
|
79
|
+
<div className={SIZE_CLASSES[size]}>
|
|
80
|
+
<ImagePreview
|
|
81
|
+
imgSrc={images[0].imageUrl ?? ""}
|
|
82
|
+
alt={images[0].alt}
|
|
83
|
+
title={images[0].title}
|
|
84
|
+
downloadUrl={images[0].downloadUrl}
|
|
85
|
+
isLoading={images[0].isLoading}
|
|
86
|
+
onClick={() => setCurrentImageIndex(0)}
|
|
87
|
+
onClose={onClose ? () => onClose() : undefined}
|
|
88
|
+
variant="embedded"
|
|
89
|
+
titlePosition="center"
|
|
90
|
+
manageZoomDialog={false}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
) : (
|
|
94
|
+
<div className="s-grid s-grid-cols-2 s-gap-2 @xxs:s-grid-cols-3 @xs:s-grid-cols-4">
|
|
95
|
+
{images.map((image, idx) => (
|
|
230
96
|
<ImagePreview
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
? (e) => {
|
|
242
|
-
e.stopPropagation();
|
|
243
|
-
onClose();
|
|
244
|
-
}
|
|
245
|
-
: undefined
|
|
246
|
-
}
|
|
247
|
-
/>
|
|
248
|
-
</div>
|
|
249
|
-
) : (
|
|
250
|
-
<div className="s-grid s-grid-cols-2 s-gap-2 @xxs:s-grid-cols-3 @xs:s-grid-cols-4">
|
|
251
|
-
{images.map((image, idx) => (
|
|
252
|
-
<ImagePreview
|
|
253
|
-
key={idx}
|
|
254
|
-
image={image}
|
|
255
|
-
onClick={() => {
|
|
256
|
-
setCurrentImageIndex(idx);
|
|
257
|
-
}}
|
|
258
|
-
onDownload={async (e) => {
|
|
259
|
-
e.stopPropagation();
|
|
260
|
-
await handleDownload(image.downloadUrl, image.title);
|
|
261
|
-
}}
|
|
262
|
-
/>
|
|
263
|
-
))}
|
|
264
|
-
</div>
|
|
265
|
-
)}
|
|
266
|
-
</div>
|
|
267
|
-
</DialogTrigger>
|
|
268
|
-
<DialogContent size="xl" className="s-max-w-[90vw] s-overflow-hidden s-p-3">
|
|
269
|
-
{currentImageIndex !== null && (
|
|
270
|
-
<div className="s-relative s-flex s-items-center s-justify-center s-gap-2">
|
|
271
|
-
{/* Previous button */}
|
|
272
|
-
{images.length > 1 && (
|
|
273
|
-
<Button
|
|
274
|
-
variant="ghost"
|
|
275
|
-
size="sm"
|
|
276
|
-
icon={ChevronLeftIcon}
|
|
277
|
-
onClick={(e) => {
|
|
278
|
-
e.stopPropagation();
|
|
279
|
-
handlePrevious();
|
|
280
|
-
}}
|
|
97
|
+
key={idx}
|
|
98
|
+
imgSrc={image.imageUrl ?? ""}
|
|
99
|
+
alt={image.alt}
|
|
100
|
+
title={image.title}
|
|
101
|
+
downloadUrl={image.downloadUrl}
|
|
102
|
+
isLoading={image.isLoading}
|
|
103
|
+
onClick={() => setCurrentImageIndex(idx)}
|
|
104
|
+
variant="embedded"
|
|
105
|
+
titlePosition="center"
|
|
106
|
+
manageZoomDialog={false}
|
|
281
107
|
/>
|
|
282
|
-
)}
|
|
283
|
-
|
|
284
|
-
{/* Image container with overlay buttons */}
|
|
285
|
-
<div className="s-relative">
|
|
286
|
-
{images[currentImageIndex].isLoading ? (
|
|
287
|
-
<ImageLoadingState size="lg" />
|
|
288
|
-
) : (
|
|
289
|
-
<>
|
|
290
|
-
<img
|
|
291
|
-
src={images[currentImageIndex].imageUrl}
|
|
292
|
-
alt={images[currentImageIndex].alt}
|
|
293
|
-
className="s-max-h-full s-max-w-full s-rounded-lg s-object-contain"
|
|
294
|
-
onLoad={() => setImageLoaded(true)}
|
|
295
|
-
/>
|
|
296
|
-
{/* Close button - top right of image */}
|
|
297
|
-
<DialogClose asChild>
|
|
298
|
-
<Button
|
|
299
|
-
variant="outline"
|
|
300
|
-
size="xs"
|
|
301
|
-
icon={XMarkIcon}
|
|
302
|
-
className="s-absolute s-right-2 s-top-2"
|
|
303
|
-
/>
|
|
304
|
-
</DialogClose>
|
|
305
|
-
{/* Download button - bottom right of image */}
|
|
306
|
-
{imageLoaded && (
|
|
307
|
-
<Button
|
|
308
|
-
variant="outline"
|
|
309
|
-
size="xs"
|
|
310
|
-
icon={ArrowDownOnSquareIcon}
|
|
311
|
-
tooltip="Download"
|
|
312
|
-
className="s-absolute s-bottom-2 s-right-2"
|
|
313
|
-
onClick={async () => {
|
|
314
|
-
await handleDownload(
|
|
315
|
-
images[currentImageIndex].downloadUrl,
|
|
316
|
-
images[currentImageIndex].title
|
|
317
|
-
);
|
|
318
|
-
}}
|
|
319
|
-
/>
|
|
320
|
-
)}
|
|
321
|
-
</>
|
|
322
|
-
)}
|
|
323
|
-
</div>
|
|
324
|
-
|
|
325
|
-
{/* Next button */}
|
|
326
|
-
{images.length > 1 && (
|
|
327
|
-
<Button
|
|
328
|
-
variant="ghost"
|
|
329
|
-
size="sm"
|
|
330
|
-
icon={ChevronRightIcon}
|
|
331
|
-
onClick={(e) => {
|
|
332
|
-
e.stopPropagation();
|
|
333
|
-
handleNext();
|
|
334
|
-
}}
|
|
335
|
-
/>
|
|
336
|
-
)}
|
|
108
|
+
))}
|
|
337
109
|
</div>
|
|
338
110
|
)}
|
|
339
|
-
</
|
|
340
|
-
|
|
111
|
+
</div>
|
|
112
|
+
<ImageZoomDialog
|
|
113
|
+
open={currentImageIndex !== null}
|
|
114
|
+
onOpenChange={(open) => !open && setCurrentImageIndex(null)}
|
|
115
|
+
image={{
|
|
116
|
+
src: currentImage?.imageUrl ?? "",
|
|
117
|
+
alt: currentImage?.alt,
|
|
118
|
+
title: currentImage?.title,
|
|
119
|
+
downloadUrl: currentImage?.downloadUrl,
|
|
120
|
+
isLoading: currentImage?.isLoading,
|
|
121
|
+
}}
|
|
122
|
+
navigation={
|
|
123
|
+
images.length > 1
|
|
124
|
+
? {
|
|
125
|
+
onPrevious: handlePrevious,
|
|
126
|
+
onNext: handleNext,
|
|
127
|
+
hasPrevious: true,
|
|
128
|
+
hasNext: true,
|
|
129
|
+
}
|
|
130
|
+
: undefined
|
|
131
|
+
}
|
|
132
|
+
/>
|
|
133
|
+
</>
|
|
341
134
|
);
|
|
342
135
|
}
|
|
343
136
|
|
package/src/components/index.ts
CHANGED
|
@@ -44,7 +44,10 @@ export {
|
|
|
44
44
|
ContentMessageInline,
|
|
45
45
|
} from "./ContentMessage";
|
|
46
46
|
export { ContextItem } from "./ContextItem";
|
|
47
|
-
export type {
|
|
47
|
+
export type {
|
|
48
|
+
ConversationListItemProps,
|
|
49
|
+
ReplySectionProps,
|
|
50
|
+
} from "./ConversationListItem";
|
|
48
51
|
export { ConversationListItem, ReplySection } from "./ConversationListItem";
|
|
49
52
|
export {
|
|
50
53
|
ConversationContainer,
|
|
@@ -115,6 +118,18 @@ export { Div3D, Hover3D } from "./Hover3D";
|
|
|
115
118
|
export { Hoverable } from "./Hoverable";
|
|
116
119
|
export { DoubleIcon, Icon } from "./Icon";
|
|
117
120
|
export { IconButton } from "./IconButton";
|
|
121
|
+
export type {
|
|
122
|
+
ImagePreviewProps,
|
|
123
|
+
ImagePreviewTitlePositionType,
|
|
124
|
+
ImagePreviewVariantType,
|
|
125
|
+
} from "./ImagePreview";
|
|
126
|
+
export {
|
|
127
|
+
IMAGE_PREVIEW_TITLE_POSITIONS,
|
|
128
|
+
IMAGE_PREVIEW_VARIANTS,
|
|
129
|
+
ImagePreview,
|
|
130
|
+
} from "./ImagePreview";
|
|
131
|
+
export type { ImageZoomDialogProps } from "./ImageZoomDialog";
|
|
132
|
+
export { downloadFile, ImageZoomDialog } from "./ImageZoomDialog";
|
|
118
133
|
export { Input } from "./Input";
|
|
119
134
|
export { InteractiveImageGrid } from "./InteractiveImageGrid";
|
|
120
135
|
export { Label } from "./Label";
|