@particle-academy/react-fancy 4.8.1 → 4.10.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 +554 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -2
- package/dist/index.d.ts +235 -2
- package/dist/index.js +549 -7
- 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
|
@@ -11656,11 +11656,10 @@ function ContentRenderer({
|
|
|
11656
11656
|
);
|
|
11657
11657
|
}
|
|
11658
11658
|
ContentRenderer.displayName = "ContentRenderer";
|
|
11659
|
-
function toHtml(value, outputFormat, unsafe) {
|
|
11659
|
+
function toHtml(value, outputFormat, unsafe, valueFormat = "auto") {
|
|
11660
11660
|
if (!value) return "";
|
|
11661
11661
|
const raw = (() => {
|
|
11662
|
-
|
|
11663
|
-
const format = detectFormat(value);
|
|
11662
|
+
const format = valueFormat !== "auto" ? valueFormat : outputFormat === "html" ? "html" : detectFormat(value);
|
|
11664
11663
|
if (format === "html") return value;
|
|
11665
11664
|
return marked.marked.parse(value, { async: false }).trim();
|
|
11666
11665
|
})();
|
|
@@ -11673,6 +11672,7 @@ function EditorRoot({
|
|
|
11673
11672
|
defaultValue = "",
|
|
11674
11673
|
onChange,
|
|
11675
11674
|
outputFormat = "html",
|
|
11675
|
+
valueFormat = "auto",
|
|
11676
11676
|
lineSpacing = 1.6,
|
|
11677
11677
|
placeholder,
|
|
11678
11678
|
mode: modeProp,
|
|
@@ -11684,13 +11684,13 @@ function EditorRoot({
|
|
|
11684
11684
|
const mode = useFieldMode(modeProp);
|
|
11685
11685
|
const isView = mode === "view";
|
|
11686
11686
|
const initialHtml = react.useMemo(
|
|
11687
|
-
() => toHtml(value, outputFormat, unsafe),
|
|
11687
|
+
() => toHtml(value, outputFormat, unsafe, valueFormat),
|
|
11688
11688
|
// Seed the contentEditable from the LIVE value when (re)entering edit mode.
|
|
11689
11689
|
// EditorContent reads this once on mount, and it only mounts in edit mode —
|
|
11690
11690
|
// so this captures the current value on view→edit, not a stale mount snapshot,
|
|
11691
11691
|
// and does NOT re-run on every keystroke.
|
|
11692
11692
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11693
|
-
[isView, outputFormat, unsafe]
|
|
11693
|
+
[isView, outputFormat, unsafe, valueFormat]
|
|
11694
11694
|
);
|
|
11695
11695
|
const extensions = react.useMemo(
|
|
11696
11696
|
() => mergeExtensions(instanceExtensions),
|
|
@@ -11767,7 +11767,7 @@ function EditorRoot({
|
|
|
11767
11767
|
ContentRenderer,
|
|
11768
11768
|
{
|
|
11769
11769
|
value,
|
|
11770
|
-
format: outputFormat,
|
|
11770
|
+
format: valueFormat !== "auto" ? valueFormat : outputFormat,
|
|
11771
11771
|
lineSpacing,
|
|
11772
11772
|
extensions: instanceExtensions,
|
|
11773
11773
|
unsafe,
|
|
@@ -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;
|