@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
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @blogmcp/mdx-render-react
2
+
3
+ **Status: scaffold only.** Implementation deferred until a React consumer exists (blog or md-editor React port).
4
+
5
+ This package mirrors [`@blogmcp/mdx-render-vue`](../mdx-render-vue/) against the shared
6
+ [`@blogmcp/mdx-schema`](../mdx-schema/) contract. Authoring stays identical across frameworks
7
+ (same MDX syntax, same metadata, same MCP tool output) — only the renderer swaps.
8
+
9
+ ## Contract to implement
10
+
11
+ ```ts
12
+ import { componentMetadata } from '@blogmcp/mdx-schema'
13
+ import type { ComponentType } from 'react'
14
+
15
+ export interface ReactComponentRegistration {
16
+ metadata: ComponentMetadata // from @blogmcp/mdx-schema
17
+ render: Record<string, () => Promise<{ default: ComponentType<any> }>>
18
+ edit: () => Promise<{ default: ComponentType<any> }>
19
+ }
20
+
21
+ export const reactRegistry: Record<string, ReactComponentRegistration>
22
+ ```
23
+
24
+ Implementation mirrors [`packages/mdx-render-vue/src/registry.ts`](../mdx-render-vue/src/registry.ts)
25
+ — same keys (`Callout`, `YouTube`, `Gallery`, `CodeBlock`, `AudioPlayer`), same variants, React
26
+ components instead of Vue SFCs. Prefer Ark UI React primitives where Vue uses Ark UI Vue; share
27
+ Tailwind classes verbatim.
28
+
29
+ ## When to implement
30
+
31
+ When the first React consumer appears (blog-react or editor-react). Until then, keep the package
32
+ empty so the schema contract proves framework-neutral by construction.
@@ -0,0 +1,29 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ImageHtmlRenderer, SanitizeFn } from '@blogmcp/sdk';
3
+ export interface MdxContentProps {
4
+ /** Raw MDX/markdown source (frontmatter allowed; it is stripped). */
5
+ content: string;
6
+ /** Optional custom image HTML renderer (IPX, Cloudinary, …). */
7
+ imageRenderer?: ImageHtmlRenderer;
8
+ /**
9
+ * Optional HTML sanitizer override. Defaults to `sanitizeHtml` from
10
+ * `@blogmcp/sanitize` (DOMPurify with our strict config). Pass `null`
11
+ * to opt out for trusted-self surfaces.
12
+ */
13
+ sanitize?: SanitizeFn | null;
14
+ }
15
+ /**
16
+ * Renders MDX content as a tree of React Server Components.
17
+ *
18
+ * Mirrors the Vue blog renderer (KbMdxContent.vue):
19
+ * - strips frontmatter
20
+ * - fast-paths pure markdown via `marked` → dangerouslySetInnerHTML
21
+ * - otherwise splits via `extractMdxComponents`, rendering html chunks via
22
+ * `marked` and components via `serverRenderMap[name]`
23
+ * - recurses into component children by rendering a nested <MdxContent>
24
+ * (React equivalent of Vue's raw-string default slot)
25
+ * - unknown component → the same yellow fallback box the Vue renderer shows
26
+ *
27
+ * Async Server Component: safe to `await` in RSC; works synchronously in tests.
28
+ */
29
+ export default function MdxContent({ content, imageRenderer, sanitize }: MdxContentProps): Promise<ReactNode>;
@@ -0,0 +1,51 @@
1
+ import { jsx as n } from "react/jsx-runtime";
2
+ import { Fragment as h } from "react";
3
+ import { stripFrontmatter as u, HAS_MDX_RE as f, extractMdxComponents as x } from "@blogmcp/mdx-schema/composables/parsing";
4
+ import { createMarked as y } from "./marked.js";
5
+ import { wrappedServerRenderMap as w } from "./server-render-map.js";
6
+ async function v({ content: a, imageRenderer: c, sanitize: l }) {
7
+ const m = y({ imageRenderer: c, sanitize: l });
8
+ if (!a)
9
+ return /* @__PURE__ */ n("div", { className: "mdx-content" });
10
+ const s = u(a);
11
+ if (!f.test(s)) {
12
+ const t = await m.parse(s);
13
+ return /* @__PURE__ */ n("div", { className: "mdx-content", dangerouslySetInnerHTML: { __html: t } });
14
+ }
15
+ const o = x(s);
16
+ if (o.length === 1 && o[0].type === "html") {
17
+ const t = await m.parse(o[0].content);
18
+ return /* @__PURE__ */ n("div", { className: "mdx-content", dangerouslySetInnerHTML: { __html: t } });
19
+ }
20
+ const r = [];
21
+ for (let t = 0; t < o.length; t++) {
22
+ const e = o[t];
23
+ if (e.type === "html") {
24
+ const p = await m.parse(e.content);
25
+ r.push(/* @__PURE__ */ n("div", { dangerouslySetInnerHTML: { __html: p } }, t));
26
+ continue;
27
+ }
28
+ const d = w[e.name];
29
+ if (!d) {
30
+ r.push(
31
+ /* @__PURE__ */ n(
32
+ "div",
33
+ {
34
+ className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4 my-4 text-yellow-800",
35
+ children: `Unknown component: ${e.name}`
36
+ },
37
+ t
38
+ )
39
+ );
40
+ continue;
41
+ }
42
+ const i = e.children ? await v({ content: e.children, imageRenderer: c, sanitize: l }) : void 0;
43
+ r.push(
44
+ /* @__PURE__ */ n(d, { ...e.props, children: i }, t)
45
+ );
46
+ }
47
+ return /* @__PURE__ */ n("div", { className: "mdx-content", children: r.map((t, e) => /* @__PURE__ */ n(h, { children: t }, e)) });
48
+ }
49
+ export {
50
+ v as default
51
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @blogmcp/mdx-render-react
3
+ *
4
+ * React renderer for MDX components. Implements the same contract as
5
+ * @blogmcp/mdx-render-vue against the shared @blogmcp/mdx-schema. Authoring
6
+ * (MDX syntax, metadata, MCP tool output) is identical across frameworks —
7
+ * only the renderer swaps.
8
+ *
9
+ * The blog/RSC path uses `MdxContent` (async Server Component) + the static
10
+ * `serverRenderMap`. `reactRegistry` mirrors the Vue registry for future
11
+ * tooling (lazy variant resolution, an eventual React md-editor).
12
+ */
13
+ export { default as MdxContent } from './MdxContent';
14
+ export type { MdxContentProps } from './MdxContent';
15
+ export { serverRenderMap } from './server-render-map';
16
+ export { reactRegistry, componentMetadata, getComponentMetadata, getComponentNames, getComponentVariants, getRenderComponentAsync, type ReactComponentRegistration, } from './registry';
17
+ export { createMarked, marked, defaultImageHtml } from './marked';
18
+ export type { ImageHtmlRenderer } from './marked';
19
+ export * from './render/index';
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ import { default as o } from "./MdxContent.js";
2
+ import { serverRenderMap as r } from "./server-render-map.js";
3
+ import { getComponentVariants as l, getRenderComponentAsync as m, reactRegistry as d } from "./registry.js";
4
+ import { createMarked as u, marked as n } from "./marked.js";
5
+ import { default as s } from "./render/callout/CalloutDefault.js";
6
+ import { default as g } from "./render/callout/CalloutMinimal.js";
7
+ import { default as c } from "./render/youtube/YouTubeDefault.js";
8
+ import { default as y } from "./render/gallery/GalleryGrid.js";
9
+ import { default as k } from "./render/codeblock/CodeBlockDefault.js";
10
+ import { default as B } from "./render/audioplayer/AudioPlayerDefault.js";
11
+ import { default as G } from "./render/button/Button.js";
12
+ import { default as T } from "./render/card/Card.js";
13
+ import { default as v } from "./render/alert/Alert.js";
14
+ import { default as N } from "./render/badge/Badge.js";
15
+ import { default as V } from "./render/icon/Icon.js";
16
+ import { default as h } from "./render/tooltip/Tooltip.js";
17
+ import { componentMetadata as q, getComponentMetadata as w, getComponentNames as z } from "@blogmcp/mdx-schema/metadata";
18
+ import { defaultImageHtml as F } from "@blogmcp/sdk";
19
+ export {
20
+ v as Alert,
21
+ B as AudioPlayerDefault,
22
+ N as Badge,
23
+ G as Button,
24
+ s as CalloutDefault,
25
+ g as CalloutMinimal,
26
+ T as Card,
27
+ k as CodeBlockDefault,
28
+ y as GalleryGrid,
29
+ V as Icon,
30
+ o as MdxContent,
31
+ h as Tooltip,
32
+ c as YouTubeDefault,
33
+ q as componentMetadata,
34
+ u as createMarked,
35
+ F as defaultImageHtml,
36
+ w as getComponentMetadata,
37
+ z as getComponentNames,
38
+ l as getComponentVariants,
39
+ m as getRenderComponentAsync,
40
+ n as marked,
41
+ d as reactRegistry,
42
+ r as serverRenderMap
43
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Configured `marked` instance for the React renderer.
3
+ *
4
+ * Reuses the SDK's `createMarkedInstance` so the image/link renderers, table
5
+ * wrap, and `th scope` post-processing match the Vue blog (KbMdxContent.vue)
6
+ * exactly. Consumers may pass a custom image renderer (IPX, Cloudinary, etc.)
7
+ * and an optional HTML sanitizer.
8
+ *
9
+ * Sanitization is wired by default via `@blogmcp/sanitize` — the reference
10
+ * renderer ships safe-by-default. Callers can opt out by passing `null`
11
+ * (intentional, grep-able) for trusted-self surfaces or when a downstream
12
+ * renderer applies its own sanitization.
13
+ */
14
+ import { defaultImageHtml, type ImageHtmlRenderer, type SanitizeFn } from '@blogmcp/sdk';
15
+ import { sanitizeHtml } from '@blogmcp/sanitize';
16
+ import type { Marked } from 'marked';
17
+ export { defaultImageHtml, sanitizeHtml };
18
+ export type { ImageHtmlRenderer, SanitizeFn };
19
+ export interface CreateMarkedOptions {
20
+ /** Custom image renderer. Defaults to plain <img> with lazy loading. */
21
+ imageRenderer?: ImageHtmlRenderer;
22
+ /**
23
+ * HTML sanitizer applied as the last step of `marked.parse()`. Defaults
24
+ * to `sanitizeHtml` from `@blogmcp/sanitize` (DOMPurify with our strict
25
+ * config). Pass `null` to opt out — only do this for trusted-self surfaces
26
+ * or when a downstream renderer applies its own sanitization. The opt-out
27
+ * is explicit and grep-able.
28
+ */
29
+ sanitize?: SanitizeFn | null;
30
+ }
31
+ /** Create a configured Marked instance (sanitize-on by default). */
32
+ export declare function createMarked(opts?: CreateMarkedOptions): Marked;
33
+ /** Shared default instance for the common case (no custom options). */
34
+ export declare const marked: Marked;
package/dist/marked.js ADDED
@@ -0,0 +1,15 @@
1
+ import { defaultImageHtml as a, createMarkedInstance as i } from "@blogmcp/sdk";
2
+ import { defaultImageHtml as s } from "@blogmcp/sdk";
3
+ import { sanitizeHtml as n } from "@blogmcp/sanitize";
4
+ import { sanitizeHtml as z } from "@blogmcp/sanitize";
5
+ function m(e = {}) {
6
+ const t = e.imageRenderer ?? a, r = e.sanitize === null ? void 0 : e.sanitize ?? n;
7
+ return i({ imageRenderer: t, sanitize: r });
8
+ }
9
+ const l = m();
10
+ export {
11
+ m as createMarked,
12
+ s as defaultImageHtml,
13
+ l as marked,
14
+ z as sanitizeHtml
15
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * React MDX Component Registry.
3
+ *
4
+ * Mirrors `@blogmcp/mdx-render-vue/src/registry.ts` against the shared schema.
5
+ * Attaches React-specific lazy render loaders to the framework-agnostic
6
+ * metadata published by `@blogmcp/mdx-schema`. Schema is the single source of
7
+ * truth for component descriptions, prop shapes, and MCP-facing examples.
8
+ *
9
+ * This registry is for future tooling (dynamic resolution, editor previews).
10
+ * The blog/RSC render path uses the static `serverRenderMap` instead.
11
+ */
12
+ import { componentMetadata, getComponentMetadata, getComponentNames, type ComponentMetadata } from '@blogmcp/mdx-schema/metadata';
13
+ export { componentMetadata, getComponentMetadata, getComponentNames };
14
+ export interface ReactComponentRegistration {
15
+ metadata: ComponentMetadata;
16
+ /** Render variants: variant name → async loader. Must include 'default'. */
17
+ render: Record<string, () => Promise<{
18
+ default: unknown;
19
+ }>>;
20
+ /**
21
+ * Optional edit component loader. The blog only needs `render`; an editor
22
+ * (React md-editor port) would populate this later.
23
+ */
24
+ edit?: () => Promise<{
25
+ default: unknown;
26
+ }>;
27
+ }
28
+ /** Merged registry: schema metadata + React wiring, keyed by component name. */
29
+ export declare const reactRegistry: Record<string, ReactComponentRegistration>;
30
+ /** Get available render variant names for a component. */
31
+ export declare function getComponentVariants(name: string): string[];
32
+ /** Async resolver — loads only the requested render component. */
33
+ export declare function getRenderComponentAsync(name: string, variant?: string): Promise<unknown | undefined>;
@@ -0,0 +1,88 @@
1
+ import { componentMetadata as d } from "@blogmcp/mdx-schema/metadata";
2
+ import { componentMetadata as f, getComponentMetadata as s, getComponentNames as g } from "@blogmcp/mdx-schema/metadata";
3
+ const i = {
4
+ Callout: {
5
+ render: {
6
+ default: () => import("./render/callout/CalloutDefault.js"),
7
+ minimal: () => import("./render/callout/CalloutMinimal.js")
8
+ }
9
+ },
10
+ YouTube: {
11
+ render: {
12
+ default: () => import("./render/youtube/YouTubeDefault.js")
13
+ }
14
+ },
15
+ Gallery: {
16
+ render: {
17
+ default: () => import("./render/gallery/GalleryGrid.js"),
18
+ grid: () => import("./render/gallery/GalleryGrid.js")
19
+ }
20
+ },
21
+ CodeBlock: {
22
+ render: {
23
+ default: () => import("./render/codeblock/CodeBlockDefault.js")
24
+ }
25
+ },
26
+ AudioPlayer: {
27
+ render: {
28
+ default: () => import("./render/audioplayer/AudioPlayerDefault.js")
29
+ }
30
+ },
31
+ Button: {
32
+ render: {
33
+ default: () => import("./render/button/Button.js")
34
+ }
35
+ },
36
+ Card: {
37
+ render: {
38
+ default: () => import("./render/card/Card.js")
39
+ }
40
+ },
41
+ Alert: {
42
+ render: {
43
+ default: () => import("./render/alert/Alert.js")
44
+ }
45
+ },
46
+ Badge: {
47
+ render: {
48
+ default: () => import("./render/badge/Badge.js")
49
+ }
50
+ },
51
+ Icon: {
52
+ render: {
53
+ default: () => import("./render/icon/Icon.js")
54
+ }
55
+ },
56
+ Tooltip: {
57
+ render: {
58
+ default: () => import("./render/tooltip/Tooltip.js")
59
+ }
60
+ }
61
+ }, o = Object.fromEntries(
62
+ d.map((e) => {
63
+ const r = i[e.name];
64
+ if (!r)
65
+ throw new Error(
66
+ `[mdx-render-react] No React wiring registered for component "${e.name}". Add an entry to reactWiring in registry.ts or remove the schema entry.`
67
+ );
68
+ return [e.name, { metadata: e, ...r }];
69
+ })
70
+ );
71
+ function u(e) {
72
+ const r = o[e];
73
+ return r ? Object.keys(r.render) : [];
74
+ }
75
+ async function p(e, r = "default") {
76
+ const t = o[e];
77
+ if (!t) return;
78
+ const n = t.render[r] || t.render.default;
79
+ return n ? (await n()).default : void 0;
80
+ }
81
+ export {
82
+ f as componentMetadata,
83
+ s as getComponentMetadata,
84
+ g as getComponentNames,
85
+ u as getComponentVariants,
86
+ p as getRenderComponentAsync,
87
+ o as reactRegistry
88
+ };
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type AlertSeverity } from '@blogmcp/mdx-schema/styles';
3
+ export interface AlertProps {
4
+ severity?: AlertSeverity;
5
+ title?: string;
6
+ children?: ReactNode;
7
+ }
8
+ /**
9
+ * Inline notice — flatter/smaller than Callout. Mirrors Vue Alert.vue:
10
+ * severity → wrap classes + iconify icon + icon color (shared table),
11
+ * optional bold title, children inside a `prose-sm` body.
12
+ */
13
+ export default function Alert({ severity, title, children }: AlertProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsxs as o, jsx as s } from "react/jsx-runtime";
2
+ import { Icon as l } from "@iconify/react";
3
+ import { alertStyles as r } from "@blogmcp/mdx-schema/styles";
4
+ function d({ severity: n = "info", title: i, children: a }) {
5
+ const e = r.severity[n];
6
+ return /* @__PURE__ */ o(
7
+ "div",
8
+ {
9
+ className: [r.base, e.wrap].join(" "),
10
+ role: "alert",
11
+ "data-mdx-component": "Alert",
12
+ children: [
13
+ /* @__PURE__ */ s(l, { icon: e.icon, className: ["mt-0.5 shrink-0", e.iconClass].join(" "), width: 18, height: 18 }),
14
+ /* @__PURE__ */ o("div", { className: "min-w-0 flex-1", children: [
15
+ i ? /* @__PURE__ */ s("div", { className: "mb-0.5 font-semibold", children: i }) : null,
16
+ /* @__PURE__ */ s("div", { className: "prose-sm", children: a })
17
+ ] })
18
+ ]
19
+ }
20
+ );
21
+ }
22
+ export {
23
+ d as default
24
+ };
@@ -0,0 +1,6 @@
1
+ import type { AudioPlayerProps } from '@blogmcp/mdx-schema';
2
+ /**
3
+ * Audio player with a collapsible, lazy-loaded transcript. Client island —
4
+ * mirrors Vue AudioPlayerDefault.vue + useAudioPlayer.ts behaviour and classes.
5
+ */
6
+ export default function AudioPlayerDefault({ slug, title, showTranscription }: AudioPlayerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { jsxs as n, jsx as r, Fragment as w } from "react/jsx-runtime";
3
+ import { useState as o, useCallback as x } from "react";
4
+ function j({ slug: e, title: l, showTranscription: h = !1 }) {
5
+ const y = !!l, f = l || e || "Audio", g = e ? `/api/media/file/${e}` : "", c = e ? `/api/media/transcription/${e}` : "", [t, s] = o(null), [d, b] = o(null), [v, m] = o(!1), [p, N] = o(!1), u = x(async () => {
6
+ if (!(!e || t !== null)) {
7
+ m(!0);
8
+ try {
9
+ const i = await fetch(c);
10
+ if (i.ok) {
11
+ const a = await i.json();
12
+ s(a.fullText || a.full_text || ""), b(a.summary || null);
13
+ } else
14
+ s("");
15
+ } catch {
16
+ s("");
17
+ } finally {
18
+ m(!1);
19
+ }
20
+ }
21
+ }, [e, c, t]), k = x(() => {
22
+ N((i) => {
23
+ const a = !i;
24
+ return a && t === null && u(), a;
25
+ });
26
+ }, [u, t]);
27
+ return /* @__PURE__ */ n("div", { className: "audio-player my-6 rounded-lg bg-gray-50 p-5 border border-gray-200", "data-mdx-component": "AudioPlayer", children: [
28
+ /* @__PURE__ */ n("div", { className: "flex items-center gap-3 mb-3", children: [
29
+ /* @__PURE__ */ r("svg", { className: "w-5 h-5 text-gray-500 flex-shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ r(
30
+ "path",
31
+ {
32
+ strokeLinecap: "round",
33
+ strokeLinejoin: "round",
34
+ strokeWidth: 2,
35
+ d: "M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
36
+ }
37
+ ) }),
38
+ y ? /* @__PURE__ */ r("h3", { className: "text-base font-semibold text-gray-800 m-0", children: f }) : null
39
+ ] }),
40
+ /* @__PURE__ */ r("audio", { src: g, controls: !0, preload: "metadata", className: "w-full" }),
41
+ h ? /* @__PURE__ */ n("div", { className: "mt-4", children: [
42
+ /* @__PURE__ */ n(
43
+ "button",
44
+ {
45
+ type: "button",
46
+ className: "flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900 transition-colors cursor-pointer bg-transparent border-0 p-0",
47
+ onClick: k,
48
+ children: [
49
+ /* @__PURE__ */ r(
50
+ "svg",
51
+ {
52
+ className: ["w-4 h-4 transition-transform", p ? "rotate-90" : ""].join(" ").trim(),
53
+ fill: "none",
54
+ stroke: "currentColor",
55
+ viewBox: "0 0 24 24",
56
+ children: /* @__PURE__ */ r("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" })
57
+ }
58
+ ),
59
+ "Transcription"
60
+ ]
61
+ }
62
+ ),
63
+ p ? /* @__PURE__ */ r("div", { className: "mt-2 p-3 bg-white rounded border border-gray-200 text-sm text-gray-700 leading-relaxed max-h-64 overflow-y-auto", children: v ? /* @__PURE__ */ r("p", { className: "text-gray-400 italic", children: "Loading transcription..." }) : t ? /* @__PURE__ */ n(w, { children: [
64
+ d ? /* @__PURE__ */ n("div", { className: "mb-3 pb-3 border-b border-gray-200", children: [
65
+ /* @__PURE__ */ r("div", { className: "text-xs uppercase tracking-wide text-gray-400 mb-1", children: "Summary" }),
66
+ /* @__PURE__ */ r("div", { className: "text-gray-800", children: d })
67
+ ] }) : null,
68
+ /* @__PURE__ */ r("div", { className: "whitespace-pre-wrap", children: t })
69
+ ] }) : /* @__PURE__ */ r("p", { className: "text-gray-400 italic", children: "No transcription available." }) }) : null
70
+ ] }) : null
71
+ ] });
72
+ }
73
+ export {
74
+ j as default
75
+ };
@@ -0,0 +1,11 @@
1
+ import { type BadgeVariant, type BadgeColor } from '@blogmcp/mdx-schema/styles';
2
+ export interface BadgeProps {
3
+ label: string;
4
+ color?: BadgeColor;
5
+ variant?: BadgeVariant;
6
+ }
7
+ /**
8
+ * Small label / tag. Mirrors Vue Badge.vue — a single <span> with the
9
+ * shared base + variant×color class table.
10
+ */
11
+ export default function Badge({ label, color, variant }: BadgeProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as r } from "react/jsx-runtime";
2
+ import { badgeStyles as a } from "@blogmcp/mdx-schema/styles";
3
+ function m({ label: o, color: t = "neutral", variant: e = "soft" }) {
4
+ const n = [
5
+ a.base,
6
+ a.variantColor[e][t]
7
+ ].join(" ");
8
+ return /* @__PURE__ */ r("span", { className: n, "data-mdx-component": "Badge", children: o });
9
+ }
10
+ export {
11
+ m as default
12
+ };
@@ -0,0 +1,14 @@
1
+ import { type ButtonVariant, type ButtonColor, type ButtonSize } from '@blogmcp/mdx-schema/styles';
2
+ export interface ButtonProps {
3
+ label: string;
4
+ to?: string;
5
+ variant?: ButtonVariant;
6
+ color?: ButtonColor;
7
+ size?: ButtonSize;
8
+ }
9
+ /**
10
+ * Action button / CTA link. Mirrors Vue Button.vue:
11
+ * renders <a> when `to` is set, else <button type="button">.
12
+ * Class tables come from the shared @blogmcp/mdx-schema/styles module.
13
+ */
14
+ export default function Button({ label, to, variant, color, size, }: ButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as i } from "react/jsx-runtime";
2
+ import { buttonStyles as t } from "@blogmcp/mdx-schema/styles";
3
+ import { sanitizeUrl as d } from "@blogmcp/sanitize";
4
+ function p({
5
+ label: o,
6
+ to: n,
7
+ variant: a = "solid",
8
+ color: m = "primary",
9
+ size: s = "md"
10
+ }) {
11
+ const r = [
12
+ t.base,
13
+ t.size[s],
14
+ t.variantColor[a][m]
15
+ ].join(" "), e = n ? d(n) : void 0;
16
+ return e ? /* @__PURE__ */ i("a", { href: e, className: r, "data-mdx-component": "Button", children: o }) : /* @__PURE__ */ i("button", { type: "button", className: r, "data-mdx-component": "Button", children: o });
17
+ }
18
+ export {
19
+ p as default
20
+ };
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { CalloutType } from '@blogmcp/mdx-schema';
3
+ export interface CalloutProps {
4
+ type?: CalloutType;
5
+ children?: ReactNode;
6
+ }
7
+ /**
8
+ * Standard callout (icon + accent left border). Mirrors Vue CalloutDefault.vue.
9
+ * Color table + icon SVG path data come from the shared styles module.
10
+ */
11
+ export default function CalloutDefault({ type, children }: CalloutProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as e, jsxs as i } from "react/jsx-runtime";
2
+ import { calloutStyles as o } from "@blogmcp/mdx-schema/styles";
3
+ function d({ type: s, children: a }) {
4
+ const l = s || "info", t = o.default.type[l], n = o.iconPaths[l];
5
+ return /* @__PURE__ */ e(
6
+ "div",
7
+ {
8
+ className: [o.default.base, t.bg, t.border, t.text].join(" "),
9
+ "data-mdx-component": "Callout",
10
+ children: /* @__PURE__ */ i("div", { className: "flex items-start gap-3", children: [
11
+ /* @__PURE__ */ e("div", { className: "flex-shrink-0 mt-0.5", children: /* @__PURE__ */ e("svg", { className: ["w-5 h-5", t.iconColor].join(" "), fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: n }) }) }),
12
+ /* @__PURE__ */ e("div", { className: "flex-1 text-sm leading-relaxed", children: a })
13
+ ] })
14
+ }
15
+ );
16
+ }
17
+ export {
18
+ d as default
19
+ };
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { CalloutType } from '@blogmcp/mdx-schema';
3
+ export interface CalloutProps {
4
+ type?: CalloutType;
5
+ children?: ReactNode;
6
+ }
7
+ /**
8
+ * Icon-less, flatter callout. Mirrors Vue CalloutMinimal.vue.
9
+ */
10
+ export default function CalloutMinimal({ type, children }: CalloutProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as t } from "react/jsx-runtime";
2
+ import { calloutStyles as o } from "@blogmcp/mdx-schema/styles";
3
+ function r({ type: a, children: l }) {
4
+ const i = a || "info", e = o.minimal.type[i];
5
+ return /* @__PURE__ */ t(
6
+ "div",
7
+ {
8
+ className: [o.minimal.base, e.bg, e.border, e.text].join(" "),
9
+ "data-mdx-component": "Callout",
10
+ children: /* @__PURE__ */ t("div", { className: "text-sm leading-relaxed", children: l })
11
+ }
12
+ );
13
+ }
14
+ export {
15
+ r as default
16
+ };
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface CardProps {
3
+ title?: string;
4
+ children?: ReactNode;
5
+ }
6
+ /**
7
+ * Bordered content block with optional title. Mirrors Vue Card.vue.
8
+ */
9
+ export default function Card({ title, children }: CardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsxs as d, jsx as a } from "react/jsx-runtime";
2
+ function t({ title: r, children: e }) {
3
+ return /* @__PURE__ */ d(
4
+ "section",
5
+ {
6
+ className: "mdx-card rounded-lg border border-gray-200 bg-white p-5 shadow-sm",
7
+ "data-mdx-component": "Card",
8
+ children: [
9
+ r ? /* @__PURE__ */ a("h3", { className: "mb-3 text-lg font-semibold text-gray-900", children: r }) : null,
10
+ /* @__PURE__ */ a("div", { className: "text-gray-700", children: e })
11
+ ]
12
+ }
13
+ );
14
+ }
15
+ export {
16
+ t as default
17
+ };
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { CodeBlockProps as SchemaCodeBlockProps } from '@blogmcp/mdx-schema';
3
+ export interface CodeBlockProps extends SchemaCodeBlockProps {
4
+ children?: ReactNode;
5
+ }
6
+ /**
7
+ * Syntax-highlighted code block. Client island — mirrors Vue CodeBlockDefault.vue
8
+ * + useCodeBlock.ts. The header (language label + optional title) and the
9
+ * `<pre><code>` block render as the initial DOM exactly as before; the copy
10
+ * button now copies the code via `navigator.clipboard.writeText` and shows a
11
+ * transient "Copied!" state (2s), toggling the same Tailwind classes as Vue.
12
+ * `code-block` + `data-mdx-component="CodeBlock"` are unchanged.
13
+ */
14
+ export default function CodeBlockDefault({ language, title, children }: CodeBlockProps): import("react/jsx-runtime").JSX.Element;