@blogmcp/mdx-render-react 0.1.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 (68) hide show
  1. package/README.md +32 -0
  2. package/dist/MdxContent.d.ts +29 -0
  3. package/dist/MdxContent.js +51 -0
  4. package/dist/index.d.ts +19 -0
  5. package/dist/index.js +43 -0
  6. package/dist/marked.d.ts +34 -0
  7. package/dist/marked.js +15 -0
  8. package/dist/registry.d.ts +33 -0
  9. package/dist/registry.js +88 -0
  10. package/dist/render/alert/Alert.d.ts +13 -0
  11. package/dist/render/alert/Alert.js +24 -0
  12. package/dist/render/audioplayer/AudioPlayerDefault.d.ts +6 -0
  13. package/dist/render/audioplayer/AudioPlayerDefault.js +75 -0
  14. package/dist/render/badge/Badge.d.ts +11 -0
  15. package/dist/render/badge/Badge.js +12 -0
  16. package/dist/render/button/Button.d.ts +14 -0
  17. package/dist/render/button/Button.js +20 -0
  18. package/dist/render/callout/CalloutDefault.d.ts +11 -0
  19. package/dist/render/callout/CalloutDefault.js +19 -0
  20. package/dist/render/callout/CalloutMinimal.d.ts +10 -0
  21. package/dist/render/callout/CalloutMinimal.js +16 -0
  22. package/dist/render/card/Card.d.ts +9 -0
  23. package/dist/render/card/Card.js +17 -0
  24. package/dist/render/codeblock/CodeBlockDefault.d.ts +14 -0
  25. package/dist/render/codeblock/CodeBlockDefault.js +56 -0
  26. package/dist/render/codeblock/language-labels.d.ts +8 -0
  27. package/dist/render/codeblock/language-labels.js +42 -0
  28. package/dist/render/gallery/GalleryGrid.d.ts +11 -0
  29. package/dist/render/gallery/GalleryGrid.js +106 -0
  30. package/dist/render/gallery/GalleryLightbox.d.ts +18 -0
  31. package/dist/render/gallery/GalleryLightbox.js +154 -0
  32. package/dist/render/gallery/gallery.d.ts +29 -0
  33. package/dist/render/gallery/gallery.js +56 -0
  34. package/dist/render/icon/Icon.d.ts +11 -0
  35. package/dist/render/icon/Icon.js +18 -0
  36. package/dist/render/index.d.ts +25 -0
  37. package/dist/render/index.js +26 -0
  38. package/dist/render/tooltip/Tooltip.d.ts +13 -0
  39. package/dist/render/tooltip/Tooltip.js +15 -0
  40. package/dist/render/youtube/YouTubeDefault.d.ts +11 -0
  41. package/dist/render/youtube/YouTubeDefault.js +53 -0
  42. package/dist/render/youtube/youtube.d.ts +8 -0
  43. package/dist/render/youtube/youtube.js +13 -0
  44. package/dist/server-render-map.d.ts +54 -0
  45. package/dist/server-render-map.js +56 -0
  46. package/package.json +77 -0
  47. package/src/MdxContent.tsx +105 -0
  48. package/src/index.ts +37 -0
  49. package/src/marked.ts +49 -0
  50. package/src/registry.ts +131 -0
  51. package/src/render/alert/Alert.tsx +32 -0
  52. package/src/render/audioplayer/AudioPlayerDefault.tsx +106 -0
  53. package/src/render/badge/Badge.tsx +24 -0
  54. package/src/render/button/Button.tsx +49 -0
  55. package/src/render/callout/CalloutDefault.tsx +34 -0
  56. package/src/render/callout/CalloutMinimal.tsx +25 -0
  57. package/src/render/card/Card.tsx +21 -0
  58. package/src/render/codeblock/CodeBlockDefault.tsx +82 -0
  59. package/src/render/codeblock/language-labels.ts +46 -0
  60. package/src/render/gallery/GalleryGrid.tsx +143 -0
  61. package/src/render/gallery/GalleryLightbox.tsx +201 -0
  62. package/src/render/gallery/gallery.ts +90 -0
  63. package/src/render/icon/Icon.tsx +25 -0
  64. package/src/render/index.ts +26 -0
  65. package/src/render/tooltip/Tooltip.tsx +38 -0
  66. package/src/render/youtube/YouTubeDefault.tsx +58 -0
  67. package/src/render/youtube/youtube.ts +17 -0
  68. package/src/server-render-map.ts +120 -0
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { jsxs as o, jsx as e } from "react/jsx-runtime";
3
+ import { useState as g, useRef as p, useCallback as x } from "react";
4
+ import { getLanguageLabel as h } from "./language-labels.js";
5
+ function v({ language: r, title: a, children: c }) {
6
+ const l = h(r), d = `language-${r || "text"}`, [t, n] = g(!1), s = p(null), i = x(async () => {
7
+ const m = s.current?.textContent || "";
8
+ try {
9
+ await navigator.clipboard.writeText(m), n(!0), setTimeout(() => n(!1), 2e3);
10
+ } catch (u) {
11
+ console.error("Failed to copy code:", u);
12
+ }
13
+ }, []);
14
+ return /* @__PURE__ */ o(
15
+ "div",
16
+ {
17
+ className: "code-block my-6 rounded-lg overflow-hidden border border-gray-200 bg-gray-50",
18
+ "data-mdx-component": "CodeBlock",
19
+ children: [
20
+ /* @__PURE__ */ o("div", { className: "flex items-center justify-between px-4 py-2 bg-gray-100 border-b border-gray-200", children: [
21
+ /* @__PURE__ */ o("div", { className: "flex items-center gap-2", children: [
22
+ /* @__PURE__ */ e("span", { className: "text-xs text-gray-600 font-medium", children: l }),
23
+ a ? /* @__PURE__ */ e("span", { className: "text-xs text-gray-500", children: a }) : null
24
+ ] }),
25
+ /* @__PURE__ */ o(
26
+ "button",
27
+ {
28
+ type: "button",
29
+ className: [
30
+ "flex items-center gap-1 px-2 py-1 text-xs rounded transition-colors",
31
+ t ? "bg-green-500 text-white" : "hover:bg-gray-200 text-gray-500 hover:text-gray-700"
32
+ ].join(" "),
33
+ onClick: i,
34
+ children: [
35
+ t ? /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }) : /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e(
36
+ "path",
37
+ {
38
+ strokeLinecap: "round",
39
+ strokeLinejoin: "round",
40
+ strokeWidth: 2,
41
+ d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
42
+ }
43
+ ) }),
44
+ t ? "Copied!" : "Copy"
45
+ ]
46
+ }
47
+ )
48
+ ] }),
49
+ /* @__PURE__ */ e("div", { className: "code-block-content p-4 overflow-x-auto", children: /* @__PURE__ */ e("pre", { className: "m-0", children: /* @__PURE__ */ e("code", { ref: s, className: d, children: c }) }) })
50
+ ]
51
+ }
52
+ );
53
+ }
54
+ export {
55
+ v as default
56
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Language label mappings — mirrored verbatim from
3
+ * `@blogmcp/mdx-render-vue/src/composables/useCodeBlock.ts` so the React
4
+ * CodeBlock header reads identically to the Vue one.
5
+ */
6
+ export declare const languageLabels: Record<string, string>;
7
+ /** Get display label for a language (capitalizes unknown languages). */
8
+ export declare function getLanguageLabel(language?: string): string;
@@ -0,0 +1,42 @@
1
+ const e = {
2
+ js: "JavaScript",
3
+ javascript: "JavaScript",
4
+ ts: "TypeScript",
5
+ typescript: "TypeScript",
6
+ vue: "Vue",
7
+ html: "HTML",
8
+ css: "CSS",
9
+ json: "JSON",
10
+ python: "Python",
11
+ py: "Python",
12
+ bash: "Bash",
13
+ sh: "Shell",
14
+ sql: "SQL",
15
+ text: "Plain Text",
16
+ rust: "Rust",
17
+ go: "Go",
18
+ java: "Java",
19
+ cpp: "C++",
20
+ c: "C",
21
+ csharp: "C#",
22
+ php: "PHP",
23
+ ruby: "Ruby",
24
+ swift: "Swift",
25
+ kotlin: "Kotlin",
26
+ yaml: "YAML",
27
+ yml: "YAML",
28
+ xml: "XML",
29
+ markdown: "Markdown",
30
+ md: "Markdown",
31
+ graphql: "GraphQL",
32
+ dockerfile: "Dockerfile"
33
+ };
34
+ function r(a) {
35
+ if (!a) return "Plain Text";
36
+ const t = a.toLowerCase();
37
+ return e[t] ? e[t] : t.charAt(0).toUpperCase() + t.slice(1);
38
+ }
39
+ export {
40
+ r as getLanguageLabel,
41
+ e as languageLabels
42
+ };
@@ -0,0 +1,11 @@
1
+ import type { GalleryProps } from '@blogmcp/mdx-schema';
2
+ /**
3
+ * Image/video grid. Client island — mirrors Vue GalleryGrid.vue + useGallery.ts.
4
+ *
5
+ * The grid (initial DOM) is unchanged: `data-mdx-component="Gallery"`,
6
+ * `grid-cols-N`, video posters, captions, and expand-icon overlay all match the
7
+ * static/Vue versions. Clicking a (non-video) image opens a fullscreen lightbox
8
+ * (GalleryLightbox) with prev/next, Escape, and backdrop-to-close — the lightbox
9
+ * indexes only the image items, exactly like the Vue `imageOnlyIndices` mapping.
10
+ */
11
+ export default function GalleryGrid({ images, columns, gap, aspectRatio, fit, position }: GalleryProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,106 @@
1
+ "use client";
2
+ import { jsxs as l, Fragment as h, jsx as r } from "react/jsx-runtime";
3
+ import { useState as y, useMemo as j } from "react";
4
+ import { galleryTileLayout as O, isVideo as s, gridColsClass as z, gapClass as A, getImageDescription as u, getVideoPoster as D, getImageSrc as a, getImageAlt as m } from "./gallery.js";
5
+ import V from "./GalleryLightbox.js";
6
+ function T({ images: t, columns: x, gap: v, aspectRatio: b, fit: f, position: C }) {
7
+ const [I, c] = y(!1), [w, d] = y(0), i = O({ aspectRatio: b, fit: f, position: C }), n = j(() => {
8
+ const e = [];
9
+ if (!t) return e;
10
+ for (let o = 0; o < t.length; o++)
11
+ s(t[o]) || e.push(o);
12
+ return e;
13
+ }, [t]);
14
+ if (!t || t.length === 0) return null;
15
+ const N = n.length;
16
+ function S(e) {
17
+ if (s(t[e])) return;
18
+ const o = n.indexOf(e);
19
+ o !== -1 && (d(o), c(!0));
20
+ }
21
+ const L = (e) => a(t[n[e]]), k = (e) => m(t[n[e]], n[e]), G = (e) => u(t[n[e]]);
22
+ return /* @__PURE__ */ l(h, { children: [
23
+ /* @__PURE__ */ r(
24
+ "div",
25
+ {
26
+ className: ["gallery my-6", z(x), A(v)].join(" "),
27
+ style: { display: "grid" },
28
+ "data-mdx-component": "Gallery",
29
+ children: t.map((e, o) => {
30
+ const g = u(e), p = s(e);
31
+ return /* @__PURE__ */ l("div", { className: "gallery-item", children: [
32
+ /* @__PURE__ */ r(
33
+ "div",
34
+ {
35
+ className: ["gallery-media group relative overflow-hidden rounded", p ? "bg-gray-900" : "bg-gray-100 cursor-pointer"].join(" ").trim(),
36
+ style: i.tileStyle,
37
+ onClick: () => S(o),
38
+ children: p ? /* @__PURE__ */ r(
39
+ "video",
40
+ {
41
+ src: a(e),
42
+ poster: D(e),
43
+ controls: !0,
44
+ preload: "metadata",
45
+ className: i.mediaSizeClass,
46
+ style: i.mediaStyle
47
+ }
48
+ ) : /* @__PURE__ */ l(h, { children: [
49
+ /* @__PURE__ */ r(
50
+ "img",
51
+ {
52
+ src: a(e),
53
+ alt: m(e, o),
54
+ className: [i.mediaSizeClass, "transition-transform duration-200 group-hover:scale-[1.03]"].join(" "),
55
+ style: i.mediaStyle,
56
+ loading: "lazy"
57
+ }
58
+ ),
59
+ /* @__PURE__ */ r("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors duration-200 pointer-events-none" }),
60
+ /* @__PURE__ */ l(
61
+ "svg",
62
+ {
63
+ className: "gallery-expand-icon absolute top-2 right-2 text-white opacity-50 [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-hover:opacity-80 transition-opacity duration-200 drop-shadow-md pointer-events-none",
64
+ xmlns: "http://www.w3.org/2000/svg",
65
+ width: "20",
66
+ height: "20",
67
+ viewBox: "0 0 24 24",
68
+ fill: "none",
69
+ stroke: "currentColor",
70
+ strokeWidth: 2,
71
+ strokeLinecap: "round",
72
+ strokeLinejoin: "round",
73
+ children: [
74
+ /* @__PURE__ */ r("polyline", { points: "15 3 21 3 21 9" }),
75
+ /* @__PURE__ */ r("polyline", { points: "9 21 3 21 3 15" }),
76
+ /* @__PURE__ */ r("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
77
+ /* @__PURE__ */ r("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
78
+ ]
79
+ }
80
+ )
81
+ ] })
82
+ }
83
+ ),
84
+ g ? /* @__PURE__ */ r("p", { className: "gallery-description text-center text-sm text-gray-500 mt-2", children: g }) : null
85
+ ] }, o);
86
+ })
87
+ }
88
+ ),
89
+ /* @__PURE__ */ r(
90
+ V,
91
+ {
92
+ isOpen: I,
93
+ currentIndex: w,
94
+ imageCount: N,
95
+ getImageSrc: L,
96
+ getImageAlt: k,
97
+ getImageDescription: G,
98
+ onClose: () => c(!1),
99
+ onNavigate: d
100
+ }
101
+ )
102
+ ] });
103
+ }
104
+ export {
105
+ T as default
106
+ };
@@ -0,0 +1,18 @@
1
+ export interface GalleryLightboxProps {
2
+ isOpen: boolean;
3
+ currentIndex: number;
4
+ imageCount: number;
5
+ getImageSrc: (index: number) => string;
6
+ getImageAlt: (index: number) => string;
7
+ getImageDescription: (index: number) => string | undefined;
8
+ onClose: () => void;
9
+ onNavigate: (index: number) => void;
10
+ }
11
+ /**
12
+ * Fullscreen image viewer with keyboard + touch navigation. Client island —
13
+ * mirrors Vue GalleryLightbox.vue: portalled to <body>, dark backdrop,
14
+ * close/prev/next buttons, image counter, Escape + ArrowLeft/Right keys,
15
+ * click-backdrop-to-close, and horizontal-swipe navigation. Markup, classes,
16
+ * and `aria-label`s match the Vue source.
17
+ */
18
+ export default function GalleryLightbox({ isOpen, currentIndex, imageCount, getImageSrc, getImageAlt, getImageDescription, onClose, onNavigate, }: GalleryLightboxProps): import("react").ReactPortal | null;
@@ -0,0 +1,154 @@
1
+ "use client";
2
+ import { jsxs as c, jsx as o } from "react/jsx-runtime";
3
+ import { useEffect as b, useRef as x, useCallback as w } from "react";
4
+ import { createPortal as E } from "react-dom";
5
+ function N({
6
+ isOpen: s,
7
+ currentIndex: t,
8
+ imageCount: r,
9
+ getImageSrc: p,
10
+ getImageAlt: y,
11
+ getImageDescription: k,
12
+ onClose: n,
13
+ onNavigate: l
14
+ }) {
15
+ const h = t > 0, a = t < r - 1, u = r > 1;
16
+ b(() => {
17
+ if (!s) return;
18
+ function e(i) {
19
+ i.key === "Escape" ? n() : i.key === "ArrowLeft" && t > 0 ? l(t - 1) : i.key === "ArrowRight" && t < r - 1 && l(t + 1);
20
+ }
21
+ return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
22
+ }, [s, t, r, n, l]), b(() => {
23
+ if (s)
24
+ return document.body.style.overflow = "hidden", () => {
25
+ document.body.style.overflow = "";
26
+ };
27
+ }, [s]);
28
+ const d = x(0), m = x(0), g = w((e) => {
29
+ d.current = e.touches[0].clientX, m.current = e.touches[0].clientY;
30
+ }, []), v = w(
31
+ (e) => {
32
+ const i = e.changedTouches[0].clientX - d.current, j = e.changedTouches[0].clientY - m.current;
33
+ Math.abs(i) > 50 && Math.abs(i) > Math.abs(j) && (i < 0 && a ? l(t + 1) : i > 0 && h && l(t - 1));
34
+ },
35
+ [t, a, h, l]
36
+ ), L = w(
37
+ (e) => {
38
+ e.target === e.currentTarget && n();
39
+ },
40
+ [n]
41
+ );
42
+ if (!s || typeof document > "u") return null;
43
+ const f = k(t);
44
+ return E(
45
+ /* @__PURE__ */ c(
46
+ "div",
47
+ {
48
+ className: "lightbox-overlay fixed inset-0 z-50 flex items-center justify-center bg-black/90",
49
+ role: "dialog",
50
+ "aria-modal": "true",
51
+ onClick: L,
52
+ onTouchStart: g,
53
+ onTouchEnd: v,
54
+ children: [
55
+ /* @__PURE__ */ o(
56
+ "button",
57
+ {
58
+ type: "button",
59
+ className: "lightbox-close absolute top-4 right-4 z-10 text-white/80 hover:text-white p-2",
60
+ "aria-label": "Close lightbox",
61
+ onClick: n,
62
+ children: /* @__PURE__ */ c(
63
+ "svg",
64
+ {
65
+ xmlns: "http://www.w3.org/2000/svg",
66
+ width: "28",
67
+ height: "28",
68
+ viewBox: "0 0 24 24",
69
+ fill: "none",
70
+ stroke: "currentColor",
71
+ strokeWidth: 2,
72
+ strokeLinecap: "round",
73
+ strokeLinejoin: "round",
74
+ children: [
75
+ /* @__PURE__ */ o("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
76
+ /* @__PURE__ */ o("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
77
+ ]
78
+ }
79
+ )
80
+ }
81
+ ),
82
+ u ? /* @__PURE__ */ c("div", { className: "lightbox-counter absolute top-4 left-4 text-white/80 text-sm select-none", children: [
83
+ t + 1,
84
+ " / ",
85
+ r
86
+ ] }) : null,
87
+ u && h ? /* @__PURE__ */ o(
88
+ "button",
89
+ {
90
+ type: "button",
91
+ className: "lightbox-prev absolute left-4 top-1/2 -translate-y-1/2 z-10 text-white/80 hover:text-white p-2",
92
+ "aria-label": "Previous image",
93
+ onClick: () => l(t - 1),
94
+ children: /* @__PURE__ */ o(
95
+ "svg",
96
+ {
97
+ xmlns: "http://www.w3.org/2000/svg",
98
+ width: "32",
99
+ height: "32",
100
+ viewBox: "0 0 24 24",
101
+ fill: "none",
102
+ stroke: "currentColor",
103
+ strokeWidth: 2,
104
+ strokeLinecap: "round",
105
+ strokeLinejoin: "round",
106
+ children: /* @__PURE__ */ o("polyline", { points: "15 18 9 12 15 6" })
107
+ }
108
+ )
109
+ }
110
+ ) : null,
111
+ u && a ? /* @__PURE__ */ o(
112
+ "button",
113
+ {
114
+ type: "button",
115
+ className: "lightbox-next absolute right-4 top-1/2 -translate-y-1/2 z-10 text-white/80 hover:text-white p-2",
116
+ "aria-label": "Next image",
117
+ onClick: () => l(t + 1),
118
+ children: /* @__PURE__ */ o(
119
+ "svg",
120
+ {
121
+ xmlns: "http://www.w3.org/2000/svg",
122
+ width: "32",
123
+ height: "32",
124
+ viewBox: "0 0 24 24",
125
+ fill: "none",
126
+ stroke: "currentColor",
127
+ strokeWidth: 2,
128
+ strokeLinecap: "round",
129
+ strokeLinejoin: "round",
130
+ children: /* @__PURE__ */ o("polyline", { points: "9 18 15 12 9 6" })
131
+ }
132
+ )
133
+ }
134
+ ) : null,
135
+ /* @__PURE__ */ c("div", { className: "flex flex-col items-center max-h-[95vh] max-w-[90vw]", onClick: (e) => e.stopPropagation(), children: [
136
+ /* @__PURE__ */ o(
137
+ "img",
138
+ {
139
+ src: p(t),
140
+ alt: y(t),
141
+ className: "lightbox-image max-h-[85vh] max-w-[90vw] object-contain"
142
+ }
143
+ ),
144
+ f ? /* @__PURE__ */ o("p", { className: "lightbox-caption text-white/80 text-sm mt-3 text-center max-w-[80vw]", children: f }) : null
145
+ ] })
146
+ ]
147
+ }
148
+ ),
149
+ document.body
150
+ );
151
+ }
152
+ export {
153
+ N as default
154
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Gallery helpers — mirrored from
3
+ * `@blogmcp/mdx-render-vue/src/composables/useGallery.ts`.
4
+ */
5
+ import type { CSSProperties } from 'react';
6
+ import type { GalleryImage, GalleryProps, MediaObject } from '@blogmcp/mdx-schema';
7
+ export declare const gridColsClasses: Record<number, string>;
8
+ export declare function isMediaObject(value: unknown): value is MediaObject;
9
+ export declare function isVideoUrl(src: string): boolean;
10
+ export declare function gridColsClass(columns?: number): string;
11
+ export declare function gapClass(gap?: number): string;
12
+ /**
13
+ * React-typed adapter over the shared, framework-agnostic `galleryTileLayout`
14
+ * in `@blogmcp/mdx-schema/styles` (the single source of truth for tile
15
+ * geometry — keeps the Vue + React flavors byte-identical). The shared helper
16
+ * returns `Record<string,string>`; the values are valid CSS, so casting to
17
+ * React's `CSSProperties` for the `style` prop is safe.
18
+ */
19
+ export interface GalleryTileLayout {
20
+ tileStyle: CSSProperties;
21
+ mediaStyle: CSSProperties;
22
+ mediaSizeClass: string;
23
+ }
24
+ export declare function galleryTileLayout(props: Pick<GalleryProps, 'aspectRatio' | 'fit' | 'position'>): GalleryTileLayout;
25
+ export declare function getImageSrc(image: GalleryImage): string;
26
+ export declare function getImageAlt(image: GalleryImage, index: number): string;
27
+ export declare function getImageDescription(image: GalleryImage): string | undefined;
28
+ export declare function isVideo(image: GalleryImage): boolean;
29
+ export declare function getVideoPoster(image: GalleryImage): string | undefined;
@@ -0,0 +1,56 @@
1
+ import { galleryTileLayout as o } from "@blogmcp/mdx-schema/styles";
2
+ const s = /\.(mp4|webm|mov|avi|mkv|m4v)(\?.*)?$/i, l = /\/api\/media\/file\/([^/?#]+)/, c = {
3
+ 1: "grid-cols-1",
4
+ 2: "grid-cols-2",
5
+ 3: "grid-cols-3",
6
+ 4: "grid-cols-4"
7
+ };
8
+ function e(t) {
9
+ return typeof t == "object" && t !== null && "src" in t && typeof t.src == "string";
10
+ }
11
+ function n(t) {
12
+ return s.test(t);
13
+ }
14
+ function d(t) {
15
+ return c[t || 2] || "grid-cols-2";
16
+ }
17
+ function f(t) {
18
+ return `gap-${t || 4}`;
19
+ }
20
+ function y(t) {
21
+ const r = o(t);
22
+ return {
23
+ tileStyle: r.tileStyle,
24
+ mediaStyle: r.mediaStyle,
25
+ mediaSizeClass: r.mediaSizeClass
26
+ };
27
+ }
28
+ function u(t) {
29
+ return e(t) ? t.src : t;
30
+ }
31
+ function g(t, r) {
32
+ return e(t) ? t.alt || `Gallery image ${r + 1}` : `Gallery image ${r + 1}`;
33
+ }
34
+ function p(t) {
35
+ return e(t) ? t.description : void 0;
36
+ }
37
+ function m(t) {
38
+ return e(t) ? t.type === "video" ? !0 : t.type === "image" ? !1 : n(t.src) : n(t);
39
+ }
40
+ function S(t) {
41
+ const i = u(t).match(l);
42
+ return i ? `/api/media/poster/${i[1]}` : void 0;
43
+ }
44
+ export {
45
+ y as galleryTileLayout,
46
+ f as gapClass,
47
+ g as getImageAlt,
48
+ p as getImageDescription,
49
+ u as getImageSrc,
50
+ S as getVideoPoster,
51
+ d as gridColsClass,
52
+ c as gridColsClasses,
53
+ e as isMediaObject,
54
+ m as isVideo,
55
+ n as isVideoUrl
56
+ };
@@ -0,0 +1,11 @@
1
+ export interface IconProps {
2
+ name: string;
3
+ size?: number;
4
+ color?: string;
5
+ }
6
+ /**
7
+ * Inline Iconify icon (any set). Mirrors Vue Icon.vue — the
8
+ * data-mdx-component attribute lands on the rendered <svg> via Iconify's
9
+ * attribute pass-through.
10
+ */
11
+ export default function Icon({ name, size, color }: IconProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as i } from "react/jsx-runtime";
2
+ import { Icon as a } from "@iconify/react";
3
+ function m({ name: t, size: n = 20, color: o }) {
4
+ return /* @__PURE__ */ i(
5
+ a,
6
+ {
7
+ icon: t,
8
+ width: n,
9
+ height: n,
10
+ style: o ? { color: o } : void 0,
11
+ className: "inline-block align-[-0.125em]",
12
+ "data-mdx-component": "Icon"
13
+ }
14
+ );
15
+ }
16
+ export {
17
+ m as default
18
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * React MDX render components.
3
+ *
4
+ * Named exports for every render component. The two client islands (Tooltip,
5
+ * AudioPlayerDefault) carry their own `'use client'` banner.
6
+ */
7
+ export { default as CalloutDefault } from './callout/CalloutDefault';
8
+ export { default as CalloutMinimal } from './callout/CalloutMinimal';
9
+ export { default as YouTubeDefault } from './youtube/YouTubeDefault';
10
+ export { default as GalleryGrid } from './gallery/GalleryGrid';
11
+ export { default as CodeBlockDefault } from './codeblock/CodeBlockDefault';
12
+ export { default as AudioPlayerDefault } from './audioplayer/AudioPlayerDefault';
13
+ export { default as Button } from './button/Button';
14
+ export { default as Card } from './card/Card';
15
+ export { default as Alert } from './alert/Alert';
16
+ export { default as Badge } from './badge/Badge';
17
+ export { default as Icon } from './icon/Icon';
18
+ export { default as Tooltip } from './tooltip/Tooltip';
19
+ export type { ButtonProps } from './button/Button';
20
+ export type { BadgeProps } from './badge/Badge';
21
+ export type { AlertProps } from './alert/Alert';
22
+ export type { CardProps } from './card/Card';
23
+ export type { IconProps } from './icon/Icon';
24
+ export type { TooltipProps } from './tooltip/Tooltip';
25
+ export type { CalloutProps } from './callout/CalloutDefault';
@@ -0,0 +1,26 @@
1
+ import { default as o } from "./callout/CalloutDefault.js";
2
+ import { default as r } from "./callout/CalloutMinimal.js";
3
+ import { default as l } from "./youtube/YouTubeDefault.js";
4
+ import { default as d } from "./gallery/GalleryGrid.js";
5
+ import { default as p } from "./codeblock/CodeBlockDefault.js";
6
+ import { default as x } from "./audioplayer/AudioPlayerDefault.js";
7
+ import { default as C } from "./button/Button.js";
8
+ import { default as n } from "./card/Card.js";
9
+ import { default as c } from "./alert/Alert.js";
10
+ import { default as A } from "./badge/Badge.js";
11
+ import { default as T } from "./icon/Icon.js";
12
+ import { default as g } from "./tooltip/Tooltip.js";
13
+ export {
14
+ c as Alert,
15
+ x as AudioPlayerDefault,
16
+ A as Badge,
17
+ C as Button,
18
+ o as CalloutDefault,
19
+ r as CalloutMinimal,
20
+ n as Card,
21
+ p as CodeBlockDefault,
22
+ d as GalleryGrid,
23
+ T as Icon,
24
+ g as Tooltip,
25
+ l as YouTubeDefault
26
+ };
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface TooltipProps {
3
+ label: string;
4
+ placement?: 'top' | 'bottom' | 'left' | 'right';
5
+ children?: ReactNode;
6
+ }
7
+ /**
8
+ * Hover-reveal tooltip backed by Ark UI (React). Client island — mirrors Vue
9
+ * Tooltip.vue: same Ark namespaced primitive, same delays/positioning, same
10
+ * content classes, portalled content. The trigger wraps an inline span that
11
+ * carries `data-mdx-component="Tooltip"`.
12
+ */
13
+ export default function Tooltip({ label, placement, children }: TooltipProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsxs as t, jsx as o } from "react/jsx-runtime";
3
+ import { Tooltip as e, Portal as n } from "@ark-ui/react";
4
+ function d({ label: a, placement: i = "top", children: r }) {
5
+ return /* @__PURE__ */ t(e.Root, { positioning: { placement: i }, openDelay: 200, closeDelay: 100, children: [
6
+ /* @__PURE__ */ o(e.Trigger, { asChild: !0, children: /* @__PURE__ */ o("span", { className: "inline-flex", "data-mdx-component": "Tooltip", children: r }) }),
7
+ /* @__PURE__ */ o(n, { children: /* @__PURE__ */ o(e.Positioner, { children: /* @__PURE__ */ t(e.Content, { className: "z-50 rounded-md bg-gray-900 px-2.5 py-1.5 text-xs text-white shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out", children: [
8
+ /* @__PURE__ */ o(e.Arrow, { children: /* @__PURE__ */ o(e.ArrowTip, { className: "border-gray-900" }) }),
9
+ a
10
+ ] }) }) })
11
+ ] });
12
+ }
13
+ export {
14
+ d as default
15
+ };
@@ -0,0 +1,11 @@
1
+ import type { YouTubeProps } from '@blogmcp/mdx-schema';
2
+ /**
3
+ * YouTube facade. Client island — mirrors Vue YouTubeDefault.vue + useYouTube.ts.
4
+ *
5
+ * Facade pattern: renders a thumbnail + play-button overlay initially (the
6
+ * server/initial DOM), and swaps to the real autoplay iframe on click — saving
7
+ * ~500KB per embed until the user actually plays. The `youtube-embed` wrapper,
8
+ * `data-mdx-component="YouTube"`, and the 56.25% aspect-ratio box are identical
9
+ * to the static/Vue versions; the interactive bit lives inside the box.
10
+ */
11
+ export default function YouTubeDefault({ id, title }: YouTubeProps): import("react/jsx-runtime").JSX.Element;