diff --git a/README.md b/README.md index 9ea5a00..bf8d58e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Blog Kit -A powerful Next.js-first toolkit for building professional blog systems with markdown. Consists of -two packages: a core library for server-side markdown parsing and a React component library for -rendering beautiful blog UIs. +A powerful Next.js-first toolkit for building professional content-driven websites, documentation +sites, and blogs with markdown. Consists of two packages: a core library for server-side markdown +parsing and a React component library for rendering beautiful UIs. ## Documentation diff --git a/app/content/docs/api-reference.md b/app/content/docs/api-reference.md index b3e6949..967db04 100644 --- a/app/content/docs/api-reference.md +++ b/app/content/docs/api-reference.md @@ -18,50 +18,56 @@ The complete reference for everything exported by Blog Kit. For tutorials and us All core functions require Node.js (`fs` module) and only work in server environments (Next.js SSR/SSG, Node.js scripts, etc.). -#### `getAllBlogsMeta(config: BlogConfig): BlogMeta[]` +#### `getAllContentMeta(config: ContentConfig): ContentMeta[]` -Returns an array of all blog metadata sorted by date (newest first). +Returns an array of all content metadata sorted by date (newest first). **Parameters:** - `config.contentDirectory` (string): Path to your content directory -- `config.blogSubdirectory` (string, optional): Subdirectory for blog files (defaults to `'blog'`) +- `config.contentSubdirectory` (string, optional): Subdirectory for content files (defaults to + `'blog'`) -**Returns:** Array of `BlogMeta` objects +**Returns:** Array of `ContentMeta` objects ```typescript -const blogsMeta = getAllBlogsMeta({ +import { getAllContentMeta } from '@haroonwaves/blog-kit-core'; + +const docsMeta = getAllContentMeta({ contentDirectory: process.cwd(), - blogSubdirectory: 'content/blog', + contentSubdirectory: 'content/docs', }); -blogsMeta.forEach((meta) => { +docsMeta.forEach((meta) => { console.log(meta.title, meta.slug, meta.readingTime); }); ``` -#### `getBlog(slug: string, config: BlogConfig): Blog | null` +#### `getContent(slug: string, config: ContentConfig): Content | null` -Returns the full blog data (metadata + content) for a specific slug. +Returns the full content data (metadata + body) for a specific slug. **Parameters:** -- `slug` (string): The blog post slug (filename without `.md`) +- `slug` (string): The content slug (filename without `.md`) - `config.contentDirectory` (string): Path to your content directory -- `config.blogSubdirectory` (string, optional): Subdirectory for blog files (defaults to `'blog'`) +- `config.contentSubdirectory` (string, optional): Subdirectory for content files (defaults to + `'blog'`) -**Returns:** `Blog` object or `null` if not found +**Returns:** `Content` object or `null` if not found ```typescript -const blog = getBlog('my-blog-post', { +import { getContent } from '@haroonwaves/blog-kit-core'; + +const doc = getContent('getting-started', { contentDirectory: process.cwd(), - blogSubdirectory: 'content/blog', + contentSubdirectory: 'content/docs', }); -if (blog) { - console.log(blog.metadata.title); - console.log(blog.metadata.readingTime); - console.log(blog.content); +if (doc) { + console.log(doc.metadata.title); + console.log(doc.metadata.readingTime); + console.log(doc.body); } ``` @@ -71,33 +77,33 @@ if (blog) { #### Components -##### `BlogList` +##### `ContentList` -A stateless component that renders a list of blog cards. Works with both server-side and client-side -rendering. +A stateless component that renders a list of content cards. Works with both server-side and +client-side rendering. -| Prop | Type | Default | Description | -| -------------- | --------------------------------------------------------------- | ------------------------ | ------------------------------------------ | -| `metadata` | `BlogMeta[]` | _required_ | Array of blog metadata to render | -| `title` | `string` | — | Page title (rendered as h1) | -| `description` | `string` | — | Page description | -| `basePath` | `string` | `'/blog'` | Base URL for blog post links | -| `renderLink` | `(href, children) => ReactNode` | — | Custom link renderer (e.g. Next.js `Link`) | -| `className` | `string` | — | CSS class for the card container | -| `emptyMessage` | `string` | `'No blog posts found.'` | Message when list is empty | -| `cardProps` | `Omit` | — | Props forwarded to each `BlogCard` | -| `classNames` | `{ title?, description? }` | — | CSS overrides for title and description | +| Prop | Type | Default | Description | +| -------------- | ------------------------------------------------------------------ | --------------------- | ------------------------------------------ | +| `metadata` | `ContentMeta[]` | _required_ | Array of content metadata to render | +| `title` | `string` | — | Page title (rendered as h1) | +| `description` | `string` | — | Page description | +| `basePath` | `string` | `'/blog'` | Base URL for content links | +| `renderLink` | `(href, children) => ReactNode` | — | Custom link renderer (e.g. Next.js `Link`) | +| `className` | `string` | — | CSS class for the card container | +| `emptyMessage` | `string` | `'No content found.'` | Message when list is empty | +| `cardProps` | `Omit` | — | Props forwarded to each `ContentCard` | +| `classNames` | `{ title?, description? }` | — | CSS overrides for title and description | --- -##### `BlogCard` +##### `ContentCard` -Renders a single blog post card with category badges, reading time, and date. +Renders a single content item card with category badges, reading time, and date. | Prop | Type | Default | Description | | ----------------- | ------------------------------- | ---------- | ------------------------------------------ | -| `metadata` | `BlogMeta` | _required_ | Blog metadata object | -| `basePath` | `string` | `'/blog'` | Base path for blog links | +| `metadata` | `ContentMeta` | _required_ | Content metadata object | +| `basePath` | `string` | `'/blog'` | Base path for content links | | `renderLink` | `(href, children) => ReactNode` | — | Custom link renderer (e.g. Next.js `Link`) | | `className` | `string` | — | Additional CSS classes | | `showCategory` | `boolean` | `true` | Show category badge | @@ -106,15 +112,15 @@ Renders a single blog post card with category badges, reading time, and date. --- -##### `BlogRenderer` +##### `ContentRenderer` Renders markdown content with syntax highlighting (Prism), GFM support, heading anchor links, and styled typography. Supports custom component overrides. | Prop | Type | Default | Description | | ----------------- | ------------------------------- | ---------- | --------------------------------------- | -| `content` | `string` | _required_ | Markdown content to render | -| `metadata` | `BlogMeta` | _required_ | Blog metadata object | +| `body` | `string` | _required_ | Markdown content to render | +| `metadata` | `ContentMeta` | _required_ | Content metadata object | | `className` | `string` | — | Additional CSS classes | | `components` | `Record` | — | Override default HTML element renderers | | `showCategory` | `boolean` | `true` | Show category badge | @@ -130,24 +136,24 @@ styled typography. Supports custom component overrides. A client component for searching and category filtering. -| Prop | Type | Default | Description | -| --------------------- | ------------------------------- | ------------------- | --------------------------------- | -| `searchTerm` | `string` | _required_ | Current search input value | -| `setSearchTerm` | `(term: string) => void` | _required_ | Callback for search input changes | -| `selectedCategory` | `string \| null` | _required_ | Currently selected category | -| `setSelectedCategory` | `(cat: string \| null) => void` | _required_ | Callback for category changes | -| `categories` | `string[]` | _required_ | List of available category labels | -| `postsCount` | `number` | — | Number of results to display | -| `placeholder` | `string` | `'Search blogs...'` | Search input placeholder text | -| `className` | `string` | — | Container CSS class | -| `classNames` | `object` | — | CSS overrides (see below) | +| Prop | Type | Default | Description | +| --------------------- | ------------------------------- | --------------------- | --------------------------------- | +| `searchTerm` | `string` | _required_ | Current search input value | +| `setSearchTerm` | `(term: string) => void` | _required_ | Callback for search input changes | +| `selectedCategory` | `string \| null` | _required_ | Currently selected category | +| `setSelectedCategory` | `(cat: string \| null) => void` | _required_ | Callback for category changes | +| `categories` | `string[]` | _required_ | List of available category labels | +| `contentCount` | `number` | — | Number of results to display | +| `placeholder` | `string` | `'Search content...'` | Search input placeholder text | +| `className` | `string` | — | Container CSS class | +| `classNames` | `object` | — | CSS overrides (see below) | **`classNames` keys:** `input`, `categoryContainer`, `pill`, `activePill`, `inactivePill`, -`postsCount` +`contentCount` --- -##### `BlogPlaceholder` +##### `ContentPlaceholder` Renders animated skeleton loading cards. @@ -160,38 +166,38 @@ Renders animated skeleton loading cards. #### Hooks -##### `useBlogs(blogsMeta: BlogMeta[])` +##### `useContent(allContentMeta: ContentMeta[])` A client-side hook that provides search and category filter state management with debounced search (500ms). **Parameters:** -- `blogsMeta` (BlogMeta[]): Array of blog metadata to filter +- `allContentMeta` (ContentMeta[]): Array of content metadata to filter **Returns:** -| Field | Type | Description | -| --------------------- | ------------------------------- | -------------------------------------- | -| `metadata` | `BlogMeta[]` | Filtered blog posts | -| `searchTerm` | `string` | Current search input value | -| `setSearchTerm` | `(term: string) => void` | Update the search term | -| `selectedCategory` | `string \| null` | Currently selected category | -| `setSelectedCategory` | `(cat: string \| null) => void` | Update selected category | -| `categories` | `string[]` | All unique categories from input blogs | +| Field | Type | Description | +| --------------------- | ------------------------------- | -------------------------------- | +| `metadata` | `ContentMeta[]` | Filtered content items | +| `searchTerm` | `string` | Current search input value | +| `setSearchTerm` | `(term: string) => void` | Update the search term | +| `selectedCategory` | `string \| null` | Currently selected category | +| `setSelectedCategory` | `(cat: string \| null) => void` | Update selected category | +| `categories` | `string[]` | All unique categories from input | --- #### Utility Functions -##### `filterBlogs(blogs: BlogMeta[], searchTerm?: string, selectedCategory?: string | null): BlogMeta[]` +##### `filterContent(items: ContentMeta[], searchTerm?: string, selectedCategory?: string | null): ContentMeta[]` -Pure utility to filter blogs by search term (matches title and description) and/or category. Useful -for server-side filtering or custom implementations outside of `useBlogs`. +Pure utility to filter content by search term (matches title and description) and/or category. +Useful for server-side filtering or custom implementations outside of `useContent`. -##### `getAvailableCategories(blogs: BlogMeta[]): string[]` +##### `getAvailableCategories(items: ContentMeta[]): string[]` -Returns a deduplicated array of all categories across the provided blog metadata. +Returns a deduplicated array of all categories across the provided content metadata. --- @@ -200,7 +206,7 @@ Returns a deduplicated array of all categories across the provided blog metadata All types are exported from both `@haroonwaves/blog-kit-core` and `@haroonwaves/blog-kit-react`. ```typescript -interface BlogMeta { +interface ContentMeta { title: string; description: string; date: string; @@ -209,13 +215,13 @@ interface BlogMeta { readingTime: string; } -interface Blog { - metadata: BlogMeta; - content: string; +interface Content { + metadata: ContentMeta; + body: string; } -interface BlogConfig { +interface ContentConfig { contentDirectory: string; - blogSubdirectory?: string; // defaults to 'blog' + contentSubdirectory?: string; // defaults to 'blog' } ``` diff --git a/app/content/docs/getting-started.md b/app/content/docs/getting-started.md index 09a3396..40f34ae 100644 --- a/app/content/docs/getting-started.md +++ b/app/content/docs/getting-started.md @@ -8,8 +8,8 @@ date: 2024-01-02 ## Getting Started -This guide walks you from zero to a fully working blog. By the end, you'll have a blog list page and -individual blog post pages — all statically generated for maximum performance. +This guide walks you from zero to a fully working content site. By the end, you'll have a content +list page and individual content pages — all statically generated for maximum performance. ### 1. Install the Packages @@ -58,9 +58,9 @@ your-project/ └── src/ └── app/ ├── blog/ - │ ├── page.tsx ← Blog list page + │ ├── page.tsx ← Content list page │ └── [slug]/ - │ └── page.tsx ← Blog post page + │ └── page.tsx ← Content item page └── layout.tsx ``` @@ -83,25 +83,25 @@ Welcome to my blog! This is my first post built with Blog Kit. **Required frontmatter fields:** -- `title` (string): The blog post title +- `title` (string): The content title - `description` (string): A brief description/summary - `date` (string): Publication date (ISO format recommended: YYYY-MM-DD) **Optional frontmatter fields:** -- `categories` (string[]): Array of categories/tags for the post +- `categories` (string[]): Array of categories/tags for the content -### 4. Create the Blog Configuration +### 4. Create the Configuration -Define your blog configuration once and reuse it across pages: +Define your configuration once and reuse it across pages: ```ts -// lib/blog.ts (or wherever you keep shared config) -import type { BlogConfig } from '@haroonwaves/blog-kit-core'; +// lib/content.ts (or wherever you keep shared config) +import type { ContentConfig } from '@haroonwaves/blog-kit-core'; -export const blogConfig: BlogConfig = { +export const contentConfig: ContentConfig = { contentDirectory: process.cwd(), // The project root - blogSubdirectory: 'content/blog', // Path to your markdown files + contentSubdirectory: 'content/blog', // Path to your markdown files }; ``` @@ -109,16 +109,16 @@ export const blogConfig: BlogConfig = { ```tsx // app/blog/page.tsx -import { getAllBlogsMeta } from '@haroonwaves/blog-kit-core'; -import { BlogList } from '@haroonwaves/blog-kit-react'; +import { getAllContentMeta } from '@haroonwaves/blog-kit-core'; +import { ContentList } from '@haroonwaves/blog-kit-react'; import Link from 'next/link'; -import { blogConfig } from '@/lib/blog'; +import { contentConfig } from '@/lib/content'; export default function BlogPage() { - const blogsMeta = getAllBlogsMeta(blogConfig); + const blogsMeta = getAllContentMeta(contentConfig); return ( - ({ slug: meta.slug })); } export default async function BlogPostPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; - const blog = getBlog(slug, blogConfig); + const blog = getContent(slug, contentConfig); if (!blog) notFound(); return (
- +
); } ``` -`BlogRenderer` automatically handles markdown rendering with syntax highlighting, GFM support, +`ContentRenderer` automatically handles markdown rendering with syntax highlighting, GFM support, heading anchor links, and beautiful typography — all out of the box. ### Next Steps -Your blog is now fully functional! Here's what to explore next: +Your site is now fully functional! Here's what to explore next: - **[Guides](/docs/guides)** — Add search/filtering, dark mode, and customize component styling - **[API Reference](/docs/api-reference)** — Complete reference for all functions, components, diff --git a/app/content/docs/guides.md b/app/content/docs/guides.md index cfa72be..bff3e19 100644 --- a/app/content/docs/guides.md +++ b/app/content/docs/guides.md @@ -2,31 +2,31 @@ title: Guides description: Learn how to add search and filtering, customize component styling, and set up dark mode for your - Blog Kit blog. + Blog Kit content site. date: 2024-01-04 --- ## Guides -Practical guides for common tasks. If you haven't set up your blog yet, start with the +Practical guides for common tasks. If you haven't set up your site yet, start with the [Getting Started](/docs/getting-started) guide first. --- ### Adding Search & Filtering -The `useBlogs` hook provides instant client-side search and category filtering. Pair it with the -`Filter` and `BlogList` components for a complete interactive blog list. +The `useContent` hook provides instant client-side search and category filtering. Pair it with the +`Filter` and `ContentList` components for a complete interactive content list. ```tsx 'use client'; -import { BlogList, Filter, useBlogs } from '@haroonwaves/blog-kit-react'; -import type { BlogMeta } from '@haroonwaves/blog-kit-react'; +import { ContentList, Filter, useContent } from '@haroonwaves/blog-kit-react'; +import type { ContentMeta } from '@haroonwaves/blog-kit-react'; -export default function BlogListPage({ blogsMeta }: { blogsMeta: BlogMeta[] }) { +export default function ContentListPage({ blogsMeta }: { blogsMeta: ContentMeta[] }) { const { metadata, searchTerm, setSearchTerm, selectedCategory, setSelectedCategory, categories } = - useBlogs(blogsMeta); + useContent(blogsMeta); return (
@@ -36,10 +36,10 @@ export default function BlogListPage({ blogsMeta }: { blogsMeta: BlogMeta[] }) { selectedCategory={selectedCategory} setSelectedCategory={setSelectedCategory} categories={categories} - postsCount={metadata.length} + contentCount={metadata.length} /> - +
); } @@ -49,54 +49,54 @@ Then pass metadata from a server component: ```tsx // app/blog/page.tsx (Server Component) -import { getAllBlogsMeta } from '@haroonwaves/blog-kit-core'; -import { blogConfig } from '@/lib/blog'; -import BlogListPage from './blog-list-page'; +import { getAllContentMeta } from '@haroonwaves/blog-kit-core'; +import { contentConfig } from '@/lib/content'; +import ContentListPage from './content-list-page'; export default function BlogPage() { - const blogsMeta = getAllBlogsMeta(blogConfig); - return ; + const blogsMeta = getAllContentMeta(contentConfig); + return ; } ``` > [!TIP] **Which rendering strategy should I use?** > -> 1. **Client-Side Filtering (< 500 posts)**: Keep your blog list page static (SSG) but use the -> `useBlogs` hook to handle searching. This makes search instant and keeps your site 100% static. -> **This is the recommended approach for most blogs.** -> 2. **Dynamic Filtering (SSR)**: Only recommended if you have thousands of blog posts and want to -> avoid sending all metadata to the user's browser. -> 3. **Static Rendering (SSG)**: For individual blog post pages, always use SSG via +> 1. **Client-Side Filtering (< 500 items)**: Keep your content list page static (SSG) but use the +> `useContent` hook to handle searching. This makes search instant and keeps your site 100% +> static. **This is the recommended approach for most sites.** +> 2. **Dynamic Filtering (SSR)**: Only recommended if you have thousands of items and want to avoid +> sending all metadata to the user's browser. +> 3. **Static Rendering (SSG)**: For individual content pages, always use SSG via > `generateStaticParams()` for maximum speed. #### Server-Side Filtering -If you prefer to filter on the server (e.g., for very large blogs), you can use the `filterBlogs` +If you prefer to filter on the server (e.g., for very large sites), you can use the `filterContent` and `getAvailableCategories` utility functions directly: ```tsx -import { filterBlogs, getAvailableCategories } from '@haroonwaves/blog-kit-react'; -import { getAllBlogsMeta } from '@haroonwaves/blog-kit-core'; +import { filterContent, getAvailableCategories } from '@haroonwaves/blog-kit-react'; +import { getAllContentMeta } from '@haroonwaves/blog-kit-core'; -const allBlogs = getAllBlogsMeta(blogConfig); +const allBlogs = getAllContentMeta(contentConfig); const categories = getAvailableCategories(allBlogs); -const filtered = filterBlogs(allBlogs, searchTerm, selectedCategory); +const filtered = filterContent(allBlogs, searchTerm, selectedCategory); ``` --- ### Customizing Components -#### Overriding BlogRenderer Elements +#### Overriding ContentRenderer Elements -The `BlogRenderer` renders markdown using styled HTML elements. You can override any element by +The `ContentRenderer` renders markdown using styled HTML elements. You can override any element by passing custom components through the `components` prop: ```tsx -import { BlogRenderer } from '@haroonwaves/blog-kit-react'; +import { ContentRenderer } from '@haroonwaves/blog-kit-react'; import type { ComponentProps } from 'react'; -function BlogPost({ content, metadata }) { +function BlogPost({ body, metadata }) { const customComponents = { // Custom blockquote with a different style blockquote: (props: ComponentProps<'blockquote'>) => ( @@ -112,7 +112,7 @@ function BlogPost({ content, metadata }) { ), }; - return ; + return ; } ``` @@ -125,10 +125,10 @@ You can override any HTML element: `h1`–`h6`, `p`, `a`, `blockquote`, `code`, Several components accept a `classNames` prop for fine-grained style overrides without replacing the entire component: -**BlogList:** +**ContentList:** ```tsx - ``` #### Loading Placeholders -Use `BlogPlaceholder` to show skeleton cards while your data loads: +Use `ContentPlaceholder` to show skeleton cards while your data loads: ```tsx -import { BlogPlaceholder } from '@haroonwaves/blog-kit-react'; +import { ContentPlaceholder } from '@haroonwaves/blog-kit-react'; function LoadingState() { - return ; + return ; } ``` diff --git a/app/content/docs/overview.md b/app/content/docs/overview.md index 60330e5..5a6ba40 100644 --- a/app/content/docs/overview.md +++ b/app/content/docs/overview.md @@ -1,19 +1,19 @@ --- title: Overview description: - A powerful, free toolkit for creating markdown-based blogs. Perfect for developers who want full - control over their blog. + A powerful, free toolkit for creating markdown-based content sites. Perfect for developers who + want full control over their blog, documentation, or portfolio. date: 2024-01-01 --- -## Build Your Professional Blog with Next.js & Blog Kit +## Build Your Professional Site with Next.js & Blog Kit Blog Kit is a modern, Next.js-first toolkit designed to help you **build your own professional -blog** with markdown in minutes. Whether you're creating a personal blog, documentation site, or -content platform, Blog Kit provides all the essential tools you need to build a high-performance -blog with minimal configuration. +content site** with markdown in minutes. Whether you're creating a personal blog, documentation +site, or content platform, Blog Kit provides all the essential tools you need to build a +high-performance site with minimal configuration. -**Perfect for developers, writers, and content creators** who want full control over their blog +**Perfect for developers, writers, and content creators** who want full control over their content using the power of Next.js and React server components. ### Key Features @@ -22,10 +22,10 @@ using the power of Next.js and React server components. - ✅ Parse markdown files with frontmatter (using [gray-matter](https://github.com/jonschlinkert/gray-matter)) -- ✅ Extract blog metadata (title, description, date, category) +- ✅ Extract content metadata (title, description, date, category) - ✅ Calculate reading time automatically (using [reading-time](https://github.com/ngryman/reading-time)) -- ✅ Sort blogs by date (newest first) +- ✅ Sort items by date (newest first) - ✅ TypeScript support with full type definitions - ✅ Zero dependencies on React or UI frameworks @@ -40,10 +40,10 @@ using the power of Next.js and React server components. - ✅ Raw HTML rendering support (using [rehype-raw](https://github.com/rehypejs/rehype-raw)) - ✅ Automatic heading IDs for anchor links (using [rehype-slug-custom-id](https://github.com/valeriangalliat/rehype-slug-custom-id)) -- ✅ Beautiful, stateless `BlogList` for high-performance rendering +- ✅ Beautiful, stateless `ContentList` for high-performance rendering - ✅ Customizable `Filter` component with built-in search, categories, and results count -- ✅ `useBlogs` hook and shared utilities for easy search/filter integration -- ✅ Loading placeholders and responsive blog cards +- ✅ `useContent` hook and shared utilities for easy search/filter integration +- ✅ Loading placeholders and responsive content cards - ✅ Customizable styling with Tailwind CSS classes - ✅ TypeScript support with full type definitions - ✅ Work seamlessly with Next.js App Router and Server Components @@ -62,19 +62,20 @@ Blog Kit is perfect for: ### Packages - **[@haroonwaves/blog-kit-core](https://www.npmjs.com/package/@haroonwaves/blog-kit-core)** - Core - utilities for parsing markdown blog files with frontmatter + utilities for parsing markdown files with frontmatter - **[@haroonwaves/blog-kit-react](https://www.npmjs.com/package/@haroonwaves/blog-kit-react)** - - React components and hooks for rendering blogs + React components and hooks for rendering content ### Live Demo -[haroonwaves.github.io/blog](https://haroonwaves.github.io/blog) - A full blog built using Blog Kit. +[blog-kit.haroonwaves.com](https://blog-kit.haroonwaves.com) - A full blog site built using Blog +Kit. ### Getting Started -Ready to build your blog? Get started in minutes: +Ready to build your site? Get started in minutes: -1. **[Getting Started](/docs/getting-started)** — Install, configure, and build your first blog +1. **[Getting Started](/docs/getting-started)** — Install, configure, and build your first site. 2. **[Guides](/docs/guides)** — Add search, dark mode, and customize components 3. **[API Reference](/docs/api-reference)** — Complete reference for all functions and components diff --git a/app/package.json b/app/package.json index 547b26d..6bf2a09 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "@haroonwaves/blog-kit-app", - "version": "1.4.0", + "version": "2.0.0", "private": true, "scripts": { "dev": "next dev", diff --git a/app/src/app/page.tsx b/app/src/app/page.tsx index db0b095..a195131 100644 --- a/app/src/app/page.tsx +++ b/app/src/app/page.tsx @@ -3,9 +3,9 @@ import { BookOpen, Zap, Code2, Palette, ArrowRight, Github } from 'lucide-react' import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Build Your Own Blog with Next.js, Markdown & TypeScript - Blog Kit', + title: 'Build Professional Content Sites with Next.js & Markdown - Blog Kit', description: - 'Build your own professional blog in minutes with Blog Kit. The ultimate Next.js toolkit for creating beautiful markdown-based blogs and documentation sites.', + 'Build professional blogs, documentation sites, and portfolios in minutes with Blog Kit. The ultimate Next.js toolkit for creating beautiful markdown-based content sites.', keywords: [ 'nextjs blog toolkit', 'build blog with nextjs', @@ -21,17 +21,17 @@ export const metadata: Metadata = { 'blog for developers', ], openGraph: { - title: 'Build Your Own Blog with Next.js, Markdown & TypeScript - Blog Kit', + title: 'Build Professional Content Sites with Next.js & Markdown - Blog Kit', description: - 'Create your own professional blog in minutes. The ultimate Next.js toolkit for building beautiful markdown-based blogs with zero configuration.', + 'Create professional content sites in minutes. The ultimate Next.js toolkit for building beautiful markdown-based blogs, docs, and portfolios.', type: 'website', url: 'https://blog-kit.haroonwaves.com', }, twitter: { card: 'summary_large_image', - title: 'Build Your Own Blog with Next.js, Markdown & TypeScript', + title: 'Build Professional Content Sites with Next.js & Markdown', description: - 'Create your own professional blog in minutes. The ultimate Next.js toolkit for building beautiful markdown-based blogs.', + 'Create professional blogs, documentation, and portfolios in minutes. The ultimate Next.js toolkit for building beautiful markdown-based content sites.', }, alternates: { canonical: 'https://blog-kit.haroonwaves.com', @@ -52,7 +52,7 @@ export default function HomePage() { priceCurrency: 'USD', }, description: - 'A modern, Next.js-first toolkit for building markdown-based blog systems with minimal configuration. Perfect for developers who want to build their own professional blog.', + 'A modern, Next.js toolkit for building markdown-based content sites with minimal configuration. Perfect for developers building professional blogs, documentation, and portfolios.', url: 'https://blog-kit.haroonwaves.com', author: { '@type': 'Person', @@ -96,14 +96,14 @@ export default function HomePage() { {/* Heading */}

- Build Your Own Blog + Build Beautiful Content Sites

{/* Subheading */}

- A modern, Next.js-first toolkit for building professional markdown-based blog systems - with minimal configuration. + A modern, Next.js toolkit for building professional blogs, documentation, and content + sites with minimal configuration.

{/* CTA Buttons */} @@ -134,16 +134,16 @@ export default function HomePage() {

Perfect For

Whether you're a developer, writer, or content creator, Blog Kit gives you the - tools to build your own blog exactly how you want it + tools to build your own site exactly how you want it

{/* Use Case 1 */}
📝
-

Personal Blogs

+

Portfolios & Blogs

- Build your own personal blog to share your thoughts, tutorials, and experiences + Build your own personal site to share your thoughts, projects, and experiences with a beautiful, fast platform that's easy to maintain.

@@ -175,7 +175,7 @@ export default function HomePage() {

- Why Choose Blog Kit to Build Your Blog? + Why Choose Blog Kit for Your Project?

Skip the complexity of markdown parsing, syntax highlighting, and UI components. Blog @@ -192,7 +192,7 @@ export default function HomePage() {

Zero Configuration

- Works out of the box with sensible defaults. Start building your blog + Works out of the box with sensible defaults. Start building your site immediately without complex setup or configuration.

@@ -208,7 +208,7 @@ export default function HomePage() {

TypeScript First

Full type safety and IntelliSense support for a superior developer experience - when building your blog. + when building your content site.

@@ -223,7 +223,7 @@ export default function HomePage() {

Production Ready

Battle-tested components with dark mode support and polished user experience. - Your blog will look professional from day one. + Your site will look professional from day one.

@@ -235,10 +235,10 @@ export default function HomePage() {
-

Ready to Build Your Own Blog?

+

Ready to Build Your New Site?

- Explore the documentation and start building your blog or documentation site today. - It's free, open-source, and takes just minutes to get started. + Explore the documentation and start building your blog, documentation, or portfolio + today. It's free, open-source, and takes just minutes to get started.

file.endsWith('.md')) .map((file) => { const slug = file.replace('.md', ''); - const filePath = path.join(blogDirectory, file); + const filePath = path.join(contentDirectory, file); const fileContent = fs.readFileSync(filePath, 'utf8'); const { data, content } = matter(fileContent); @@ -36,27 +36,27 @@ export function getAllBlogsMeta(config: BlogConfig): BlogMeta[] { slug, ...data, readingTime: readingTimeText, - } as BlogMeta; + } as ContentMeta; }) .sort((a, b) => { return new Date(b.date).getTime() - new Date(a.date).getTime(); }); - return blogs; + return allContent; } /** - * SSR and SSG only: Get blog from the filesystem. + * SSR and SSG only: Get content from the filesystem. * This function requires Node.js fs module and only works in server environments * (Next.js SSR/SSG, Node.js scripts, etc.). * - * @param slug - Blog post slug/identifier - * @param config - Blog kit configuration - * @returns Blog data or null if not found + * @param slug - Content slug/identifier + * @param config - Content kit configuration + * @returns Content data or null if not found */ -export function getBlog(slug: string, config: BlogConfig): Blog | null { - const blogDirectory = path.join(config.contentDirectory, config.blogSubdirectory || 'blog'); - const filePath = path.join(blogDirectory, `${slug}.md`); +export function getContent(slug: string, config: ContentConfig): Content | null { + const contentDirectory = path.join(config.contentDirectory, config.contentSubdirectory || 'blog'); + const filePath = path.join(contentDirectory, `${slug}.md`); if (!fs.existsSync(filePath)) { return null; @@ -69,10 +69,10 @@ export function getBlog(slug: string, config: BlogConfig): Blog | null { return { metadata: { - ...(data as Omit), + ...(data as Omit), slug, readingTime: readingTimeText, }, - content, + body: content, }; } diff --git a/packages/blog-kit-core/src/types.ts b/packages/blog-kit-core/src/types.ts index a928ef6..b8296ed 100644 --- a/packages/blog-kit-core/src/types.ts +++ b/packages/blog-kit-core/src/types.ts @@ -1,4 +1,4 @@ -export interface BlogMeta { +export interface ContentMeta { title: string; description: string; date: string; @@ -7,12 +7,12 @@ export interface BlogMeta { readingTime: string; } -export interface Blog { - metadata: BlogMeta; - content: string; +export interface Content { + metadata: ContentMeta; + body: string; } -export interface BlogConfig { +export interface ContentConfig { contentDirectory: string; - blogSubdirectory?: string; // defaults to 'blog' + contentSubdirectory?: string; // defaults to 'blog' } diff --git a/packages/blog-kit-react/package.json b/packages/blog-kit-react/package.json index 90a8bea..8228b88 100644 --- a/packages/blog-kit-react/package.json +++ b/packages/blog-kit-react/package.json @@ -1,6 +1,6 @@ { "name": "@haroonwaves/blog-kit-react", - "version": "1.4.0", + "version": "2.0.0", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", diff --git a/packages/blog-kit-react/src/components/BlogCard.tsx b/packages/blog-kit-react/src/components/ContentCard.tsx similarity index 94% rename from packages/blog-kit-react/src/components/BlogCard.tsx rename to packages/blog-kit-react/src/components/ContentCard.tsx index 18972cb..80b3bd0 100644 --- a/packages/blog-kit-react/src/components/BlogCard.tsx +++ b/packages/blog-kit-react/src/components/ContentCard.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import type { BlogMeta } from '../types'; +import type { ContentMeta } from '../types'; import { Badge } from './Badge'; -export interface BlogCardProps { - metadata: BlogMeta; +export interface ContentCardProps { + metadata: ContentMeta; basePath?: string; // e.g., '/blog' renderLink?: (href: string, children: React.ReactNode) => React.ReactNode; className?: string; @@ -12,7 +12,7 @@ export interface BlogCardProps { showDate?: boolean; } -export function BlogCard({ +export function ContentCard({ metadata, basePath = '/blog', renderLink, @@ -20,7 +20,7 @@ export function BlogCard({ showCategory = true, showReadingTime = true, showDate = true, -}: BlogCardProps) { +}: ContentCardProps) { const href = `${basePath}/${metadata.slug}`; const defaultLink = (href: string, children: React.ReactNode) => {children}; const Link = renderLink || defaultLink; diff --git a/packages/blog-kit-react/src/components/BlogList.tsx b/packages/blog-kit-react/src/components/ContentList.tsx similarity index 71% rename from packages/blog-kit-react/src/components/BlogList.tsx rename to packages/blog-kit-react/src/components/ContentList.tsx index de8e019..ef21b3b 100644 --- a/packages/blog-kit-react/src/components/BlogList.tsx +++ b/packages/blog-kit-react/src/components/ContentList.tsx @@ -1,16 +1,16 @@ import React from 'react'; -import { BlogCard, type BlogCardProps } from './BlogCard'; -import type { BlogMeta } from '../types'; +import { ContentCard, type ContentCardProps } from './ContentCard'; +import type { ContentMeta } from '../types'; -export interface BlogListProps { - metadata: BlogMeta[]; +export interface ContentListProps { + metadata: ContentMeta[]; title?: string; description?: string; basePath?: string; - renderLink?: BlogCardProps['renderLink']; + renderLink?: ContentCardProps['renderLink']; className?: string; emptyMessage?: string; - cardProps?: Omit; + cardProps?: Omit; classNames?: { // cardContainer: string; title?: string; @@ -19,20 +19,20 @@ export interface BlogListProps { } /** - * A pure, stateless BlogList component that renders a list of blog cards. + * A pure, stateless ContentList component that renders a list of content cards. * Accepts metadata and renders it exactly as provided. */ -export function BlogList({ +export function ContentList({ metadata, title, description, basePath = '/blog', renderLink, className = '', - emptyMessage = 'No blog posts found.', + emptyMessage = 'No content found.', cardProps, classNames = {}, -}: BlogListProps) { +}: ContentListProps) { return (
{title && ( @@ -60,7 +60,7 @@ export function BlogList({ ) : (
{metadata.map((meta) => ( - {Array.from({ length: count }).map((_, i) => ( diff --git a/packages/blog-kit-react/src/components/BlogRenderer.tsx b/packages/blog-kit-react/src/components/ContentRenderer.tsx similarity index 96% rename from packages/blog-kit-react/src/components/BlogRenderer.tsx rename to packages/blog-kit-react/src/components/ContentRenderer.tsx index 252cfb7..f06973e 100644 --- a/packages/blog-kit-react/src/components/BlogRenderer.tsx +++ b/packages/blog-kit-react/src/components/ContentRenderer.tsx @@ -4,14 +4,14 @@ import remarkGfm from 'remark-gfm'; import rehypePrismPlus from 'rehype-prism-plus'; import rehypeRaw from 'rehype-raw'; import rehypeSlugCustomId from 'rehype-slug-custom-id'; -import type { BlogMeta } from '../types'; +import type { ContentMeta } from '../types'; import '../prism.css'; import { Badge } from './Badge'; -export interface BlogRendererProps { - content: string; - metadata: BlogMeta; +export interface ContentRendererProps { + body: string; + metadata: ContentMeta; className?: string; components?: Record>; showCategory?: boolean; @@ -185,15 +185,15 @@ const defaultComponents = { ), }; -export function BlogRenderer({ - content, +export function ContentRenderer({ + body, metadata, className = '', components, showCategory = true, showReadingTime = true, showDate = true, -}: BlogRendererProps) { +}: ContentRendererProps) { const mergedComponents = { ...defaultComponents, ...components }; return ( @@ -231,7 +231,7 @@ export function BlogRenderer({ rehypePlugins={[rehypeRaw, rehypePrismPlus, rehypeSlugCustomId]} components={mergedComponents} > - {content} + {body}
diff --git a/packages/blog-kit-react/src/components/Filter.tsx b/packages/blog-kit-react/src/components/Filter.tsx index 36fe688..6d28b56 100644 --- a/packages/blog-kit-react/src/components/Filter.tsx +++ b/packages/blog-kit-react/src/components/Filter.tsx @@ -8,7 +8,7 @@ export interface FilterProps { selectedCategory: string | null; setSelectedCategory: (selectedCategory: string | null) => void; categories: string[]; - postsCount?: number; + contentCount?: number; className?: string; placeholder?: string; classNames?: { @@ -17,7 +17,7 @@ export interface FilterProps { pill?: string; activePill?: string; inactivePill?: string; - postsCount?: string; + contentCount?: string; }; } @@ -27,9 +27,9 @@ export function Filter({ selectedCategory, setSelectedCategory, categories, - postsCount, + contentCount, className = '', - placeholder = 'Search blogs...', + placeholder = 'Search content...', classNames = {}, }: FilterProps) { // Consolidate styles to avoid JSX bloat and repetition @@ -56,7 +56,7 @@ export function Filter({ /> setSearchTerm(e.target.value)} @@ -89,12 +89,12 @@ export function Filter({ )}
- {/* Posts Count */} - {postsCount !== undefined && ( + {/* Content Count */} + {contentCount !== undefined && (

- {postsCount} {postsCount === 1 ? 'post' : 'posts'} found + {contentCount} {contentCount === 1 ? 'item' : 'items'} found

)}
diff --git a/packages/blog-kit-react/src/hooks/useBlogs.ts b/packages/blog-kit-react/src/hooks/useContent.ts similarity index 58% rename from packages/blog-kit-react/src/hooks/useBlogs.ts rename to packages/blog-kit-react/src/hooks/useContent.ts index 63220dd..dc3da98 100644 --- a/packages/blog-kit-react/src/hooks/useBlogs.ts +++ b/packages/blog-kit-react/src/hooks/useContent.ts @@ -1,9 +1,9 @@ import { useState, useMemo } from 'react'; -import type { BlogMeta } from '../types'; +import type { ContentMeta } from '../types'; import { useDebounce } from './useDebounce'; -import { filterBlogs, getAvailableCategories } from '../utils/filter-utils'; +import { filterContent, getAvailableCategories } from '../utils/filter-utils'; -export function useBlogs(blogsMeta: BlogMeta[]) { +export function useContent(allContentMeta: ContentMeta[]) { const [searchTerm, setSearchTerm] = useState(''); const [selectedCategory, setSelectedCategory] = useState(null); @@ -11,13 +11,13 @@ export function useBlogs(blogsMeta: BlogMeta[]) { // Compute filtered results using the pure utility const metadata = useMemo(() => { - return filterBlogs(blogsMeta, debouncedSearchTerm, selectedCategory); - }, [blogsMeta, debouncedSearchTerm, selectedCategory]); + return filterContent(allContentMeta, debouncedSearchTerm, selectedCategory); + }, [allContentMeta, debouncedSearchTerm, selectedCategory]); // Compute available categories const categories = useMemo(() => { - return getAvailableCategories(blogsMeta); - }, [blogsMeta]); + return getAvailableCategories(allContentMeta); + }, [allContentMeta]); return { metadata, diff --git a/packages/blog-kit-react/src/index.ts b/packages/blog-kit-react/src/index.ts index 83e0249..294b9b0 100644 --- a/packages/blog-kit-react/src/index.ts +++ b/packages/blog-kit-react/src/index.ts @@ -2,23 +2,23 @@ export type * from './types'; // Components -export { BlogRenderer } from './components/BlogRenderer'; -export type { BlogRendererProps } from './components/BlogRenderer'; +export { ContentRenderer } from './components/ContentRenderer'; +export type { ContentRendererProps } from './components/ContentRenderer'; -export { BlogCard } from './components/BlogCard'; -export type { BlogCardProps } from './components/BlogCard'; +export { ContentCard } from './components/ContentCard'; +export type { ContentCardProps } from './components/ContentCard'; -export { BlogList } from './components/BlogList'; -export type { BlogListProps } from './components/BlogList'; +export { ContentList } from './components/ContentList'; +export type { ContentListProps } from './components/ContentList'; export { Filter } from './components/Filter'; export type { FilterProps } from './components/Filter'; -export { BlogPlaceholder } from './components/BlogPlaceholder'; -export type { BlogPlaceholderProps } from './components/BlogPlaceholder'; +export { ContentPlaceholder } from './components/ContentPlaceholder'; +export type { ContentPlaceholderProps } from './components/ContentPlaceholder'; // Hooks -export { useBlogs } from './hooks/useBlogs'; +export { useContent } from './hooks/useContent'; // Utils export * from './utils/filter-utils'; diff --git a/packages/blog-kit-react/src/types.ts b/packages/blog-kit-react/src/types.ts index a928ef6..b8296ed 100644 --- a/packages/blog-kit-react/src/types.ts +++ b/packages/blog-kit-react/src/types.ts @@ -1,4 +1,4 @@ -export interface BlogMeta { +export interface ContentMeta { title: string; description: string; date: string; @@ -7,12 +7,12 @@ export interface BlogMeta { readingTime: string; } -export interface Blog { - metadata: BlogMeta; - content: string; +export interface Content { + metadata: ContentMeta; + body: string; } -export interface BlogConfig { +export interface ContentConfig { contentDirectory: string; - blogSubdirectory?: string; // defaults to 'blog' + contentSubdirectory?: string; // defaults to 'blog' } diff --git a/packages/blog-kit-react/src/utils/filter-utils.ts b/packages/blog-kit-react/src/utils/filter-utils.ts index 14ed103..6cc8024 100644 --- a/packages/blog-kit-react/src/utils/filter-utils.ts +++ b/packages/blog-kit-react/src/utils/filter-utils.ts @@ -1,24 +1,24 @@ -import type { BlogMeta } from '../types'; +import type { ContentMeta } from '../types'; -export function filterBlogs( - blogs: BlogMeta[], +export function filterContent( + items: ContentMeta[], searchTerm: string = '', selectedCategory: string | null = null -): BlogMeta[] { - let filtered = blogs; +): ContentMeta[] { + let filtered = items; if (searchTerm) { const lowerSearch = searchTerm.toLowerCase(); filtered = filtered.filter( - (blog) => - (blog.title?.toLowerCase() || '').includes(lowerSearch) || - (blog.description?.toLowerCase() || '').includes(lowerSearch) + (item) => + (item.title?.toLowerCase() || '').includes(lowerSearch) || + (item.description?.toLowerCase() || '').includes(lowerSearch) ); } if (selectedCategory) { - filtered = filtered.filter((blog) => { - const categories = blog.categories ?? []; + filtered = filtered.filter((item) => { + const categories = item.categories ?? []; return categories.includes(selectedCategory); }); } @@ -26,6 +26,6 @@ export function filterBlogs( return filtered; } -export function getAvailableCategories(blogs: BlogMeta[]): string[] { - return Array.from(new Set(blogs.flatMap((blog) => blog.categories ?? []))); +export function getAvailableCategories(items: ContentMeta[]): string[] { + return Array.from(new Set(items.flatMap((item) => item.categories ?? []))); }