@haroonwaves/blog-kit-react 0.0.4

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 ADDED
@@ -0,0 +1,38 @@
1
+ # @haroonwaves/blog-kit-react
2
+
3
+ React components and hooks for rendering markdown blogs. Includes blog renderer with syntax
4
+ highlighting, blog cards, lists, and search functionality.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install @haroonwaves/blog-kit-react
10
+ # or
11
+ pnpm add @haroonwaves/blog-kit-react
12
+ # or
13
+ yarn add @haroonwaves/blog-kit-react
14
+ ```
15
+
16
+ **Note:** This package requires React 18+ as a peer dependency.
17
+
18
+ ## Documentation
19
+
20
+ For complete documentation, API reference, and examples, please visit the
21
+ [main documentation](https://github.com/haroonwaves/blog-kit).
22
+
23
+ ## Quick Links
24
+
25
+ - [React Package Usage](https://github.com/haroonwaves/blog-kit#react-package)
26
+ - [Components Reference](https://github.com/haroonwaves/blog-kit#react-package)
27
+ - [useBlogs Hook](https://github.com/haroonwaves/blog-kit#useblogs-hook)
28
+ - [Complete Example](https://github.com/haroonwaves/blog-kit#complete-example)
29
+ - [Next.js Integration](https://github.com/haroonwaves/blog-kit#nextjs-integration)
30
+
31
+ ## Related Packages
32
+
33
+ - [@haroonwaves/blog-kit-core](https://www.npmjs.com/package/@haroonwaves/blog-kit-core) - Core
34
+ utilities for parsing markdown files
35
+
36
+ ## License
37
+
38
+ ISC
package/dist/index.cjs ADDED
@@ -0,0 +1,224 @@
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 src_exports = {};
32
+ __export(src_exports, {
33
+ BlogCard: () => BlogCard,
34
+ BlogList: () => BlogList,
35
+ BlogPlaceholder: () => BlogPlaceholder,
36
+ MarkdownRenderer: () => BlogRenderer,
37
+ useBlogs: () => useBlogs
38
+ });
39
+ module.exports = __toCommonJS(src_exports);
40
+
41
+ // src/components/MarkdownRenderer.tsx
42
+ var import_react_markdown = __toESM(require("react-markdown"), 1);
43
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
44
+ var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
45
+ var import_rehype_raw = __toESM(require("rehype-raw"), 1);
46
+ var import_jsx_runtime = require("react/jsx-runtime");
47
+ function BlogRenderer({ content, className = "", components }) {
48
+ const defaultComponents = {
49
+ h1: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-4xl font-bold mb-4 mt-8 text-gray-800", ...props }),
50
+ h2: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-3xl font-bold mb-3 mt-6 text-gray-800", ...props }),
51
+ h3: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-2xl font-semibold mb-2 mt-4 text-gray-800", ...props }),
52
+ h4: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { className: "text-xl font-semibold mb-2 mt-4 text-gray-800", ...props }),
53
+ h5: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h5", { className: "text-lg font-semibold mb-2 mt-3 text-gray-800", ...props }),
54
+ h6: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h6", { className: "text-base font-semibold mb-2 mt-3 text-gray-800", ...props }),
55
+ p: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mb-4 leading-7 text-gray-600", ...props }),
56
+ ul: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "mb-4 ml-6 list-disc text-gray-600", ...props }),
57
+ ol: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "mb-4 ml-6 list-decimal text-gray-600", ...props }),
58
+ li: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "mb-2", ...props }),
59
+ code: ({ className: codeClassName, children, ...props }) => {
60
+ const isInline = !codeClassName;
61
+ return isInline ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "px-1.5 py-0.5 bg-gray-100 rounded text-sm text-red-600", ...props, children }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: codeClassName, ...props, children });
62
+ },
63
+ pre: ({ className: preClassName, children, ...props }) => {
64
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
65
+ "pre",
66
+ {
67
+ className: `mb-4 rounded-lg overflow-x-auto [&>code]:block [&>code]:p-4 ${preClassName || ""}`,
68
+ ...props,
69
+ children
70
+ }
71
+ );
72
+ },
73
+ blockquote: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
74
+ "blockquote",
75
+ {
76
+ className: "border-l-4 border-blue-500 pl-4 italic my-4 text-gray-600",
77
+ ...props
78
+ }
79
+ ),
80
+ a: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: "text-blue-600 hover:text-blue-800 underline", ...props }),
81
+ strong: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { className: "font-semibold text-gray-800", ...props }),
82
+ em: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("em", { className: "italic", ...props }),
83
+ del: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("del", { className: "line-through text-gray-500", ...props }),
84
+ hr: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { className: "my-8 border-gray-300", ...props }),
85
+ br: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", { ...props }),
86
+ img: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { className: "max-w-full h-auto rounded-lg my-4", ...props }),
87
+ table: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-x-auto my-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { className: "min-w-full border border-gray-300 rounded", ...props }) }),
88
+ thead: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { className: "bg-gray-50", ...props }),
89
+ tbody: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { ...props }),
90
+ tr: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { className: "border-b border-gray-300", ...props }),
91
+ th: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: "px-4 py-2 text-left font-semibold border border-gray-300", ...props }),
92
+ td: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "px-4 py-2 border border-gray-300", ...props }),
93
+ input: ({ ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "mr-2", type: "checkbox", disabled: true, ...props })
94
+ };
95
+ const mergedComponents = { ...defaultComponents, ...components };
96
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `prose prose-slate max-w-none ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
97
+ import_react_markdown.default,
98
+ {
99
+ remarkPlugins: [import_remark_gfm.default],
100
+ rehypePlugins: [import_rehype_raw.default, import_rehype_prism_plus.default],
101
+ components: mergedComponents,
102
+ children: content
103
+ }
104
+ ) });
105
+ }
106
+
107
+ // src/components/BlogCard.tsx
108
+ var import_jsx_runtime2 = require("react/jsx-runtime");
109
+ function BlogCard({
110
+ blog,
111
+ basePath = "/blog",
112
+ renderLink,
113
+ className = "",
114
+ showCategory = true,
115
+ showReadingTime = true,
116
+ showDate = true
117
+ }) {
118
+ const href = `${basePath}/${blog.slug}`;
119
+ const defaultLink = (href2, children) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: href2, children });
120
+ const Link = renderLink || defaultLink;
121
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
122
+ "article",
123
+ {
124
+ className: `bg-white rounded-lg border border-gray-200 hover:border-gray-300 p-6 transition-colors ${className}`,
125
+ children: [
126
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-3", children: [
127
+ showCategory && blog.category && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "inline-flex items-center rounded-full border border-transparent bg-orange-100 px-2.5 py-0.5 text-xs font-semibold text-orange-500", children: blog.category }),
128
+ (showReadingTime || showDate) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
129
+ showReadingTime && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: blog.readingTime }),
130
+ showReadingTime && showDate && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u2022" }),
131
+ showDate && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("time", { dateTime: blog.date, children: new Date(blog.date).toLocaleDateString("en-US", {
132
+ year: "numeric",
133
+ month: "long",
134
+ day: "numeric"
135
+ }) })
136
+ ] })
137
+ ] }) }),
138
+ Link(
139
+ href,
140
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-semibold text-xl text-gray-700 mb-2 hover:underline transition-colors", children: blog.title })
141
+ ),
142
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm text-gray-500 leading-6 mb-4", children: blog.description }),
143
+ Link(
144
+ href,
145
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "inline-flex items-center text-blue-600 hover:text-blue-700 font-medium text-sm", children: "Read more \u2192" })
146
+ )
147
+ ]
148
+ }
149
+ );
150
+ }
151
+
152
+ // src/components/BlogList.tsx
153
+ var import_jsx_runtime3 = require("react/jsx-runtime");
154
+ function BlogList({
155
+ blogs,
156
+ basePath = "/blog",
157
+ renderLink,
158
+ className = "",
159
+ emptyMessage = "No blog posts found.",
160
+ cardProps
161
+ }) {
162
+ if (blogs.length === 0) {
163
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `text-center text-gray-500 py-12 ${className}`, children: emptyMessage });
164
+ }
165
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `space-y-6 ${className}`, children: blogs.map((blog) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
166
+ BlogCard,
167
+ {
168
+ blog,
169
+ basePath,
170
+ renderLink,
171
+ ...cardProps
172
+ },
173
+ blog.slug
174
+ )) });
175
+ }
176
+
177
+ // src/components/BlogPlaceholder.tsx
178
+ var import_jsx_runtime4 = require("react/jsx-runtime");
179
+ function BlogPlaceholder({ count = 3, className = "" }) {
180
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `space-y-6 ${className}`, children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "bg-white rounded-lg border border-gray-200 p-6 animate-pulse", children: [
181
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-4 bg-gray-200 rounded mb-3" }),
182
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-6 bg-gray-200 rounded mb-2" }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-4 bg-gray-200 rounded w-3/4" })
184
+ ] }, i)) });
185
+ }
186
+
187
+ // src/hooks/useBlogs.ts
188
+ var import_react = require("react");
189
+ function useBlogs(blogs) {
190
+ const [filteredBlogs, setFilteredBlogs] = (0, import_react.useState)(blogs);
191
+ const [searchTerm, setSearchTerm] = (0, import_react.useState)("");
192
+ const [selectedCategory, setSelectedCategory] = (0, import_react.useState)(null);
193
+ (0, import_react.useEffect)(() => {
194
+ let filtered = blogs;
195
+ if (searchTerm) {
196
+ filtered = filtered.filter(
197
+ (blog) => blog.title.toLowerCase().includes(searchTerm.toLowerCase()) || blog.description.toLowerCase().includes(searchTerm.toLowerCase())
198
+ );
199
+ }
200
+ if (selectedCategory) {
201
+ filtered = filtered.filter((blog) => blog.category === selectedCategory);
202
+ }
203
+ setFilteredBlogs(filtered);
204
+ }, [blogs, searchTerm, selectedCategory]);
205
+ const categories = Array.from(
206
+ new Set(blogs.map((blog) => blog.category).filter(Boolean))
207
+ );
208
+ return {
209
+ blogs: filteredBlogs,
210
+ searchTerm,
211
+ setSearchTerm,
212
+ selectedCategory,
213
+ setSelectedCategory,
214
+ categories
215
+ };
216
+ }
217
+ // Annotate the CommonJS export names for ESM import in node:
218
+ 0 && (module.exports = {
219
+ BlogCard,
220
+ BlogList,
221
+ BlogPlaceholder,
222
+ MarkdownRenderer,
223
+ useBlogs
224
+ });
package/dist/index.css ADDED
@@ -0,0 +1,3 @@
1
+ @import "prismjs/themes/prism-tomorrow.css";
2
+
3
+ /* src/prism.css */
@@ -0,0 +1,66 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ import react__default from 'react';
4
+
5
+ interface BlogMeta {
6
+ title: string;
7
+ description: string;
8
+ date: string;
9
+ category?: string;
10
+ slug: string;
11
+ readingTime: string;
12
+ }
13
+ interface Blog {
14
+ metadata: BlogMeta;
15
+ content: string;
16
+ readingTime: string;
17
+ }
18
+ interface BlogConfig {
19
+ contentDirectory: string;
20
+ blogSubdirectory?: string;
21
+ }
22
+
23
+ interface BlogRendererProps {
24
+ content: string;
25
+ className?: string;
26
+ components?: Record<string, react__default.ComponentType<any>>;
27
+ }
28
+ declare function BlogRenderer({ content, className, components }: BlogRendererProps): react_jsx_runtime.JSX.Element;
29
+
30
+ interface BlogCardProps {
31
+ blog: BlogMeta;
32
+ basePath?: string;
33
+ renderLink?: (href: string, children: react__default.ReactNode) => react__default.ReactNode;
34
+ className?: string;
35
+ showCategory?: boolean;
36
+ showReadingTime?: boolean;
37
+ showDate?: boolean;
38
+ }
39
+ declare function BlogCard({ blog, basePath, renderLink, className, showCategory, showReadingTime, showDate, }: BlogCardProps): react_jsx_runtime.JSX.Element;
40
+
41
+ interface BlogListProps {
42
+ blogs: BlogMeta[];
43
+ basePath?: string;
44
+ renderLink?: BlogCardProps['renderLink'];
45
+ className?: string;
46
+ emptyMessage?: string;
47
+ cardProps?: Omit<BlogCardProps, 'blog' | 'basePath' | 'renderLink'>;
48
+ }
49
+ declare function BlogList({ blogs, basePath, renderLink, className, emptyMessage, cardProps, }: BlogListProps): react_jsx_runtime.JSX.Element;
50
+
51
+ interface BlogPlaceholderProps {
52
+ count?: number;
53
+ className?: string;
54
+ }
55
+ declare function BlogPlaceholder({ count, className }: BlogPlaceholderProps): react_jsx_runtime.JSX.Element;
56
+
57
+ declare function useBlogs(blogs: BlogMeta[]): {
58
+ blogs: BlogMeta[];
59
+ searchTerm: string;
60
+ setSearchTerm: react.Dispatch<react.SetStateAction<string>>;
61
+ selectedCategory: string | null;
62
+ setSelectedCategory: react.Dispatch<react.SetStateAction<string | null>>;
63
+ categories: string[];
64
+ };
65
+
66
+ export { type Blog, BlogCard, type BlogCardProps, type BlogConfig, BlogList, type BlogListProps, type BlogMeta, BlogPlaceholder, type BlogPlaceholderProps, BlogRenderer as MarkdownRenderer, type BlogRendererProps as MarkdownRendererProps, useBlogs };
@@ -0,0 +1,66 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ import react__default from 'react';
4
+
5
+ interface BlogMeta {
6
+ title: string;
7
+ description: string;
8
+ date: string;
9
+ category?: string;
10
+ slug: string;
11
+ readingTime: string;
12
+ }
13
+ interface Blog {
14
+ metadata: BlogMeta;
15
+ content: string;
16
+ readingTime: string;
17
+ }
18
+ interface BlogConfig {
19
+ contentDirectory: string;
20
+ blogSubdirectory?: string;
21
+ }
22
+
23
+ interface BlogRendererProps {
24
+ content: string;
25
+ className?: string;
26
+ components?: Record<string, react__default.ComponentType<any>>;
27
+ }
28
+ declare function BlogRenderer({ content, className, components }: BlogRendererProps): react_jsx_runtime.JSX.Element;
29
+
30
+ interface BlogCardProps {
31
+ blog: BlogMeta;
32
+ basePath?: string;
33
+ renderLink?: (href: string, children: react__default.ReactNode) => react__default.ReactNode;
34
+ className?: string;
35
+ showCategory?: boolean;
36
+ showReadingTime?: boolean;
37
+ showDate?: boolean;
38
+ }
39
+ declare function BlogCard({ blog, basePath, renderLink, className, showCategory, showReadingTime, showDate, }: BlogCardProps): react_jsx_runtime.JSX.Element;
40
+
41
+ interface BlogListProps {
42
+ blogs: BlogMeta[];
43
+ basePath?: string;
44
+ renderLink?: BlogCardProps['renderLink'];
45
+ className?: string;
46
+ emptyMessage?: string;
47
+ cardProps?: Omit<BlogCardProps, 'blog' | 'basePath' | 'renderLink'>;
48
+ }
49
+ declare function BlogList({ blogs, basePath, renderLink, className, emptyMessage, cardProps, }: BlogListProps): react_jsx_runtime.JSX.Element;
50
+
51
+ interface BlogPlaceholderProps {
52
+ count?: number;
53
+ className?: string;
54
+ }
55
+ declare function BlogPlaceholder({ count, className }: BlogPlaceholderProps): react_jsx_runtime.JSX.Element;
56
+
57
+ declare function useBlogs(blogs: BlogMeta[]): {
58
+ blogs: BlogMeta[];
59
+ searchTerm: string;
60
+ setSearchTerm: react.Dispatch<react.SetStateAction<string>>;
61
+ selectedCategory: string | null;
62
+ setSelectedCategory: react.Dispatch<react.SetStateAction<string | null>>;
63
+ categories: string[];
64
+ };
65
+
66
+ export { type Blog, BlogCard, type BlogCardProps, type BlogConfig, BlogList, type BlogListProps, type BlogMeta, BlogPlaceholder, type BlogPlaceholderProps, BlogRenderer as MarkdownRenderer, type BlogRendererProps as MarkdownRendererProps, useBlogs };
package/dist/index.js ADDED
@@ -0,0 +1,183 @@
1
+ // src/components/MarkdownRenderer.tsx
2
+ import ReactMarkdown from "react-markdown";
3
+ import remarkGfm from "remark-gfm";
4
+ import rehypePrismPlus from "rehype-prism-plus";
5
+ import rehypeRaw from "rehype-raw";
6
+ import { jsx } from "react/jsx-runtime";
7
+ function BlogRenderer({ content, className = "", components }) {
8
+ const defaultComponents = {
9
+ h1: ({ ...props }) => /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold mb-4 mt-8 text-gray-800", ...props }),
10
+ h2: ({ ...props }) => /* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold mb-3 mt-6 text-gray-800", ...props }),
11
+ h3: ({ ...props }) => /* @__PURE__ */ jsx("h3", { className: "text-2xl font-semibold mb-2 mt-4 text-gray-800", ...props }),
12
+ h4: ({ ...props }) => /* @__PURE__ */ jsx("h4", { className: "text-xl font-semibold mb-2 mt-4 text-gray-800", ...props }),
13
+ h5: ({ ...props }) => /* @__PURE__ */ jsx("h5", { className: "text-lg font-semibold mb-2 mt-3 text-gray-800", ...props }),
14
+ h6: ({ ...props }) => /* @__PURE__ */ jsx("h6", { className: "text-base font-semibold mb-2 mt-3 text-gray-800", ...props }),
15
+ p: ({ ...props }) => /* @__PURE__ */ jsx("p", { className: "mb-4 leading-7 text-gray-600", ...props }),
16
+ ul: ({ ...props }) => /* @__PURE__ */ jsx("ul", { className: "mb-4 ml-6 list-disc text-gray-600", ...props }),
17
+ ol: ({ ...props }) => /* @__PURE__ */ jsx("ol", { className: "mb-4 ml-6 list-decimal text-gray-600", ...props }),
18
+ li: ({ ...props }) => /* @__PURE__ */ jsx("li", { className: "mb-2", ...props }),
19
+ code: ({ className: codeClassName, children, ...props }) => {
20
+ const isInline = !codeClassName;
21
+ return isInline ? /* @__PURE__ */ jsx("code", { className: "px-1.5 py-0.5 bg-gray-100 rounded text-sm text-red-600", ...props, children }) : /* @__PURE__ */ jsx("code", { className: codeClassName, ...props, children });
22
+ },
23
+ pre: ({ className: preClassName, children, ...props }) => {
24
+ return /* @__PURE__ */ jsx(
25
+ "pre",
26
+ {
27
+ className: `mb-4 rounded-lg overflow-x-auto [&>code]:block [&>code]:p-4 ${preClassName || ""}`,
28
+ ...props,
29
+ children
30
+ }
31
+ );
32
+ },
33
+ blockquote: ({ ...props }) => /* @__PURE__ */ jsx(
34
+ "blockquote",
35
+ {
36
+ className: "border-l-4 border-blue-500 pl-4 italic my-4 text-gray-600",
37
+ ...props
38
+ }
39
+ ),
40
+ a: ({ ...props }) => /* @__PURE__ */ jsx("a", { className: "text-blue-600 hover:text-blue-800 underline", ...props }),
41
+ strong: ({ ...props }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold text-gray-800", ...props }),
42
+ em: ({ ...props }) => /* @__PURE__ */ jsx("em", { className: "italic", ...props }),
43
+ del: ({ ...props }) => /* @__PURE__ */ jsx("del", { className: "line-through text-gray-500", ...props }),
44
+ hr: ({ ...props }) => /* @__PURE__ */ jsx("hr", { className: "my-8 border-gray-300", ...props }),
45
+ br: ({ ...props }) => /* @__PURE__ */ jsx("br", { ...props }),
46
+ img: ({ ...props }) => /* @__PURE__ */ jsx("img", { className: "max-w-full h-auto rounded-lg my-4", ...props }),
47
+ table: ({ ...props }) => /* @__PURE__ */ jsx("div", { className: "overflow-x-auto my-4", children: /* @__PURE__ */ jsx("table", { className: "min-w-full border border-gray-300 rounded", ...props }) }),
48
+ thead: ({ ...props }) => /* @__PURE__ */ jsx("thead", { className: "bg-gray-50", ...props }),
49
+ tbody: ({ ...props }) => /* @__PURE__ */ jsx("tbody", { ...props }),
50
+ tr: ({ ...props }) => /* @__PURE__ */ jsx("tr", { className: "border-b border-gray-300", ...props }),
51
+ th: ({ ...props }) => /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-semibold border border-gray-300", ...props }),
52
+ td: ({ ...props }) => /* @__PURE__ */ jsx("td", { className: "px-4 py-2 border border-gray-300", ...props }),
53
+ input: ({ ...props }) => /* @__PURE__ */ jsx("input", { className: "mr-2", type: "checkbox", disabled: true, ...props })
54
+ };
55
+ const mergedComponents = { ...defaultComponents, ...components };
56
+ return /* @__PURE__ */ jsx("div", { className: `prose prose-slate max-w-none ${className}`, children: /* @__PURE__ */ jsx(
57
+ ReactMarkdown,
58
+ {
59
+ remarkPlugins: [remarkGfm],
60
+ rehypePlugins: [rehypeRaw, rehypePrismPlus],
61
+ components: mergedComponents,
62
+ children: content
63
+ }
64
+ ) });
65
+ }
66
+
67
+ // src/components/BlogCard.tsx
68
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
69
+ function BlogCard({
70
+ blog,
71
+ basePath = "/blog",
72
+ renderLink,
73
+ className = "",
74
+ showCategory = true,
75
+ showReadingTime = true,
76
+ showDate = true
77
+ }) {
78
+ const href = `${basePath}/${blog.slug}`;
79
+ const defaultLink = (href2, children) => /* @__PURE__ */ jsx2("a", { href: href2, children });
80
+ const Link = renderLink || defaultLink;
81
+ return /* @__PURE__ */ jsxs(
82
+ "article",
83
+ {
84
+ className: `bg-white rounded-lg border border-gray-200 hover:border-gray-300 p-6 transition-colors ${className}`,
85
+ children: [
86
+ /* @__PURE__ */ jsx2("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
87
+ showCategory && blog.category && /* @__PURE__ */ jsx2("span", { className: "inline-flex items-center rounded-full border border-transparent bg-orange-100 px-2.5 py-0.5 text-xs font-semibold text-orange-500", children: blog.category }),
88
+ (showReadingTime || showDate) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
89
+ showReadingTime && /* @__PURE__ */ jsx2("span", { children: blog.readingTime }),
90
+ showReadingTime && showDate && /* @__PURE__ */ jsx2("span", { children: "\u2022" }),
91
+ showDate && /* @__PURE__ */ jsx2("time", { dateTime: blog.date, children: new Date(blog.date).toLocaleDateString("en-US", {
92
+ year: "numeric",
93
+ month: "long",
94
+ day: "numeric"
95
+ }) })
96
+ ] })
97
+ ] }) }),
98
+ Link(
99
+ href,
100
+ /* @__PURE__ */ jsx2("h2", { className: "font-semibold text-xl text-gray-700 mb-2 hover:underline transition-colors", children: blog.title })
101
+ ),
102
+ /* @__PURE__ */ jsx2("p", { className: "text-sm text-gray-500 leading-6 mb-4", children: blog.description }),
103
+ Link(
104
+ href,
105
+ /* @__PURE__ */ jsx2("span", { className: "inline-flex items-center text-blue-600 hover:text-blue-700 font-medium text-sm", children: "Read more \u2192" })
106
+ )
107
+ ]
108
+ }
109
+ );
110
+ }
111
+
112
+ // src/components/BlogList.tsx
113
+ import { jsx as jsx3 } from "react/jsx-runtime";
114
+ function BlogList({
115
+ blogs,
116
+ basePath = "/blog",
117
+ renderLink,
118
+ className = "",
119
+ emptyMessage = "No blog posts found.",
120
+ cardProps
121
+ }) {
122
+ if (blogs.length === 0) {
123
+ return /* @__PURE__ */ jsx3("div", { className: `text-center text-gray-500 py-12 ${className}`, children: emptyMessage });
124
+ }
125
+ return /* @__PURE__ */ jsx3("div", { className: `space-y-6 ${className}`, children: blogs.map((blog) => /* @__PURE__ */ jsx3(
126
+ BlogCard,
127
+ {
128
+ blog,
129
+ basePath,
130
+ renderLink,
131
+ ...cardProps
132
+ },
133
+ blog.slug
134
+ )) });
135
+ }
136
+
137
+ // src/components/BlogPlaceholder.tsx
138
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
139
+ function BlogPlaceholder({ count = 3, className = "" }) {
140
+ return /* @__PURE__ */ jsx4("div", { className: `space-y-6 ${className}`, children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsxs2("div", { className: "bg-white rounded-lg border border-gray-200 p-6 animate-pulse", children: [
141
+ /* @__PURE__ */ jsx4("div", { className: "h-4 bg-gray-200 rounded mb-3" }),
142
+ /* @__PURE__ */ jsx4("div", { className: "h-6 bg-gray-200 rounded mb-2" }),
143
+ /* @__PURE__ */ jsx4("div", { className: "h-4 bg-gray-200 rounded w-3/4" })
144
+ ] }, i)) });
145
+ }
146
+
147
+ // src/hooks/useBlogs.ts
148
+ import { useState, useEffect } from "react";
149
+ function useBlogs(blogs) {
150
+ const [filteredBlogs, setFilteredBlogs] = useState(blogs);
151
+ const [searchTerm, setSearchTerm] = useState("");
152
+ const [selectedCategory, setSelectedCategory] = useState(null);
153
+ useEffect(() => {
154
+ let filtered = blogs;
155
+ if (searchTerm) {
156
+ filtered = filtered.filter(
157
+ (blog) => blog.title.toLowerCase().includes(searchTerm.toLowerCase()) || blog.description.toLowerCase().includes(searchTerm.toLowerCase())
158
+ );
159
+ }
160
+ if (selectedCategory) {
161
+ filtered = filtered.filter((blog) => blog.category === selectedCategory);
162
+ }
163
+ setFilteredBlogs(filtered);
164
+ }, [blogs, searchTerm, selectedCategory]);
165
+ const categories = Array.from(
166
+ new Set(blogs.map((blog) => blog.category).filter(Boolean))
167
+ );
168
+ return {
169
+ blogs: filteredBlogs,
170
+ searchTerm,
171
+ setSearchTerm,
172
+ selectedCategory,
173
+ setSelectedCategory,
174
+ categories
175
+ };
176
+ }
177
+ export {
178
+ BlogCard,
179
+ BlogList,
180
+ BlogPlaceholder,
181
+ BlogRenderer as MarkdownRenderer,
182
+ useBlogs
183
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@haroonwaves/blog-kit-react",
3
+ "version": "0.0.4",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "peerDependencies": {
12
+ "react": ">=18",
13
+ "react-dom": ">=18"
14
+ },
15
+ "dependencies": {
16
+ "react-markdown": "^10.1.0",
17
+ "remark-gfm": "^4.0.1",
18
+ "rehype-prism-plus": "^2.0.1",
19
+ "rehype-raw": "^7.0.0",
20
+ "prismjs": "^1.30.0"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^7.2.0",
24
+ "typescript": "^5.5.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup src/index.ts --format cjs,esm --dts",
28
+ "type-check": "tsc --noEmit"
29
+ }
30
+ }