diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d271f3d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,36 @@
+name: CI
+
+on:
+ push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main, develop]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [20.x]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Lint
+ run: npm run lint
+
+ - name: Build
+ run: npm run build
+
+ - name: Test
+ run: npm test
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..aab15bf
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,46 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*.*.*'
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ packages: write
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js 20.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ registry-url: 'https://npm.pkg.github.com'
+ scope: '@frontkom'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Lint
+ run: npm run lint
+
+ - name: Test
+ run: npm test
+
+ - name: Publish to GitHub Packages
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: npm publish
+
+ - name: Create GitHub Release with changelog
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ github.ref }}
+ release_name: ${{ github.ref_name }}
+ generate_release_notes: true
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2d9c2a5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,238 @@
+# @frontkom/block-react-parser
+
+A Gutenberg-generated HTML to React parser. Converts WordPress block editor (Gutenberg) markup into React components with full support for custom tag and block handlers.
+
+## Features
+
+- **Parses Gutenberg blocks** from WordPress HTML markup
+- **Custom handlers** for block types and HTML tags
+- **Dynamic component rendering** with proper prop passing
+- **React 19 support** with automatic JSX runtime
+- **Comprehensive test coverage** (30+ tests)
+- **ESLint validated** code quality
+- **CI/CD ready** with GitHub Actions
+
+## Installation
+
+Add GitHub Packages to your `.npmrc` (requires a GitHub token with `read:packages` for private repos, or the default `GITHUB_TOKEN` in CI):
+
+```bash
+@frontkom:registry=https://npm.pkg.github.com
+//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
+```
+
+Then install:
+
+```bash
+npm install @frontkom/block-react-parser
+```
+
+## Usage
+
+### Basic Usage
+
+```jsx
+import { parseBlocks, Provider, customBlocks, customTags } from '@frontkom/block-react-parser';
+
+function MyComponent({ html }) {
+ const blocks = parseBlocks(html);
+
+ return (
+
+ {block.innerHTML} +
+); + +const handlers = customBlocks({ + 'core/paragraph': CustomParagraph +}); + +function App({ html, blocks }) { + return ( +








Hello world with bold and italic text.
+ +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('bold'), 'Should render bold text'); + assert.ok(rendered.includes('italic'), 'Should render italic text'); +}); + +// ============ Heading Block Render Tests ============ +test('Block component renders heading block', () => { + const html = ` + +
+ ++ +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('The only way to do great work is to love what you do.
+ +Steve Jobs
Steve Jobs'), 'Should render citation'); +}); + +// ============ Code Block Render Tests ============ +test('Block component renders code block', () => { + const html = ` + ++ +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('const x = 42;'), 'Should render code element'); + assert.ok(rendered.includes('const x = 42;'), 'Should render code content'); +}); + +// ============ Preformatted Block Render Tests ============ +test('Block component renders preformatted block', () => { + const html = ` + +Indented + text+ +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('{ + const html = ` + + + +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('class="wp-block-buttons"'), 'Should render buttons wrapper'); + assert.ok(rendered.includes('Click Me'), 'Should render first button text'); + assert.ok(rendered.includes('Outline'), 'Should render second button text'); + assert.ok(rendered.includes('href="https://example.com"'), 'Should preserve href'); + assert.ok(rendered.includes('is-style-outline'), 'Should preserve outline style class'); +}); + +// ============ Separator Block Render Tests ============ +test('Block component renders separator block', () => { + const html = ` + +
+ +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('
{ + const html = ` + + + +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('{ + const html = ` + ++ +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('
Name Value Item 1 100 Item 2 200 '), 'Should render thead'); + assert.ok(rendered.includes(''), 'Should render tbody'); + assert.ok(rendered.includes('
Name '), 'Should render header cell'); + assert.ok(rendered.includes('Item 1 '), 'Should render data cell'); +}); + +// ============ Video Block Render Tests ============ +test('Block component renders video block', () => { + const html = ` + ++ +`; + + const rendered = parseAndRender(html); + + assert.ok(rendered.includes('