@kyro-cms/kyro-rich-text-react 0.12.15
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 +172 -0
- package/dist/index.cjs +134 -0
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +95 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# kyro-rich-text-react
|
|
2
|
+
|
|
3
|
+
A lightweight, headless React renderer for [Kyro CMS](https://kyro.dev) rich text content.
|
|
4
|
+
|
|
5
|
+
When you use the `richText` field in Kyro CMS, it stores the content as structured Tiptap/ProseMirror JSON (not raw HTML). This package allows you to easily render that JSON structure into React components.
|
|
6
|
+
|
|
7
|
+
By default, it is **completely headless and unstyled**, outputting clean semantic HTML (`<p>`, `<h1>`, `<strong>`). You have full control over the rendering by passing in custom components.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install kyro-rich-text-react
|
|
15
|
+
# or
|
|
16
|
+
pnpm add kyro-rich-text-react
|
|
17
|
+
# or
|
|
18
|
+
yarn add kyro-rich-text-react
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
Import the `KyroRichTextRenderer` component and pass the JSON array from your Kyro API to the `content` prop.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import React from 'react';
|
|
29
|
+
import { KyroRichTextRenderer } from 'kyro-rich-text-react';
|
|
30
|
+
|
|
31
|
+
export default function BlogPost({ post }) {
|
|
32
|
+
// post.content is the JSON array from Kyro
|
|
33
|
+
return (
|
|
34
|
+
<article className="blog-content">
|
|
35
|
+
<KyroRichTextRenderer content={post.content} />
|
|
36
|
+
</article>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Customizing Components (Advanced)
|
|
44
|
+
|
|
45
|
+
Because the renderer is headless, it doesn't come with CSS classes. If you use a CSS framework like Tailwind, or if you simply want to use your own custom React components for specific nodes (like links or headings), use the `components` prop.
|
|
46
|
+
|
|
47
|
+
The `components` prop accepts an object with two keys:
|
|
48
|
+
- `types`: For block-level nodes (paragraphs, headings, blockquotes, images)
|
|
49
|
+
- `marks`: For inline text formatting (bold, italic, links)
|
|
50
|
+
|
|
51
|
+
### Example: Tailwind CSS & Custom Components
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import React from 'react';
|
|
55
|
+
import { KyroRichTextRenderer, KyroRichTextComponents } from 'kyro-rich-text-react';
|
|
56
|
+
import Link from 'next/link';
|
|
57
|
+
|
|
58
|
+
const customComponents: KyroRichTextComponents = {
|
|
59
|
+
types: {
|
|
60
|
+
// Override headings to include Tailwind classes
|
|
61
|
+
heading: ({ node, children }) => {
|
|
62
|
+
const level = node.attrs?.level || 1;
|
|
63
|
+
const classes = {
|
|
64
|
+
1: 'text-4xl font-extrabold mb-6 mt-8',
|
|
65
|
+
2: 'text-2xl font-bold mb-4 mt-6',
|
|
66
|
+
3: 'text-xl font-semibold mb-3 mt-4',
|
|
67
|
+
};
|
|
68
|
+
const Tag = `h${level}` as keyof JSX.IntrinsicElements;
|
|
69
|
+
|
|
70
|
+
return <Tag className={classes[level] || ''}>{children}</Tag>;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Custom Paragraphs
|
|
74
|
+
paragraph: ({ children }) => (
|
|
75
|
+
<p className="text-gray-700 leading-relaxed mb-4">{children}</p>
|
|
76
|
+
),
|
|
77
|
+
|
|
78
|
+
// Render Kyro uploads as responsive images
|
|
79
|
+
image: ({ node }) => (
|
|
80
|
+
<img
|
|
81
|
+
src={node.attrs?.src}
|
|
82
|
+
alt={node.attrs?.alt || 'Image'}
|
|
83
|
+
className="w-full rounded-xl shadow-lg my-6"
|
|
84
|
+
/>
|
|
85
|
+
),
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
marks: {
|
|
89
|
+
// Map links to Next.js Link component for internal routing
|
|
90
|
+
link: ({ mark, children }) => {
|
|
91
|
+
const href = mark.attrs?.href || '#';
|
|
92
|
+
const isInternal = href.startsWith('/') || href.startsWith('#');
|
|
93
|
+
|
|
94
|
+
if (isInternal) {
|
|
95
|
+
return <Link href={href} className="text-blue-600 hover:underline">{children}</Link>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// External link
|
|
99
|
+
return (
|
|
100
|
+
<a href={href} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
|
|
101
|
+
{children}
|
|
102
|
+
</a>
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Style bold text
|
|
107
|
+
bold: ({ children }) => <strong className="font-bold text-gray-900">{children}</strong>
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default function BlogPost({ post }) {
|
|
112
|
+
return (
|
|
113
|
+
<KyroRichTextRenderer
|
|
114
|
+
content={post.content}
|
|
115
|
+
components={customComponents}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Using with Tailwind Typography
|
|
124
|
+
|
|
125
|
+
If you use the Tailwind `@tailwindcss/typography` plugin, you don't even need to write custom overrides. You can simply wrap the default headless output in a `.prose` class, and Tailwind will style all the semantic HTML automatically:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { KyroRichTextRenderer } from 'kyro-rich-text-react';
|
|
129
|
+
|
|
130
|
+
export default function BlogPost({ post }) {
|
|
131
|
+
return (
|
|
132
|
+
<div className="prose prose-blue lg:prose-xl mx-auto">
|
|
133
|
+
<KyroRichTextRenderer content={post.content} />
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Supported Nodes and Marks
|
|
142
|
+
|
|
143
|
+
By default, the renderer supports mapping these Tiptap formats to standard HTML tags:
|
|
144
|
+
|
|
145
|
+
**Types (Blocks):**
|
|
146
|
+
- `doc` -> `<React.Fragment>`
|
|
147
|
+
- `paragraph` -> `<p>`
|
|
148
|
+
- `heading` -> `<h1>` - `<h6>`
|
|
149
|
+
- `blockquote` -> `<blockquote>`
|
|
150
|
+
- `bulletList` -> `<ul>`
|
|
151
|
+
- `orderedList` -> `<ol>`
|
|
152
|
+
- `listItem` -> `<li>`
|
|
153
|
+
- `codeBlock` -> `<pre><code>`
|
|
154
|
+
- `horizontalRule` -> `<hr />`
|
|
155
|
+
- `hardBreak` -> `<br />`
|
|
156
|
+
- `image` -> `<img>`
|
|
157
|
+
|
|
158
|
+
**Marks (Inline):**
|
|
159
|
+
- `bold` -> `<strong>`
|
|
160
|
+
- `italic` -> `<em>`
|
|
161
|
+
- `strike` -> `<s>`
|
|
162
|
+
- `code` -> `<code>`
|
|
163
|
+
- `underline` -> `<u>`
|
|
164
|
+
- `link` -> `<a>`
|
|
165
|
+
|
|
166
|
+
If your Kyro CMS uses custom blocks in the rich text editor, you can handle them by adding a new key to the `types` object in your `components` prop matching your custom block's `type` string.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
KyroRichTextRenderer: () => KyroRichTextRenderer,
|
|
34
|
+
defaultMarks: () => defaultMarks,
|
|
35
|
+
defaultTypes: () => defaultTypes
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/KyroRichTextRenderer.tsx
|
|
40
|
+
var import_react = __toESM(require("react"), 1);
|
|
41
|
+
|
|
42
|
+
// src/defaultComponents.tsx
|
|
43
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
44
|
+
var defaultTypes = {
|
|
45
|
+
doc: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children }),
|
|
46
|
+
paragraph: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children }),
|
|
47
|
+
heading: ({ node, children }) => {
|
|
48
|
+
const level = node.attrs?.level || 1;
|
|
49
|
+
const Tag = `h${level}`;
|
|
50
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag, { children });
|
|
51
|
+
},
|
|
52
|
+
blockquote: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("blockquote", { children }),
|
|
53
|
+
bulletList: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children }),
|
|
54
|
+
orderedList: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { children }),
|
|
55
|
+
listItem: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children }),
|
|
56
|
+
codeBlock: ({ node, children }) => {
|
|
57
|
+
const language = node.attrs?.language;
|
|
58
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: language ? `language-${language}` : "", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children }) });
|
|
59
|
+
},
|
|
60
|
+
horizontalRule: () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", {}),
|
|
61
|
+
hardBreak: () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
|
|
62
|
+
image: ({ node }) => {
|
|
63
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
64
|
+
"img",
|
|
65
|
+
{
|
|
66
|
+
src: node.attrs?.src,
|
|
67
|
+
alt: node.attrs?.alt || "",
|
|
68
|
+
title: node.attrs?.title || ""
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var defaultMarks = {
|
|
74
|
+
bold: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children }),
|
|
75
|
+
italic: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("em", { children }),
|
|
76
|
+
strike: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("s", { children }),
|
|
77
|
+
code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children }),
|
|
78
|
+
underline: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("u", { children }),
|
|
79
|
+
link: ({ mark, children }) => {
|
|
80
|
+
const { href, target, rel } = mark.attrs || {};
|
|
81
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href, target, rel: rel || (target === "_blank" ? "noopener noreferrer" : void 0), children });
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/KyroRichTextRenderer.tsx
|
|
86
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
87
|
+
var KyroRichTextRenderer = ({ content, components = {} }) => {
|
|
88
|
+
const mergedTypes = (0, import_react.useMemo)(
|
|
89
|
+
() => ({ ...defaultTypes, ...components.types }),
|
|
90
|
+
[components.types]
|
|
91
|
+
);
|
|
92
|
+
const mergedMarks = (0, import_react.useMemo)(
|
|
93
|
+
() => ({ ...defaultMarks, ...components.marks }),
|
|
94
|
+
[components.marks]
|
|
95
|
+
);
|
|
96
|
+
const renderNode = (node, index) => {
|
|
97
|
+
if (node.type === "text") {
|
|
98
|
+
let element = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react.default.Fragment, { children: node.text }, index);
|
|
99
|
+
if (node.marks && node.marks.length > 0) {
|
|
100
|
+
const marks = [...node.marks].reverse();
|
|
101
|
+
marks.forEach((mark, markIndex) => {
|
|
102
|
+
const MarkComponent = mergedMarks[mark.type];
|
|
103
|
+
if (MarkComponent) {
|
|
104
|
+
element = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MarkComponent, { mark, children: element }, `${index}-${markIndex}`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return element;
|
|
109
|
+
}
|
|
110
|
+
if (node.type) {
|
|
111
|
+
const NodeComponent = mergedTypes[node.type];
|
|
112
|
+
let children = null;
|
|
113
|
+
if (node.content && node.content.length > 0) {
|
|
114
|
+
children = node.content.map((child, childIndex) => renderNode(child, childIndex));
|
|
115
|
+
}
|
|
116
|
+
if (NodeComponent) {
|
|
117
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NodeComponent, { node, children }, index);
|
|
118
|
+
}
|
|
119
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react.default.Fragment, { children }, index);
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
if (!content) return null;
|
|
124
|
+
if (Array.isArray(content)) {
|
|
125
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content.map((node, index) => renderNode(node, index)) });
|
|
126
|
+
}
|
|
127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderNode(content, 0) });
|
|
128
|
+
};
|
|
129
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
130
|
+
0 && (module.exports = {
|
|
131
|
+
KyroRichTextRenderer,
|
|
132
|
+
defaultMarks,
|
|
133
|
+
defaultTypes
|
|
134
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React$1 from 'react';
|
|
2
|
+
|
|
3
|
+
interface JSONContent {
|
|
4
|
+
type?: string;
|
|
5
|
+
attrs?: Record<string, any>;
|
|
6
|
+
content?: JSONContent[];
|
|
7
|
+
marks?: {
|
|
8
|
+
type: string;
|
|
9
|
+
attrs?: Record<string, any>;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}[];
|
|
12
|
+
text?: string;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
type CustomComponentProps = {
|
|
16
|
+
node: JSONContent;
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
type MarkComponentProps = {
|
|
20
|
+
mark: {
|
|
21
|
+
type: string;
|
|
22
|
+
attrs?: Record<string, any>;
|
|
23
|
+
};
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
};
|
|
26
|
+
interface KyroRichTextComponents {
|
|
27
|
+
types?: Record<string, React.ComponentType<CustomComponentProps>>;
|
|
28
|
+
marks?: Record<string, React.ComponentType<MarkComponentProps>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface KyroRichTextRendererProps {
|
|
32
|
+
content: JSONContent | JSONContent[];
|
|
33
|
+
components?: KyroRichTextComponents;
|
|
34
|
+
}
|
|
35
|
+
declare const KyroRichTextRenderer: React$1.FC<KyroRichTextRendererProps>;
|
|
36
|
+
|
|
37
|
+
declare const defaultTypes: Record<string, React$1.ComponentType<CustomComponentProps>>;
|
|
38
|
+
declare const defaultMarks: Record<string, React$1.ComponentType<MarkComponentProps>>;
|
|
39
|
+
|
|
40
|
+
export { type CustomComponentProps, type JSONContent, type KyroRichTextComponents, KyroRichTextRenderer, type MarkComponentProps, defaultMarks, defaultTypes };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React$1 from 'react';
|
|
2
|
+
|
|
3
|
+
interface JSONContent {
|
|
4
|
+
type?: string;
|
|
5
|
+
attrs?: Record<string, any>;
|
|
6
|
+
content?: JSONContent[];
|
|
7
|
+
marks?: {
|
|
8
|
+
type: string;
|
|
9
|
+
attrs?: Record<string, any>;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}[];
|
|
12
|
+
text?: string;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
type CustomComponentProps = {
|
|
16
|
+
node: JSONContent;
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
type MarkComponentProps = {
|
|
20
|
+
mark: {
|
|
21
|
+
type: string;
|
|
22
|
+
attrs?: Record<string, any>;
|
|
23
|
+
};
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
};
|
|
26
|
+
interface KyroRichTextComponents {
|
|
27
|
+
types?: Record<string, React.ComponentType<CustomComponentProps>>;
|
|
28
|
+
marks?: Record<string, React.ComponentType<MarkComponentProps>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface KyroRichTextRendererProps {
|
|
32
|
+
content: JSONContent | JSONContent[];
|
|
33
|
+
components?: KyroRichTextComponents;
|
|
34
|
+
}
|
|
35
|
+
declare const KyroRichTextRenderer: React$1.FC<KyroRichTextRendererProps>;
|
|
36
|
+
|
|
37
|
+
declare const defaultTypes: Record<string, React$1.ComponentType<CustomComponentProps>>;
|
|
38
|
+
declare const defaultMarks: Record<string, React$1.ComponentType<MarkComponentProps>>;
|
|
39
|
+
|
|
40
|
+
export { type CustomComponentProps, type JSONContent, type KyroRichTextComponents, KyroRichTextRenderer, type MarkComponentProps, defaultMarks, defaultTypes };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/KyroRichTextRenderer.tsx
|
|
2
|
+
import React, { useMemo } from "react";
|
|
3
|
+
|
|
4
|
+
// src/defaultComponents.tsx
|
|
5
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
6
|
+
var defaultTypes = {
|
|
7
|
+
doc: ({ children }) => /* @__PURE__ */ jsx(Fragment, { children }),
|
|
8
|
+
paragraph: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
|
|
9
|
+
heading: ({ node, children }) => {
|
|
10
|
+
const level = node.attrs?.level || 1;
|
|
11
|
+
const Tag = `h${level}`;
|
|
12
|
+
return /* @__PURE__ */ jsx(Tag, { children });
|
|
13
|
+
},
|
|
14
|
+
blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { children }),
|
|
15
|
+
bulletList: ({ children }) => /* @__PURE__ */ jsx("ul", { children }),
|
|
16
|
+
orderedList: ({ children }) => /* @__PURE__ */ jsx("ol", { children }),
|
|
17
|
+
listItem: ({ children }) => /* @__PURE__ */ jsx("li", { children }),
|
|
18
|
+
codeBlock: ({ node, children }) => {
|
|
19
|
+
const language = node.attrs?.language;
|
|
20
|
+
return /* @__PURE__ */ jsx("pre", { className: language ? `language-${language}` : "", children: /* @__PURE__ */ jsx("code", { children }) });
|
|
21
|
+
},
|
|
22
|
+
horizontalRule: () => /* @__PURE__ */ jsx("hr", {}),
|
|
23
|
+
hardBreak: () => /* @__PURE__ */ jsx("br", {}),
|
|
24
|
+
image: ({ node }) => {
|
|
25
|
+
return /* @__PURE__ */ jsx(
|
|
26
|
+
"img",
|
|
27
|
+
{
|
|
28
|
+
src: node.attrs?.src,
|
|
29
|
+
alt: node.attrs?.alt || "",
|
|
30
|
+
title: node.attrs?.title || ""
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var defaultMarks = {
|
|
36
|
+
bold: ({ children }) => /* @__PURE__ */ jsx("strong", { children }),
|
|
37
|
+
italic: ({ children }) => /* @__PURE__ */ jsx("em", { children }),
|
|
38
|
+
strike: ({ children }) => /* @__PURE__ */ jsx("s", { children }),
|
|
39
|
+
code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
|
|
40
|
+
underline: ({ children }) => /* @__PURE__ */ jsx("u", { children }),
|
|
41
|
+
link: ({ mark, children }) => {
|
|
42
|
+
const { href, target, rel } = mark.attrs || {};
|
|
43
|
+
return /* @__PURE__ */ jsx("a", { href, target, rel: rel || (target === "_blank" ? "noopener noreferrer" : void 0), children });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/KyroRichTextRenderer.tsx
|
|
48
|
+
import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
|
|
49
|
+
var KyroRichTextRenderer = ({ content, components = {} }) => {
|
|
50
|
+
const mergedTypes = useMemo(
|
|
51
|
+
() => ({ ...defaultTypes, ...components.types }),
|
|
52
|
+
[components.types]
|
|
53
|
+
);
|
|
54
|
+
const mergedMarks = useMemo(
|
|
55
|
+
() => ({ ...defaultMarks, ...components.marks }),
|
|
56
|
+
[components.marks]
|
|
57
|
+
);
|
|
58
|
+
const renderNode = (node, index) => {
|
|
59
|
+
if (node.type === "text") {
|
|
60
|
+
let element = /* @__PURE__ */ jsx2(React.Fragment, { children: node.text }, index);
|
|
61
|
+
if (node.marks && node.marks.length > 0) {
|
|
62
|
+
const marks = [...node.marks].reverse();
|
|
63
|
+
marks.forEach((mark, markIndex) => {
|
|
64
|
+
const MarkComponent = mergedMarks[mark.type];
|
|
65
|
+
if (MarkComponent) {
|
|
66
|
+
element = /* @__PURE__ */ jsx2(MarkComponent, { mark, children: element }, `${index}-${markIndex}`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return element;
|
|
71
|
+
}
|
|
72
|
+
if (node.type) {
|
|
73
|
+
const NodeComponent = mergedTypes[node.type];
|
|
74
|
+
let children = null;
|
|
75
|
+
if (node.content && node.content.length > 0) {
|
|
76
|
+
children = node.content.map((child, childIndex) => renderNode(child, childIndex));
|
|
77
|
+
}
|
|
78
|
+
if (NodeComponent) {
|
|
79
|
+
return /* @__PURE__ */ jsx2(NodeComponent, { node, children }, index);
|
|
80
|
+
}
|
|
81
|
+
return /* @__PURE__ */ jsx2(React.Fragment, { children }, index);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
if (!content) return null;
|
|
86
|
+
if (Array.isArray(content)) {
|
|
87
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: content.map((node, index) => renderNode(node, index)) });
|
|
88
|
+
}
|
|
89
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: renderNode(content, 0) });
|
|
90
|
+
};
|
|
91
|
+
export {
|
|
92
|
+
KyroRichTextRenderer,
|
|
93
|
+
defaultMarks,
|
|
94
|
+
defaultTypes
|
|
95
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kyro-cms/kyro-rich-text-react",
|
|
3
|
+
"version": "0.12.15",
|
|
4
|
+
"description": "React renderer for Kyro CMS Tiptap rich text content",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"prepublishOnly": "pnpm build"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/danielDozie/kyro-cms"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@testing-library/dom": "^10.4.1",
|
|
33
|
+
"@testing-library/react": "^16.3.2",
|
|
34
|
+
"@types/react": "^18.0.0",
|
|
35
|
+
"jsdom": "^29.1.1",
|
|
36
|
+
"react": "^18.0.0",
|
|
37
|
+
"react-dom": "18.3.1",
|
|
38
|
+
"tsup": "^8.0.0",
|
|
39
|
+
"typescript": "^5.0.0",
|
|
40
|
+
"vitest": "^4.1.9"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
}
|
|
45
|
+
}
|