@particle-academy/react-fancy 4.8.1 → 4.9.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/README.md +13 -0
- package/dist/index.cjs +548 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +223 -1
- package/dist/index.d.ts +223 -1
- package/dist/index.js +543 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.css +46 -0
- package/dist/styles.css.map +1 -1
- package/docs/AudioViewer.md +29 -0
- package/docs/ImageViewer.md +60 -0
- package/docs/MediaViewer.md +75 -0
- package/docs/PdfViewer.md +29 -0
- package/docs/VideoViewer.md +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,6 +166,18 @@ npx vite build # Build demo app (verifies imports work)
|
|
|
166
166
|
| Diagram | Entity-relationship diagram with draggable nodes and relation lines | [docs](docs/Diagram.md) |
|
|
167
167
|
| ContentRenderer | Markdown/HTML content renderer | [docs](docs/ContentRenderer.md) |
|
|
168
168
|
|
|
169
|
+
### Media Viewers
|
|
170
|
+
|
|
171
|
+
Standalone, read-only viewers for displaying media files — drop one in anywhere, or use `<MediaViewer>` to pick the right one from a `mime`/`src` when you don't know the type ahead of time (e.g. showing a file from a tree). SVG renders through `ImageViewer`.
|
|
172
|
+
|
|
173
|
+
| Component | Description | Docs |
|
|
174
|
+
|-----------|-------------|------|
|
|
175
|
+
| MediaViewer | Picks image/video/audio/PDF from `mime` (or `src`) + download fallback | [docs](docs/MediaViewer.md) |
|
|
176
|
+
| ImageViewer | Fit-to-container image with zoom/pan and transparency checkerboard | [docs](docs/ImageViewer.md) |
|
|
177
|
+
| VideoViewer | Native video controls, poster, fit | [docs](docs/VideoViewer.md) |
|
|
178
|
+
| AudioViewer | Themed card around the native audio player | [docs](docs/AudioViewer.md) |
|
|
179
|
+
| PdfViewer | Inline PDF via `<object>`/`<iframe>` with download fallback | [docs](docs/PdfViewer.md) |
|
|
180
|
+
|
|
169
181
|
### Human+ Primitives
|
|
170
182
|
|
|
171
183
|
Components for surfaces where humans and AI agents trade control fluidly. Each promoted from the [`dreaming`](https://github.com/Particle-Academy/pa-ux-sandbox/tree/dreaming) sandbox after the API stabilized.
|
|
@@ -191,6 +203,7 @@ Components for surfaces where humans and AI agents trade control fluidly. Each p
|
|
|
191
203
|
|--------|-------------|------|
|
|
192
204
|
| Portal | `createPortal` wrapper with automatic dark mode propagation | [docs](docs/Portal.md) |
|
|
193
205
|
| `cn()` | `clsx` + `tailwind-merge` for conditional class composition | [docs](docs/utilities.md) |
|
|
206
|
+
| `resolveMediaType()` | Resolve `image`/`video`/`audio`/`pdf`/`unknown` from a `mime` and/or `src` | [docs](docs/MediaViewer.md) |
|
|
194
207
|
| Hooks | useControllableState, useFloatingPosition, useOutsideClick, useEscapeKey, useFocusTrap, useAnimation, useId, usePanZoom | [docs](docs/hooks.md) |
|
|
195
208
|
|
|
196
209
|
## Customization
|
package/dist/index.cjs
CHANGED
|
@@ -13026,6 +13026,108 @@ TreeNavRoot.displayName = "TreeNav";
|
|
|
13026
13026
|
var TreeNav = Object.assign(TreeNavRoot, {
|
|
13027
13027
|
Node: TreeNode
|
|
13028
13028
|
});
|
|
13029
|
+
|
|
13030
|
+
// src/utils/media-type.ts
|
|
13031
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set([
|
|
13032
|
+
"png",
|
|
13033
|
+
"jpg",
|
|
13034
|
+
"jpeg",
|
|
13035
|
+
"jfif",
|
|
13036
|
+
"pjpeg",
|
|
13037
|
+
"gif",
|
|
13038
|
+
"webp",
|
|
13039
|
+
"avif",
|
|
13040
|
+
"bmp",
|
|
13041
|
+
"ico",
|
|
13042
|
+
"cur",
|
|
13043
|
+
"svg",
|
|
13044
|
+
"svgz",
|
|
13045
|
+
"apng",
|
|
13046
|
+
"tif",
|
|
13047
|
+
"tiff",
|
|
13048
|
+
"heic",
|
|
13049
|
+
"heif"
|
|
13050
|
+
]);
|
|
13051
|
+
var VIDEO_EXTS = /* @__PURE__ */ new Set([
|
|
13052
|
+
"mp4",
|
|
13053
|
+
"m4v",
|
|
13054
|
+
"webm",
|
|
13055
|
+
"ogv",
|
|
13056
|
+
"ogm",
|
|
13057
|
+
"mov",
|
|
13058
|
+
"mkv",
|
|
13059
|
+
"avi",
|
|
13060
|
+
"wmv",
|
|
13061
|
+
"flv",
|
|
13062
|
+
"3gp",
|
|
13063
|
+
"3g2",
|
|
13064
|
+
"mpg",
|
|
13065
|
+
"mpeg"
|
|
13066
|
+
]);
|
|
13067
|
+
var AUDIO_EXTS = /* @__PURE__ */ new Set([
|
|
13068
|
+
"mp3",
|
|
13069
|
+
"wav",
|
|
13070
|
+
"wave",
|
|
13071
|
+
"ogg",
|
|
13072
|
+
"oga",
|
|
13073
|
+
"m4a",
|
|
13074
|
+
"m4b",
|
|
13075
|
+
"aac",
|
|
13076
|
+
"flac",
|
|
13077
|
+
"opus",
|
|
13078
|
+
"weba",
|
|
13079
|
+
"mid",
|
|
13080
|
+
"midi",
|
|
13081
|
+
"aiff",
|
|
13082
|
+
"aif"
|
|
13083
|
+
]);
|
|
13084
|
+
function mimeToKind(mime) {
|
|
13085
|
+
const m = mime.toLowerCase().split(";")[0].trim();
|
|
13086
|
+
if (!m) return "unknown";
|
|
13087
|
+
if (m === "application/pdf" || m === "application/x-pdf") return "pdf";
|
|
13088
|
+
if (m.startsWith("image/")) return "image";
|
|
13089
|
+
if (m.startsWith("video/")) return "video";
|
|
13090
|
+
if (m.startsWith("audio/")) return "audio";
|
|
13091
|
+
if (m === "application/ogg") return "audio";
|
|
13092
|
+
return "unknown";
|
|
13093
|
+
}
|
|
13094
|
+
function extToKind(ext) {
|
|
13095
|
+
const e = ext.toLowerCase();
|
|
13096
|
+
if (e === "pdf") return "pdf";
|
|
13097
|
+
if (IMAGE_EXTS.has(e)) return "image";
|
|
13098
|
+
if (VIDEO_EXTS.has(e)) return "video";
|
|
13099
|
+
if (AUDIO_EXTS.has(e)) return "audio";
|
|
13100
|
+
return "unknown";
|
|
13101
|
+
}
|
|
13102
|
+
function extFromSrc(src) {
|
|
13103
|
+
const clean = src.split(/[?#]/, 1)[0];
|
|
13104
|
+
const lastSeg = clean.split("/").pop() ?? "";
|
|
13105
|
+
const dot = lastSeg.lastIndexOf(".");
|
|
13106
|
+
if (dot <= 0 || dot === lastSeg.length - 1) return null;
|
|
13107
|
+
return lastSeg.slice(dot + 1);
|
|
13108
|
+
}
|
|
13109
|
+
function mimeFromDataUri(src) {
|
|
13110
|
+
if (!/^data:/i.test(src)) return null;
|
|
13111
|
+
const header = src.slice(5).split(",", 1)[0];
|
|
13112
|
+
const mt = header.split(";")[0].trim();
|
|
13113
|
+
return mt || null;
|
|
13114
|
+
}
|
|
13115
|
+
function resolveMediaType({ mime, src }) {
|
|
13116
|
+
if (mime) {
|
|
13117
|
+
const k = mimeToKind(mime);
|
|
13118
|
+
if (k !== "unknown") return k;
|
|
13119
|
+
}
|
|
13120
|
+
if (src) {
|
|
13121
|
+
const dataMime = mimeFromDataUri(src);
|
|
13122
|
+
if (dataMime) {
|
|
13123
|
+
const k = mimeToKind(dataMime);
|
|
13124
|
+
if (k !== "unknown") return k;
|
|
13125
|
+
}
|
|
13126
|
+
const ext = extFromSrc(src);
|
|
13127
|
+
if (ext) return extToKind(ext);
|
|
13128
|
+
}
|
|
13129
|
+
return "unknown";
|
|
13130
|
+
}
|
|
13029
13131
|
var CONFIDENCE_TIERS = [
|
|
13030
13132
|
{ min: 0.85, color: "#10b981", label: "high" },
|
|
13031
13133
|
{ min: 0.6, color: "#f59e0b", label: "medium" },
|
|
@@ -14298,6 +14400,446 @@ function caretRect(ta, start, end) {
|
|
|
14298
14400
|
const y = taRect.top - parentRect.top + offsetY - ta.scrollTop;
|
|
14299
14401
|
return { x, y };
|
|
14300
14402
|
}
|
|
14403
|
+
var DEFAULT_VIEWPORT = { panX: 0, panY: 0, zoom: 1 };
|
|
14404
|
+
var fitClasses = {
|
|
14405
|
+
contain: "h-full w-full object-contain",
|
|
14406
|
+
cover: "h-full w-full object-cover",
|
|
14407
|
+
none: "max-w-none object-none"
|
|
14408
|
+
};
|
|
14409
|
+
function clamp2(n, min, max) {
|
|
14410
|
+
return Math.min(max, Math.max(min, n));
|
|
14411
|
+
}
|
|
14412
|
+
var ImageViewer = react.forwardRef(
|
|
14413
|
+
({
|
|
14414
|
+
src,
|
|
14415
|
+
alt = "",
|
|
14416
|
+
fit = "contain",
|
|
14417
|
+
zoomable = true,
|
|
14418
|
+
pannable = true,
|
|
14419
|
+
controls,
|
|
14420
|
+
minZoom = 0.25,
|
|
14421
|
+
maxZoom = 8,
|
|
14422
|
+
checkerboard = true,
|
|
14423
|
+
viewport,
|
|
14424
|
+
defaultViewport,
|
|
14425
|
+
onViewportChange,
|
|
14426
|
+
onLoad,
|
|
14427
|
+
onError,
|
|
14428
|
+
className,
|
|
14429
|
+
style
|
|
14430
|
+
}, ref) => {
|
|
14431
|
+
const containerRef = react.useRef(null);
|
|
14432
|
+
const [status, setStatus] = react.useState("loading");
|
|
14433
|
+
const isControlled = viewport !== void 0;
|
|
14434
|
+
const [internal, setInternal] = react.useState(
|
|
14435
|
+
defaultViewport ?? DEFAULT_VIEWPORT
|
|
14436
|
+
);
|
|
14437
|
+
const current = isControlled ? viewport : internal;
|
|
14438
|
+
const currentRef = react.useRef(current);
|
|
14439
|
+
currentRef.current = current;
|
|
14440
|
+
const setViewport = react.useCallback(
|
|
14441
|
+
(next) => {
|
|
14442
|
+
const resolved = typeof next === "function" ? next(currentRef.current) : next;
|
|
14443
|
+
if (!isControlled) setInternal(resolved);
|
|
14444
|
+
onViewportChange?.(resolved);
|
|
14445
|
+
},
|
|
14446
|
+
[isControlled, onViewportChange]
|
|
14447
|
+
);
|
|
14448
|
+
const interactive = status === "loaded";
|
|
14449
|
+
const { containerProps, isPanning } = usePanZoom({
|
|
14450
|
+
viewport: current,
|
|
14451
|
+
setViewport,
|
|
14452
|
+
minZoom,
|
|
14453
|
+
maxZoom,
|
|
14454
|
+
pannable: pannable && interactive,
|
|
14455
|
+
zoomable: zoomable && interactive,
|
|
14456
|
+
containerRef
|
|
14457
|
+
});
|
|
14458
|
+
const zoomBy = react.useCallback(
|
|
14459
|
+
(factor) => {
|
|
14460
|
+
const el = containerRef.current;
|
|
14461
|
+
if (!el) return;
|
|
14462
|
+
const rect = el.getBoundingClientRect();
|
|
14463
|
+
const cx = rect.width / 2;
|
|
14464
|
+
const cy = rect.height / 2;
|
|
14465
|
+
setViewport((prev) => {
|
|
14466
|
+
const newZoom = clamp2(prev.zoom * factor, minZoom, maxZoom);
|
|
14467
|
+
const ratio = newZoom / prev.zoom;
|
|
14468
|
+
return {
|
|
14469
|
+
zoom: newZoom,
|
|
14470
|
+
panX: cx - (cx - prev.panX) * ratio,
|
|
14471
|
+
panY: cy - (cy - prev.panY) * ratio
|
|
14472
|
+
};
|
|
14473
|
+
});
|
|
14474
|
+
},
|
|
14475
|
+
[setViewport, minZoom, maxZoom]
|
|
14476
|
+
);
|
|
14477
|
+
const reset = react.useCallback(() => setViewport(DEFAULT_VIEWPORT), [setViewport]);
|
|
14478
|
+
const showControls = (controls ?? zoomable) && interactive;
|
|
14479
|
+
const transform = `translate(${current.panX}px, ${current.panY}px) scale(${current.zoom})`;
|
|
14480
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14481
|
+
"div",
|
|
14482
|
+
{
|
|
14483
|
+
ref,
|
|
14484
|
+
"data-react-fancy-image-viewer": "",
|
|
14485
|
+
className: cn(
|
|
14486
|
+
"relative isolate overflow-hidden rounded-md select-none",
|
|
14487
|
+
checkerboard ? "fancy-checkerboard" : "bg-zinc-100 dark:bg-zinc-900",
|
|
14488
|
+
className
|
|
14489
|
+
),
|
|
14490
|
+
style,
|
|
14491
|
+
children: [
|
|
14492
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14493
|
+
"div",
|
|
14494
|
+
{
|
|
14495
|
+
ref: containerRef,
|
|
14496
|
+
"data-canvas-bg": "",
|
|
14497
|
+
className: cn(
|
|
14498
|
+
"absolute inset-0 flex items-center justify-center",
|
|
14499
|
+
isPanning ? "cursor-grabbing" : pannable && interactive ? "cursor-grab" : ""
|
|
14500
|
+
),
|
|
14501
|
+
style: {
|
|
14502
|
+
transform,
|
|
14503
|
+
transformOrigin: "0 0",
|
|
14504
|
+
willChange: "transform",
|
|
14505
|
+
touchAction: zoomable || pannable ? "none" : void 0
|
|
14506
|
+
},
|
|
14507
|
+
onDoubleClick: zoomable ? reset : void 0,
|
|
14508
|
+
...containerProps,
|
|
14509
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14510
|
+
"img",
|
|
14511
|
+
{
|
|
14512
|
+
src,
|
|
14513
|
+
alt,
|
|
14514
|
+
draggable: false,
|
|
14515
|
+
onLoad: () => {
|
|
14516
|
+
setStatus("loaded");
|
|
14517
|
+
onLoad?.();
|
|
14518
|
+
},
|
|
14519
|
+
onError: () => {
|
|
14520
|
+
setStatus("error");
|
|
14521
|
+
onError?.();
|
|
14522
|
+
},
|
|
14523
|
+
className: cn(
|
|
14524
|
+
"pointer-events-none block",
|
|
14525
|
+
status === "error" && "hidden",
|
|
14526
|
+
fitClasses[fit]
|
|
14527
|
+
)
|
|
14528
|
+
}
|
|
14529
|
+
)
|
|
14530
|
+
}
|
|
14531
|
+
),
|
|
14532
|
+
status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14533
|
+
"div",
|
|
14534
|
+
{
|
|
14535
|
+
"data-react-fancy-image-viewer-error": "",
|
|
14536
|
+
className: "absolute inset-0 flex flex-col items-center justify-center gap-1 p-4 text-center text-sm text-zinc-500 dark:text-zinc-400",
|
|
14537
|
+
children: [
|
|
14538
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Couldn't load image" }),
|
|
14539
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-full truncate text-xs opacity-70", children: alt || src })
|
|
14540
|
+
]
|
|
14541
|
+
}
|
|
14542
|
+
),
|
|
14543
|
+
showControls && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14544
|
+
"div",
|
|
14545
|
+
{
|
|
14546
|
+
"data-react-fancy-image-viewer-controls": "",
|
|
14547
|
+
className: "absolute bottom-2 right-2 flex items-center gap-0.5 rounded-md border border-zinc-200 bg-white/90 p-0.5 text-zinc-700 shadow-sm backdrop-blur dark:border-zinc-700 dark:bg-zinc-900/90 dark:text-zinc-200",
|
|
14548
|
+
children: [
|
|
14549
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14550
|
+
"button",
|
|
14551
|
+
{
|
|
14552
|
+
type: "button",
|
|
14553
|
+
"aria-label": "Zoom out",
|
|
14554
|
+
onClick: () => zoomBy(1 / 1.25),
|
|
14555
|
+
className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
14556
|
+
children: "\u2212"
|
|
14557
|
+
}
|
|
14558
|
+
),
|
|
14559
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
14560
|
+
"button",
|
|
14561
|
+
{
|
|
14562
|
+
type: "button",
|
|
14563
|
+
"aria-label": "Reset zoom",
|
|
14564
|
+
onClick: reset,
|
|
14565
|
+
className: "min-w-[3rem] rounded px-1 text-center text-xs tabular-nums hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
14566
|
+
children: [
|
|
14567
|
+
Math.round(current.zoom * 100),
|
|
14568
|
+
"%"
|
|
14569
|
+
]
|
|
14570
|
+
}
|
|
14571
|
+
),
|
|
14572
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14573
|
+
"button",
|
|
14574
|
+
{
|
|
14575
|
+
type: "button",
|
|
14576
|
+
"aria-label": "Zoom in",
|
|
14577
|
+
onClick: () => zoomBy(1.25),
|
|
14578
|
+
className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
14579
|
+
children: "+"
|
|
14580
|
+
}
|
|
14581
|
+
)
|
|
14582
|
+
]
|
|
14583
|
+
}
|
|
14584
|
+
)
|
|
14585
|
+
]
|
|
14586
|
+
}
|
|
14587
|
+
);
|
|
14588
|
+
}
|
|
14589
|
+
);
|
|
14590
|
+
ImageViewer.displayName = "ImageViewer";
|
|
14591
|
+
var VideoViewer = react.forwardRef(
|
|
14592
|
+
({
|
|
14593
|
+
src,
|
|
14594
|
+
poster,
|
|
14595
|
+
controls = true,
|
|
14596
|
+
autoPlay = false,
|
|
14597
|
+
loop = false,
|
|
14598
|
+
muted = false,
|
|
14599
|
+
fit = "contain",
|
|
14600
|
+
onError,
|
|
14601
|
+
className,
|
|
14602
|
+
style
|
|
14603
|
+
}, ref) => {
|
|
14604
|
+
const [errored, setErrored] = react.useState(false);
|
|
14605
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14606
|
+
"div",
|
|
14607
|
+
{
|
|
14608
|
+
ref,
|
|
14609
|
+
"data-react-fancy-video-viewer": "",
|
|
14610
|
+
className: cn(
|
|
14611
|
+
"relative flex items-center justify-center overflow-hidden rounded-md bg-black",
|
|
14612
|
+
className
|
|
14613
|
+
),
|
|
14614
|
+
style,
|
|
14615
|
+
children: errored ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center gap-1 p-4 text-center text-sm text-zinc-400", children: [
|
|
14616
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Couldn't load video" }),
|
|
14617
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-full truncate text-xs opacity-70", children: src })
|
|
14618
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
14619
|
+
"video",
|
|
14620
|
+
{
|
|
14621
|
+
src,
|
|
14622
|
+
poster,
|
|
14623
|
+
controls,
|
|
14624
|
+
autoPlay,
|
|
14625
|
+
loop,
|
|
14626
|
+
muted,
|
|
14627
|
+
playsInline: true,
|
|
14628
|
+
onError: () => {
|
|
14629
|
+
setErrored(true);
|
|
14630
|
+
onError?.();
|
|
14631
|
+
},
|
|
14632
|
+
className: cn(
|
|
14633
|
+
"max-h-full max-w-full",
|
|
14634
|
+
fit === "cover" ? "h-full w-full object-cover" : "object-contain"
|
|
14635
|
+
)
|
|
14636
|
+
}
|
|
14637
|
+
)
|
|
14638
|
+
}
|
|
14639
|
+
);
|
|
14640
|
+
}
|
|
14641
|
+
);
|
|
14642
|
+
VideoViewer.displayName = "VideoViewer";
|
|
14643
|
+
var AudioViewer = react.forwardRef(
|
|
14644
|
+
({
|
|
14645
|
+
src,
|
|
14646
|
+
title,
|
|
14647
|
+
controls = true,
|
|
14648
|
+
autoPlay = false,
|
|
14649
|
+
loop = false,
|
|
14650
|
+
onError,
|
|
14651
|
+
className,
|
|
14652
|
+
style
|
|
14653
|
+
}, ref) => {
|
|
14654
|
+
const [errored, setErrored] = react.useState(false);
|
|
14655
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14656
|
+
"div",
|
|
14657
|
+
{
|
|
14658
|
+
ref,
|
|
14659
|
+
"data-react-fancy-audio-viewer": "",
|
|
14660
|
+
className: cn(
|
|
14661
|
+
"flex flex-col gap-2 rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900",
|
|
14662
|
+
className
|
|
14663
|
+
),
|
|
14664
|
+
style,
|
|
14665
|
+
children: [
|
|
14666
|
+
title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-zinc-700 dark:text-zinc-200", children: title }),
|
|
14667
|
+
errored ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Couldn't load audio." }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
14668
|
+
"audio",
|
|
14669
|
+
{
|
|
14670
|
+
src,
|
|
14671
|
+
controls,
|
|
14672
|
+
autoPlay,
|
|
14673
|
+
loop,
|
|
14674
|
+
onError: () => {
|
|
14675
|
+
setErrored(true);
|
|
14676
|
+
onError?.();
|
|
14677
|
+
},
|
|
14678
|
+
className: "w-full"
|
|
14679
|
+
}
|
|
14680
|
+
)
|
|
14681
|
+
]
|
|
14682
|
+
}
|
|
14683
|
+
);
|
|
14684
|
+
}
|
|
14685
|
+
);
|
|
14686
|
+
AudioViewer.displayName = "AudioViewer";
|
|
14687
|
+
var PdfViewer = react.forwardRef(
|
|
14688
|
+
({ src, title = "PDF document", className, style }, ref) => {
|
|
14689
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14690
|
+
"div",
|
|
14691
|
+
{
|
|
14692
|
+
ref,
|
|
14693
|
+
"data-react-fancy-pdf-viewer": "",
|
|
14694
|
+
className: cn(
|
|
14695
|
+
"relative h-full min-h-[20rem] w-full overflow-hidden rounded-md bg-zinc-100 dark:bg-zinc-900",
|
|
14696
|
+
className
|
|
14697
|
+
),
|
|
14698
|
+
style,
|
|
14699
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("object", { data: src, type: "application/pdf", className: "h-full w-full", children: [
|
|
14700
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14701
|
+
"iframe",
|
|
14702
|
+
{
|
|
14703
|
+
src,
|
|
14704
|
+
title,
|
|
14705
|
+
className: "h-full w-full border-0"
|
|
14706
|
+
}
|
|
14707
|
+
),
|
|
14708
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-2 p-4 text-center text-sm text-zinc-500 dark:text-zinc-400", children: [
|
|
14709
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "This browser can't display the PDF inline." }),
|
|
14710
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14711
|
+
"a",
|
|
14712
|
+
{
|
|
14713
|
+
href: src,
|
|
14714
|
+
download: true,
|
|
14715
|
+
className: "font-medium text-blue-600 hover:underline dark:text-blue-400",
|
|
14716
|
+
children: "Download PDF"
|
|
14717
|
+
}
|
|
14718
|
+
)
|
|
14719
|
+
] })
|
|
14720
|
+
] })
|
|
14721
|
+
}
|
|
14722
|
+
);
|
|
14723
|
+
}
|
|
14724
|
+
);
|
|
14725
|
+
PdfViewer.displayName = "PdfViewer";
|
|
14726
|
+
function basename(src) {
|
|
14727
|
+
if (/^(data|blob):/i.test(src)) return null;
|
|
14728
|
+
const seg = src.split(/[?#]/, 1)[0].split("/").pop();
|
|
14729
|
+
return seg ? decodeURIComponent(seg) : null;
|
|
14730
|
+
}
|
|
14731
|
+
function MediaFallback({ src, mime, label, className }) {
|
|
14732
|
+
const name = label || basename(src);
|
|
14733
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14734
|
+
"div",
|
|
14735
|
+
{
|
|
14736
|
+
"data-react-fancy-media-fallback": "",
|
|
14737
|
+
className: cn(
|
|
14738
|
+
"flex flex-col items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white p-6 text-center dark:border-zinc-700 dark:bg-zinc-900",
|
|
14739
|
+
className
|
|
14740
|
+
),
|
|
14741
|
+
children: [
|
|
14742
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-200", children: "No preview available" }),
|
|
14743
|
+
(name || mime) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "max-w-full truncate text-xs text-zinc-500 dark:text-zinc-400", children: [
|
|
14744
|
+
name,
|
|
14745
|
+
name && mime ? " \xB7 " : "",
|
|
14746
|
+
mime
|
|
14747
|
+
] }),
|
|
14748
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14749
|
+
"a",
|
|
14750
|
+
{
|
|
14751
|
+
href: src,
|
|
14752
|
+
download: name ?? void 0,
|
|
14753
|
+
className: "text-xs font-medium text-blue-600 hover:underline dark:text-blue-400",
|
|
14754
|
+
children: "Download"
|
|
14755
|
+
}
|
|
14756
|
+
)
|
|
14757
|
+
]
|
|
14758
|
+
}
|
|
14759
|
+
);
|
|
14760
|
+
}
|
|
14761
|
+
var MediaViewer = react.forwardRef(
|
|
14762
|
+
({
|
|
14763
|
+
src,
|
|
14764
|
+
mime,
|
|
14765
|
+
alt,
|
|
14766
|
+
kind,
|
|
14767
|
+
imageProps,
|
|
14768
|
+
videoProps,
|
|
14769
|
+
audioProps,
|
|
14770
|
+
pdfProps,
|
|
14771
|
+
fallback,
|
|
14772
|
+
onError,
|
|
14773
|
+
className,
|
|
14774
|
+
style
|
|
14775
|
+
}, ref) => {
|
|
14776
|
+
const resolved = kind ?? resolveMediaType({ mime, src });
|
|
14777
|
+
switch (resolved) {
|
|
14778
|
+
case "image":
|
|
14779
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14780
|
+
ImageViewer,
|
|
14781
|
+
{
|
|
14782
|
+
ref,
|
|
14783
|
+
src,
|
|
14784
|
+
alt: alt ?? "",
|
|
14785
|
+
onError,
|
|
14786
|
+
className,
|
|
14787
|
+
style,
|
|
14788
|
+
...imageProps
|
|
14789
|
+
}
|
|
14790
|
+
);
|
|
14791
|
+
case "video":
|
|
14792
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14793
|
+
VideoViewer,
|
|
14794
|
+
{
|
|
14795
|
+
ref,
|
|
14796
|
+
src,
|
|
14797
|
+
onError,
|
|
14798
|
+
className,
|
|
14799
|
+
style,
|
|
14800
|
+
...videoProps
|
|
14801
|
+
}
|
|
14802
|
+
);
|
|
14803
|
+
case "audio":
|
|
14804
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14805
|
+
AudioViewer,
|
|
14806
|
+
{
|
|
14807
|
+
ref,
|
|
14808
|
+
src,
|
|
14809
|
+
title: alt,
|
|
14810
|
+
onError,
|
|
14811
|
+
className,
|
|
14812
|
+
style,
|
|
14813
|
+
...audioProps
|
|
14814
|
+
}
|
|
14815
|
+
);
|
|
14816
|
+
case "pdf":
|
|
14817
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14818
|
+
PdfViewer,
|
|
14819
|
+
{
|
|
14820
|
+
ref,
|
|
14821
|
+
src,
|
|
14822
|
+
title: alt,
|
|
14823
|
+
className,
|
|
14824
|
+
style,
|
|
14825
|
+
...pdfProps
|
|
14826
|
+
}
|
|
14827
|
+
);
|
|
14828
|
+
default:
|
|
14829
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14830
|
+
"div",
|
|
14831
|
+
{
|
|
14832
|
+
ref,
|
|
14833
|
+
"data-react-fancy-media-viewer": "",
|
|
14834
|
+
className,
|
|
14835
|
+
style,
|
|
14836
|
+
children: fallback ?? /* @__PURE__ */ jsxRuntime.jsx(MediaFallback, { src, mime, label: alt })
|
|
14837
|
+
}
|
|
14838
|
+
);
|
|
14839
|
+
}
|
|
14840
|
+
}
|
|
14841
|
+
);
|
|
14842
|
+
MediaViewer.displayName = "MediaViewer";
|
|
14301
14843
|
|
|
14302
14844
|
exports.Accordion = Accordion;
|
|
14303
14845
|
exports.AccordionPanel = AccordionPanel;
|
|
@@ -14305,6 +14847,7 @@ exports.AccordionPanelContent = AccordionPanelContent;
|
|
|
14305
14847
|
exports.AccordionPanelSection = AccordionPanelSection;
|
|
14306
14848
|
exports.AccordionPanelTrigger = AccordionPanelTrigger;
|
|
14307
14849
|
exports.Action = Action;
|
|
14850
|
+
exports.AudioViewer = AudioViewer;
|
|
14308
14851
|
exports.Autocomplete = Autocomplete;
|
|
14309
14852
|
exports.Avatar = Avatar;
|
|
14310
14853
|
exports.Badge = Badge;
|
|
@@ -14341,10 +14884,12 @@ exports.Form = Form;
|
|
|
14341
14884
|
exports.FormProvider = FormProvider;
|
|
14342
14885
|
exports.Heading = Heading;
|
|
14343
14886
|
exports.Icon = Icon;
|
|
14887
|
+
exports.ImageViewer = ImageViewer;
|
|
14344
14888
|
exports.Input = Input;
|
|
14345
14889
|
exports.InputTag = InputTag;
|
|
14346
14890
|
exports.Kanban = Kanban;
|
|
14347
14891
|
exports.MagicWand = MagicWand;
|
|
14892
|
+
exports.MediaViewer = MediaViewer;
|
|
14348
14893
|
exports.Menu = Menu2;
|
|
14349
14894
|
exports.MobileMenu = MobileMenu;
|
|
14350
14895
|
exports.Modal = Modal;
|
|
@@ -14353,6 +14898,7 @@ exports.MultiSwitch = MultiSwitch;
|
|
|
14353
14898
|
exports.Navbar = Navbar;
|
|
14354
14899
|
exports.OtpInput = OtpInput;
|
|
14355
14900
|
exports.Pagination = Pagination;
|
|
14901
|
+
exports.PdfViewer = PdfViewer;
|
|
14356
14902
|
exports.Pillbox = Pillbox;
|
|
14357
14903
|
exports.Popover = Popover;
|
|
14358
14904
|
exports.Portal = Portal;
|
|
@@ -14379,6 +14925,7 @@ exports.Timeline = Timeline;
|
|
|
14379
14925
|
exports.Toast = Toast;
|
|
14380
14926
|
exports.Tooltip = Tooltip;
|
|
14381
14927
|
exports.TreeNav = TreeNav;
|
|
14928
|
+
exports.VideoViewer = VideoViewer;
|
|
14382
14929
|
exports.applyTone = applyTone;
|
|
14383
14930
|
exports.cn = cn;
|
|
14384
14931
|
exports.configureIcons = configureIcons;
|
|
@@ -14393,6 +14940,7 @@ exports.registerIconAddendum = registerIconAddendum;
|
|
|
14393
14940
|
exports.registerIconSet = registerIconSet;
|
|
14394
14941
|
exports.registerIcons = registerIcons;
|
|
14395
14942
|
exports.resolve = resolve;
|
|
14943
|
+
exports.resolveMediaType = resolveMediaType;
|
|
14396
14944
|
exports.sanitizeHref = sanitizeHref;
|
|
14397
14945
|
exports.sanitizeHtml = sanitizeHtml;
|
|
14398
14946
|
exports.search = search;
|